From 480923433e426241c145f3c7efa8d493c3bda4fa Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:40:40 +0200 Subject: [PATCH 001/105] Fix(TV): Crash issue due to missing xml, Closes #2980 --- app/src/main/res/layout/repository_item_tv.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/layout/repository_item_tv.xml b/app/src/main/res/layout/repository_item_tv.xml index 7ef3b04db..cf2ee0c41 100644 --- a/app/src/main/res/layout/repository_item_tv.xml +++ b/app/src/main/res/layout/repository_item_tv.xml @@ -106,6 +106,13 @@ android:textSize="12sp" tools:text="https://github.com/..." /> + Date: Tue, 30 Jun 2026 20:40:58 +0200 Subject: [PATCH 002/105] Fix(TV): Crash issue due to missing xml, Closes #2980 (#2981) --- app/src/main/res/layout/repository_item_tv.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/layout/repository_item_tv.xml b/app/src/main/res/layout/repository_item_tv.xml index 7ef3b04db..cf2ee0c41 100644 --- a/app/src/main/res/layout/repository_item_tv.xml +++ b/app/src/main/res/layout/repository_item_tv.xml @@ -106,6 +106,13 @@ android:textSize="12sp" tools:text="https://github.com/..." /> + Date: Thu, 2 Jul 2026 18:29:35 +0300 Subject: [PATCH 003/105] Focus "Add Repo" by Right click on Extensions page (#2986) --- app/src/main/res/layout/fragment_downloads.xml | 4 ++-- app/src/main/res/layout/fragment_extensions.xml | 2 ++ app/src/main/res/layout/repository_item_tv.xml | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/fragment_downloads.xml b/app/src/main/res/layout/fragment_downloads.xml index 96e1e50ab..ac5d383df 100644 --- a/app/src/main/res/layout/fragment_downloads.xml +++ b/app/src/main/res/layout/fragment_downloads.xml @@ -261,7 +261,7 @@ android:padding="8dp" android:src="@drawable/ic_network_stream" - app:tint="?attr/textColor"> + app:tint="?attr/textColor" /> @@ -306,7 +306,7 @@ android:paddingBottom="100dp" android:clipToPadding="false" android:descendantFocusability="afterDescendants" - android:nextFocusUp="@id/download_appbar" + android:nextFocusUp="@id/download_stream_button_tv" android:nextFocusLeft="@id/navigation_downloads" android:nextFocusDown="@id/download_queue_button" android:tag="@string/tv_no_focus_tag" diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml index ccb56df15..6dd2d8662 100644 --- a/app/src/main/res/layout/fragment_extensions.xml +++ b/app/src/main/res/layout/fragment_extensions.xml @@ -241,6 +241,8 @@ android:focusable="true" android:nextFocusLeft="@id/plugin_storage_appbar" android:nextFocusUp="@id/repo_recycler_view" + android:nextFocusRight="@id/add_repo_button_imageview" + android:nextFocusDown="@id/add_repo_button_imageview" android:src="@drawable/ic_baseline_add_24" app:tint="?attr/textColor" /> diff --git a/app/src/main/res/layout/repository_item_tv.xml b/app/src/main/res/layout/repository_item_tv.xml index cf2ee0c41..53d5386b1 100644 --- a/app/src/main/res/layout/repository_item_tv.xml +++ b/app/src/main/res/layout/repository_item_tv.xml @@ -141,6 +141,7 @@ android:contentDescription="@string/download" android:focusable="true" android:nextFocusLeft="@id/action_settings" + android:nextFocusRight="@id/add_repo_button_imageview" android:padding="12dp" tools:src="@drawable/ic_baseline_add_24" /> From 971027d2d1eeedd9dcf3dcf42b8a380a09b2cc2f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:26:54 -0600 Subject: [PATCH 004/105] [skip ci] Add JvmName import to MainAPI (#2988) Necessary for non JVM platforms or it won't resolve. On non JVM platforms the annotation is simply erased at compile time but still compiled as long as the import is present. --- .../src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 301673320..086a97ef1 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -41,6 +41,7 @@ import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 +import kotlin.jvm.JvmName import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.time.Clock From 56c6400fd9c54ca8fedbdd88984154b17ae3a1be Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:27:46 -0600 Subject: [PATCH 005/105] [skip ci] Remove unnecessary ?. (#2982) It is a build warning --- .../cloudstream3/ui/settings/extensions/PluginsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index c4f5ac396..09c202d43 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -238,7 +238,7 @@ class PluginsViewModel : ViewModel() { if (it.pluginWrapper.plugin.language == null) { return@filter selectedLanguages.contains("none") } - selectedLanguages.contains(it.pluginWrapper.plugin.language?.lowercase()) + selectedLanguages.contains(it.pluginWrapper.plugin.language.lowercase()) } } From aaa87c1ccc607038046d91de11e3e418f2b61647 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:31:41 -0600 Subject: [PATCH 006/105] LibraryFragment: migrate to kotlinx serialization (#2972) --- .../cloudstream3/ui/library/LibraryFragment.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index c5f8fa3d9..e73a8c75a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -20,6 +20,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView +import com.fasterxml.jackson.annotation.JsonProperty import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.APIHolder @@ -52,12 +53,13 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import java.util.concurrent.CopyOnWriteArrayList import kotlin.math.abs const val LIBRARY_FOLDER = "library_folder" - enum class LibraryOpenerType(@StringRes val stringRes: Int) { Default(R.string.action_default), Provider(R.string.none), @@ -67,13 +69,15 @@ enum class LibraryOpenerType(@StringRes val stringRes: Int) { } /** Used to store how the user wants to open said poster */ +@Serializable data class LibraryOpener( - val openType: LibraryOpenerType, - val providerData: ProviderLibraryData?, + @JsonProperty("openType") @SerialName("openType") val openType: LibraryOpenerType, + @JsonProperty("providerData") @SerialName("providerData") val providerData: ProviderLibraryData?, ) +@Serializable data class ProviderLibraryData( - val apiName: String + @JsonProperty("apiName") @SerialName("apiName") val apiName: String, ) class LibraryFragment : BaseFragment( @@ -568,4 +572,4 @@ class LibraryFragment : BaseFragment( } } -class MenuSearchView(context: Context) : SearchView(context) \ No newline at end of file +class MenuSearchView(context: Context) : SearchView(context) From 60da132d3f80aaa3d945b8eabaea398d200f811c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:40:57 -0600 Subject: [PATCH 007/105] [skip ci] MyDramaList: migrate to kotlinx serialization (#2870) --- .../cloudstream3/metaproviders/MyDramaList.kt | 238 +++++++++--------- 1 file changed, 126 insertions(+), 112 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt index e7e1175f0..a28e1c37b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt @@ -29,10 +29,13 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement import okhttp3.Interceptor import okhttp3.Response -//Reference: https://mydramalist.github.io/MDL-API/ +// Reference: https://mydramalist.github.io/MDL-API/ abstract class MyDramaListAPI : MainAPI() { override var name = "MyDramaList" override val hasMainPage = true @@ -77,7 +80,7 @@ abstract class MyDramaListAPI : MainAPI() { val list = app.get( url = "${request.data}&limit=20&page=$page&lang=en-US", interceptor = headerInterceptor - ).parsed().map { element -> + ).parsed>().map { element -> element.toSearchResponse() } return newHomePageResponse(request.name, list) @@ -88,7 +91,7 @@ abstract class MyDramaListAPI : MainAPI() { url = "$API_HOST/search/titles", data = mapOf("q" to query), interceptor = headerInterceptor - ).parsed().map { element -> + ).parsed>().map { element -> element.toSearchResponse() } } @@ -210,69 +213,69 @@ abstract class MyDramaListAPI : MainAPI() { } } + @Serializable data class Data( - val type: TvType? = null, - val media: MediaSummary? = null, + @JsonProperty("type") @SerialName("type") val type: TvType? = null, + @JsonProperty("media") @SerialName("media") val media: MediaSummary? = null, ) - class SearchResult : ArrayList() - - class Recommendations : ArrayList() - + @Serializable data class MediaSummary( - @JsonProperty("id") val id: Long, - @JsonProperty("title") val title: String, - @JsonProperty("original_title") val originalTitle: String, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("permalink") val permalink: String? = null, - @JsonProperty("type") val type: String, - @JsonProperty("media_type") val mediaType: String? = null, - @JsonProperty("country") val country: String? = null, - @JsonProperty("language") val language: String? = null, - @JsonProperty("images") val images: Images, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("permalink") @SerialName("permalink") val permalink: String? = null, + @JsonProperty("type") @SerialName("type") val type: String, + @JsonProperty("media_type") @SerialName("media_type") val mediaType: String? = null, + @JsonProperty("country") @SerialName("country") val country: String? = null, + @JsonProperty("language") @SerialName("language") val language: String? = null, + @JsonProperty("images") @SerialName("images") val images: Images, ) + @Serializable data class Images( - @JsonProperty("thumb") val thumb: String? = null, - @JsonProperty("medium") val medium: String? = null, - @JsonProperty("poster") val poster: String? = null, + @JsonProperty("thumb") @SerialName("thumb") val thumb: String? = null, + @JsonProperty("medium") @SerialName("medium") val medium: String? = null, + @JsonProperty("poster") @SerialName("poster") val poster: String? = null, ) + @Serializable data class Media( - @JsonProperty("id") val id: Long, - @JsonProperty("slug") val slug: String, - @JsonProperty("title") val title: String, - @JsonProperty("original_title") val originalTitle: String, - @JsonProperty("year") val mediaYear: Int, - @JsonProperty("episodes") val episodes: Long, - @JsonProperty("rating") val mediaRating: Double, - @JsonProperty("permalink") val permalink: String? = null, - @JsonProperty("synopsis") val synopsis: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("media_type") val mediaType: String? = null, - @JsonProperty("country") val country: String? = null, - @JsonProperty("language") val language: String? = null, - @JsonProperty("images") val images: Images, - @JsonProperty("alt_titles") val altTitles: List? = null, - @JsonProperty("votes") val votes: Long? = null, - @JsonProperty("aired_start") val airedStart: String? = null, - @JsonProperty("released") val released: String? = null, - @JsonProperty("release_dates_fmt") val releaseDatesFmt: String, - @JsonProperty("genres") val genres: List? = null, - @JsonProperty("trailer") val trailer: Trailer?, - @JsonProperty("watchers") val watchers: Long, - @JsonProperty("ranked") val ranked: Long, - @JsonProperty("popularity") val popularity: Long, - @JsonProperty("runtime") val runtime: Long, - @JsonProperty("reviews_count") val reviewsCount: Long, - @JsonProperty("recs_count") val recsCount: Long, - @JsonProperty("comments_count") val commentsCount: Long, - @JsonProperty("certification") val certification: String, - @JsonProperty("status") val status: String, - @JsonProperty("enable_ads") val enableAds: Boolean, - @JsonProperty("sources") val sources: List, - @JsonProperty("updated_at") val updatedAt: Long, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("slug") @SerialName("slug") val slug: String, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String, + @JsonProperty("year") @SerialName("year") val mediaYear: Int, + @JsonProperty("episodes") @SerialName("episodes") val episodes: Long, + @JsonProperty("rating") @SerialName("rating") val mediaRating: Double, + @JsonProperty("permalink") @SerialName("permalink") val permalink: String? = null, + @JsonProperty("synopsis") @SerialName("synopsis") val synopsis: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("media_type") @SerialName("media_type") val mediaType: String? = null, + @JsonProperty("country") @SerialName("country") val country: String? = null, + @JsonProperty("language") @SerialName("language") val language: String? = null, + @JsonProperty("images") @SerialName("images") val images: Images, + @JsonProperty("alt_titles") @SerialName("alt_titles") val altTitles: List? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Long? = null, + @JsonProperty("aired_start") @SerialName("aired_start") val airedStart: String? = null, + @JsonProperty("released") @SerialName("released") val released: String? = null, + @JsonProperty("release_dates_fmt") @SerialName("release_dates_fmt") val releaseDatesFmt: String, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("trailer") @SerialName("trailer") val trailer: Trailer?, + @JsonProperty("watchers") @SerialName("watchers") val watchers: Long, + @JsonProperty("ranked") @SerialName("ranked") val ranked: Long, + @JsonProperty("popularity") @SerialName("popularity") val popularity: Long, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Long, + @JsonProperty("reviews_count") @SerialName("reviews_count") val reviewsCount: Long, + @JsonProperty("recs_count") @SerialName("recs_count") val recsCount: Long, + @JsonProperty("comments_count") @SerialName("comments_count") val commentsCount: Long, + @JsonProperty("certification") @SerialName("certification") val certification: String, + @JsonProperty("status") @SerialName("status") val status: String, + @JsonProperty("enable_ads") @SerialName("enable_ads") val enableAds: Boolean, + @JsonProperty("sources") @SerialName("sources") val sources: List, + @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: Long, ) { suspend fun fetchCredits(): List { val actors = app.get( @@ -290,11 +293,11 @@ abstract class MyDramaListAPI : MainAPI() { return actors } - suspend fun fetchRecommendations(): Recommendations { + suspend fun fetchRecommendations(): ArrayList { return app.get( url = "$API_HOST/titles/$id/recommendations", interceptor = headerInterceptor - ).parsed() + ).parsed>() } suspend fun fetchTrailer(): String? { @@ -319,7 +322,7 @@ abstract class MyDramaListAPI : MainAPI() { return app.get( url = "$API_HOST/titles/${this.id}/episodes", interceptor = headerInterceptor - ).parsed().map { + ).parsed>().map { it.episodes }.flatten().map { ep -> val link = LinkData( @@ -348,95 +351,106 @@ abstract class MyDramaListAPI : MainAPI() { } } + @Serializable data class Genre( - @JsonProperty("id") val id: Long, - @JsonProperty("name") val name: String, - @JsonProperty("slug") val slug: String, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("slug") @SerialName("slug") val slug: String, ) + @Serializable data class Tag( - @JsonProperty("id") val id: Long, - @JsonProperty("name") val name: String, - @JsonProperty("slug") val slug: String, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("slug") @SerialName("slug") val slug: String, ) + @Serializable data class Source( - @JsonProperty("xid") val xid: String, - @JsonProperty("name") val name: String, - @JsonProperty("source") val source: String, - @JsonProperty("source_type") val sourceType: String, - @JsonProperty("link") val link: String, - @JsonProperty("image") val image: String, + @JsonProperty("xid") @SerialName("xid") val xid: String, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("source") @SerialName("source") val source: String, + @JsonProperty("source_type") @SerialName("source_type") val sourceType: String, + @JsonProperty("link") @SerialName("link") val link: String, + @JsonProperty("image") @SerialName("image") val image: String, ) + @Serializable data class Trailer( - @JsonProperty("id") val id: Long? = null, + @JsonProperty("id") @SerialName("id") val id: Long? = null, ) + @Serializable data class Credits( - @JsonProperty("cast") val cast: List, - @JsonProperty("crew") val crew: List, + @JsonProperty("cast") @SerialName("cast") val cast: List, + @JsonProperty("crew") @SerialName("crew") val crew: List, ) + @Serializable data class Cast( - @JsonProperty("id") val id: Long, - @JsonProperty("name") val name: String, - @JsonProperty("url") val url: String, - @JsonProperty("slug") val slug: String, - @JsonProperty("images") val images: Images, - @JsonProperty("character_name") val characterName: String, - @JsonProperty("role") val role: String, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("url") @SerialName("url") val url: String, + @JsonProperty("slug") @SerialName("slug") val slug: String, + @JsonProperty("images") @SerialName("images") val images: Images, + @JsonProperty("character_name") @SerialName("character_name") val characterName: String, + @JsonProperty("role") @SerialName("role") val role: String, ) + @Serializable data class Crew( - @JsonProperty("id") val id: Long, - @JsonProperty("name") val name: String, - @JsonProperty("slug") val slug: String, - @JsonProperty("images") val images: Images, - @JsonProperty("job") val job: String, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("slug") @SerialName("slug") val slug: String, + @JsonProperty("images") @SerialName("images") val images: Images, + @JsonProperty("job") @SerialName("job") val job: String, ) - class ShowEpisodes : ArrayList() - + @Serializable data class ShowEpisodesItem( - @JsonProperty("name") val name: String, - @JsonProperty("release_date") val releaseDate: String, - @JsonProperty("episodes") val episodes: List, - @JsonProperty("timezone") val timezone: String, - @JsonProperty("total") val total: Int, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List, + @JsonProperty("timezone") @SerialName("timezone") val timezone: String, + @JsonProperty("total") @SerialName("total") val total: Int, ) + @Serializable data class ShowEpisode( - @JsonProperty("id") val id: Int, - @JsonProperty("episode_number") val episodeNumber: Int, - @JsonProperty("rating") val rating: Double, - @JsonProperty("votes") val votes: Int, - @JsonProperty("released_at") val releasedAt: String, + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("episode_number") @SerialName("episode_number") val episodeNumber: Int, + @JsonProperty("rating") @SerialName("rating") val rating: Double, + @JsonProperty("votes") @SerialName("votes") val votes: Int, + @JsonProperty("released_at") @SerialName("released_at") val releasedAt: String, ) + @Serializable data class TrailerRoot( - @JsonProperty("trailer") val trailer: TrailerNode, + @JsonProperty("trailer") @SerialName("trailer") val trailer: TrailerNode, ) + @Serializable data class TrailerNode( - @JsonProperty("trailer") val trailerDetails: TrailerDetails, + @JsonProperty("trailer") @SerialName("trailer") val trailerDetails: TrailerDetails, ) + @Serializable data class TrailerDetails( - @JsonProperty("id") val id: Long, - @JsonProperty("source") val source: String, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("source") @SerialName("source") val source: String, ) + @Serializable data class LinkData( - @JsonProperty("id") val id: Long? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("season") val season: Int? = null, - @JsonProperty("episode") val episode: Int? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("orgTitle") val orgTitle: String? = null, - @JsonProperty("lastSeason") val lastSeason: Int? = null, - @JsonProperty("date") val date: String? = null, - @JsonProperty("airedDate") val airedDate: String? = null, + @JsonProperty("id") @SerialName("id") val id: Long? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("season") @SerialName("season") val season: Int? = null, + @JsonProperty("episode") @SerialName("episode") val episode: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("orgTitle") @SerialName("orgTitle") val orgTitle: String? = null, + @JsonProperty("lastSeason") @SerialName("lastSeason") val lastSeason: Int? = null, + @JsonProperty("date") @SerialName("date") val date: String? = null, + @JsonProperty("airedDate") @SerialName("airedDate") val airedDate: String? = null, ) } From b1022805bbf993c48ece069fddefc4016fa3e695 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:41:12 -0600 Subject: [PATCH 008/105] Add kotlin-reflect to jvmCommonMain in library (#2989) --- gradle/libs.versions.toml | 1 + library/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 99f1aca80..d0c8eef94 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -92,6 +92,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitKtx" } juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardet" } +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinGradlePlugin" } kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinGradlePlugin" } kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinxAtomicfu" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 4b713656e..23351f7d0 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -81,6 +81,7 @@ kotlin { val jvmCommonMain by creating { dependsOn(commonMain.get()) dependencies { + implementation(libs.kotlin.reflect) implementation(libs.newpipeextractor) } } From d164c36123841bdb0a980abbc152ba0f577d0a75 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:42:32 -0600 Subject: [PATCH 009/105] [skip ci] Remove completely commented and out of date ProviderTests file (#2973) --- .../lagradost/cloudstream3/ProviderTests.kt | 220 ------------------ 1 file changed, 220 deletions(-) delete mode 100644 app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt diff --git a/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt b/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt deleted file mode 100644 index e09e194ef..000000000 --- a/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt +++ /dev/null @@ -1,220 +0,0 @@ -package com.lagradost.cloudstream3 - -// import com.lagradost.cloudstream3.APIHolder.allProviders -// import com.lagradost.cloudstream3.mvvm.logError -// import com.lagradost.cloudstream3.utils.Qualities -// import com.lagradost.cloudstream3.utils.SubtitleHelper -// import kotlinx.coroutines.runBlocking -// import org.junit.Assert -// import org.junit.Test - -class ProviderTests { -// private fun getAllProviders(): List { -// return allProviders.filter { !it.usesWebView } -// } - -// private suspend fun loadLinks(api: MainAPI, url: String?): Boolean { -// Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) -// if (url == null) return true -// var linksLoaded = 0 -// try { -// val success = api.loadLinks(url, false, {}) { link -> -// Assert.assertTrue( -// "Api ${api.name} returns link with invalid Quality", -// Qualities.values().map { it.value }.contains(link.quality) -// ) -// Assert.assertTrue( -// "Api ${api.name} returns link with invalid url", -// link.url.length > 4 -// ) -// linksLoaded++ -// } -// if (success) { -// return linksLoaded > 0 -// } -// Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success) -// } catch (e: Exception) { -// if (e.cause is NotImplementedError) { -// Assert.fail("Provider has not implemented .loadLinks") -// } -// logError(e) -// } -// return true -// } - -// private suspend fun testSingleProviderApi(api: MainAPI): Boolean { -// val searchQueries = listOf("over", "iron", "guy") -// var correctResponses = 0 -// var searchResult: List? = null -// for (query in searchQueries) { -// val response = try { -// api.search(query, 1) -// } catch (e: Exception) { -// if (e.cause is NotImplementedError) { -// Assert.fail("Provider has not implemented .search") -// } -// logError(e) -// null -// }?.items -// if (!response.isNullOrEmpty()) { -// correctResponses++ -// if (searchResult == null) { -// searchResult = response -// } -// } -// } - -// if (correctResponses == 0 || searchResult == null) { -// System.err.println("Api ${api.name} did not return any valid search responses") -// return false -// } - -// try { -// var validResults = false -// for (result in searchResult) { -// Assert.assertEquals( -// "Invalid apiName on response on ${api.name}", -// result.apiName, -// api.name -// ) -// val load = api.load(result.url) ?: continue -// Assert.assertEquals( -// "Invalid apiName on load on ${api.name}", -// load.apiName, -// result.apiName -// ) -// Assert.assertTrue( -// "Api ${api.name} on load does not contain any of the supportedTypes", -// api.supportedTypes.contains(load.type) -// ) -// when (load) { -// is AnimeLoadResponse -> { -// val gotNoEpisodes = -// load.episodes.keys.isEmpty() || load.episodes.keys.any { load.episodes[it].isNullOrEmpty() } - -// if (gotNoEpisodes) { -// println("Api ${api.name} got no episodes on ${load.url}") -// continue -// } - -// val url = (load.episodes[load.episodes.keys.first()])?.first()?.data -// validResults = loadLinks(api, url) -// if (!validResults) continue -// } -// is MovieLoadResponse -> { -// val gotNoEpisodes = load.dataUrl.isBlank() -// if (gotNoEpisodes) { -// println("Api ${api.name} got no movie on ${load.url}") -// continue -// } - -// validResults = loadLinks(api, load.dataUrl) -// if (!validResults) continue -// } -// is TvSeriesLoadResponse -> { -// val gotNoEpisodes = load.episodes.isEmpty() -// if (gotNoEpisodes) { -// println("Api ${api.name} got no episodes on ${load.url}") -// continue -// } - -// validResults = loadLinks(api, load.episodes.first().data) -// if (!validResults) continue -// } -// } -// break -// } - -// Assert.assertTrue("Api ${api.name} did not load on any}", validResults) -// } catch (e: Exception) { -// if (e.cause is NotImplementedError) { -// Assert.fail("Provider has not implemented .load") -// } -// logError(e) -// return false -// } -// return true -// } - -// @Test -// fun providersExist() { -// Assert.assertTrue(getAllProviders().isNotEmpty()) -// } - -// @Test -// fun providerCorrectData() { -// val langTagsIETF = SubtitleHelper.languages.map { it.IETF_tag } -// Assert.assertFalse("langTagsIETF does not contain any languages", langTagsIETF.isNullOrEmpty()) -// for (api in getAllProviders()) { -// Assert.assertTrue("Api does not contain a mainUrl", api.mainUrl != "NONE") -// Assert.assertTrue("Api does not contain a name", api.name != "NONE") -// Assert.assertTrue( -// "Api ${api.name} does not contain a valid language code", -// langTagsIETF.contains(api.lang) -// ) -// Assert.assertTrue( -// "Api ${api.name} does not contain any supported types", -// api.supportedTypes.isNotEmpty() -// ) -// } -// } - -// @Test -// fun providerCorrectHomepage() { -// runBlocking { -// getAllProviders().amap { api -> -// if (api.hasMainPage) { -// try { -// val homepage = api.getMainPage() -// when { -// homepage == null -> { -// Assert.fail("Homepage provider ${api.name} did not correctly load homepage!") -// } -// homepage.items.isEmpty() -> { -// Assert.fail("Homepage provider ${api.name} does not contain any items!") -// } -// homepage.items.any { it.list.isEmpty() } -> { -// Assert.fail("Homepage provider ${api.name} does not have any items on result!") -// } -// } -// } catch (e: Exception) { -// if (e.cause is NotImplementedError) { -// Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") -// } -// logError(e) -// } -// } -// } -// } -// } - -// // @Test -// // fun testSingleProvider() { -// // testSingleProviderApi(ThenosProvider()) -// // } - -// @Test -// suspend fun providerCorrect() { -// val invalidProvider = ArrayList>() -// val providers = getAllProviders() -// providers.amap { api -> -// try { -// println("Trying $api") -// if (testSingleProviderApi(api)) { -// println("Success $api") -// } else { -// System.err.println("Error $api") -// invalidProvider.add(Pair(api, null)) -// } -// } catch (e: Exception) { -// logError(e) -// invalidProvider.add(Pair(api, e)) -// } -// } - -// println("Invalid providers are: ") -// for (provider in invalidProvider) { -// println("${provider.first}") -// } -// } -} \ No newline at end of file From faacc1a8938bc8eb307c631ef8336154fe6615b2 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:43:53 -0600 Subject: [PATCH 010/105] [skip ci] Remove `failOnNoDiscoveredTests = false` (#2985) Not needed anymore since we actually have tests now. --- build.gradle.kts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 609a94b3a..f58fb584f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,10 +8,3 @@ plugins { alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.kotlin.serialization) apply false } - -allprojects { - // https://docs.gradle.org/current/userguide/upgrading_major_version_9.html#test_task_fails_when_no_tests_are_discovered - tasks.withType().configureEach { - failOnNoDiscoveredTests = false - } -} From 982189cdf008627f9f8b7e8f0b714ccbf33b96fb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:12:28 -0600 Subject: [PATCH 011/105] AniListApi: migrate to kotlinx serialization (#2952) --- .../syncproviders/providers/AniListApi.kt | 571 ++++++++++-------- 1 file changed, 322 insertions(+), 249 deletions(-) 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 441eb1bf2..d3e7f22c7 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 @@ -27,9 +27,10 @@ import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject import com.lagradost.cloudstream3.utils.DataStoreHelper.toYear import com.lagradost.cloudstream3.utils.txt +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import java.net.URLEncoder import java.util.Locale @@ -54,7 +55,7 @@ class AniListApi : SyncAPI() { val token = AuthToken( accessToken = sanitizer["access_token"] ?: throw ErrorLoadingException("No access token"), - //refreshToken = sanitizer["refresh_token"], + // refreshToken = sanitizer["refresh_token"], accessTokenLifetime = APIHolder.unixTime + sanitizer["expires_in"]!!.toLong(), ) return token @@ -81,7 +82,6 @@ class AniListApi : SyncAPI() { override fun urlToId(url: String): String? = url.removePrefix("$mainUrl/anime/").removeSuffix("/") - private fun getUrlFromId(id: Int): String { return "$mainUrl/anime/$id" } @@ -103,7 +103,6 @@ class AniListApi : SyncAPI() { val internalId = (Regex("anilist\\.co/anime/(\\d*)").find(id)?.groupValues?.getOrNull(1) ?: id).toIntOrNull() ?: throw ErrorLoadingException("Invalid internalId") val season = getSeason(internalId).data.media - return SyncAPI.SyncResult( season.id.toString(), nextAiring = season.nextAiringEpisode?.let { @@ -157,14 +156,13 @@ class AniListApi : SyncAPI() { "youtube" -> listOf("https://www.youtube.com/watch?v=${season.trailer.id}") else -> null } - //TODO REST + // TODO REST ) } override suspend fun status(auth: AuthData?, id: String): SyncAPI.AbstractSyncStatus? { val internalId = id.toIntOrNull() ?: return null val data = getDataAboutId(auth ?: return null, internalId) ?: return null - return SyncAPI.SyncStatus( score = Score.from100(data.score), watchedEpisodes = data.progress, @@ -260,24 +258,24 @@ class AniListApi : SyncAPI() { val data = mapOf( "query" to query, - "variables" to - mapOf( - "search" to name, - "page" to 1, - "type" to "ANIME" - ).toJson() + "variables" to Variables( + search = name, + page = 1, + type = "ANIME", + ).toJson() ) val res = app.post( "https://graphql.anilist.co/", - //headers = mapOf(), - data = data,//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars)) + // headers = mapOf(), + data = data, // (if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars)) timeout = 5000 // REASONABLE TIMEOUT ).text.replace("\\", "") - return res.toKotlinObject() + return parseJson(res) } catch (e: Exception) { logError(e) } + return null } @@ -300,7 +298,7 @@ class AniListApi : SyncAPI() { .replace(")", "\\)") })""" ) - //println("NAME $name NEW NAME ${name.replace(blackListRegex, "")}") + // println("NAME $name NEW NAME ${name.replace(blackListRegex, "")}") val shows = searchShows(name.replace(blackListRegex, "")) shows?.data?.page?.media?.find { @@ -458,7 +456,7 @@ class AniListApi : SyncAPI() { cacheTime = 0, ).text - return tryParseJson(data) ?: throw ErrorLoadingException("Error parsing $data") + return tryParseJson(data) ?: throw ErrorLoadingException("Error parsing $data") } } @@ -506,7 +504,6 @@ class AniListApi : SyncAPI() { type = AniListStatusType.None, ) } - } private suspend fun postApi(token: AuthToken, q: String, cache: Boolean = false): String? { @@ -522,71 +519,84 @@ class AniListApi : SyncAPI() { q, "UTF-8" ) - ), //(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars)) + ), // (if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars)) timeout = 5 // REASONABLE TIMEOUT ).text.replace("\\/", "/") } + @Serializable + data class Variables( + @JsonProperty("search") @SerialName("search") val search: String, + @JsonProperty("page") @SerialName("page") val page: Int, + @JsonProperty("type") @SerialName("type") val type: String, + ) + @Serializable data class MediaRecommendation( - @JsonProperty("id") val id: Int, - @JsonProperty("title") val title: Title?, - @JsonProperty("idMal") val idMal: Int?, - @JsonProperty("coverImage") val coverImage: CoverImage?, - @JsonProperty("averageScore") val averageScore: Int? + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("title") @SerialName("title") val title: Title?, + @JsonProperty("idMal") @SerialName("idMal") val idMal: Int?, + @JsonProperty("coverImage") @SerialName("coverImage") val coverImage: CoverImage?, + @JsonProperty("averageScore") @SerialName("averageScore") val averageScore: Int?, ) + @Serializable data class FullAnilistList( - @JsonProperty("data") val data: Data? + @JsonProperty("data") @SerialName("data") val data: Data?, ) + @Serializable data class CompletedAt( - @JsonProperty("year") val year: Int, - @JsonProperty("month") val month: Int, - @JsonProperty("day") val day: Int + @JsonProperty("year") @SerialName("year") val year: Int, + @JsonProperty("month") @SerialName("month") val month: Int, + @JsonProperty("day") @SerialName("day") val day: Int, ) + @Serializable data class StartedAt( - @JsonProperty("year") val year: String?, - @JsonProperty("month") val month: String?, - @JsonProperty("day") val day: String? + @JsonProperty("year") @SerialName("year") val year: String?, + @JsonProperty("month") @SerialName("month") val month: String?, + @JsonProperty("day") @SerialName("day") val day: String?, ) + @Serializable data class Title( - @JsonProperty("english") val english: String?, - @JsonProperty("romaji") val romaji: String? + @JsonProperty("english") @SerialName("english") val english: String?, + @JsonProperty("romaji") @SerialName("romaji") val romaji: String?, ) + @Serializable data class CoverImage( - @JsonProperty("medium") val medium: String?, - @JsonProperty("large") val large: String?, - @JsonProperty("extraLarge") val extraLarge: String? + @JsonProperty("medium") @SerialName("medium") val medium: String?, + @JsonProperty("large") @SerialName("large") val large: String?, + @JsonProperty("extraLarge") @SerialName("extraLarge") val extraLarge: String?, ) + @Serializable data class Media( - @JsonProperty("id") val id: Int, - @JsonProperty("idMal") val idMal: Int?, - @JsonProperty("season") val season: String?, - @JsonProperty("seasonYear") val seasonYear: Int, - @JsonProperty("format") val format: String?, - //@JsonProperty("source") val source: String, - @JsonProperty("episodes") val episodes: Int, - @JsonProperty("title") val title: Title, - @JsonProperty("description") val description: String?, - @JsonProperty("coverImage") val coverImage: CoverImage, - @JsonProperty("synonyms") val synonyms: List, - @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("idMal") @SerialName("idMal") val idMal: Int?, + @JsonProperty("season") @SerialName("season") val season: String?, + @JsonProperty("seasonYear") @SerialName("seasonYear") val seasonYear: Int, + @JsonProperty("format") @SerialName("format") val format: String?, + @JsonProperty("episodes") @SerialName("episodes") val episodes: Int, + @JsonProperty("title") @SerialName("title") val title: Title, + @JsonProperty("description") @SerialName("description") val description: String?, + @JsonProperty("coverImage") @SerialName("coverImage") val coverImage: CoverImage, + @JsonProperty("synonyms") @SerialName("synonyms") val synonyms: List, + @JsonProperty("nextAiringEpisode") @SerialName("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, ) + @Serializable data class Entries( - @JsonProperty("status") val status: String?, - @JsonProperty("completedAt") val completedAt: CompletedAt, - @JsonProperty("startedAt") val startedAt: StartedAt, - @JsonProperty("updatedAt") val updatedAt: Int, - @JsonProperty("progress") val progress: Int, - @JsonProperty("score") val score: Int, - @JsonProperty("private") val private: Boolean, - @JsonProperty("media") val media: Media + @JsonProperty("status") @SerialName("status") val status: String?, + @JsonProperty("completedAt") @SerialName("completedAt") val completedAt: CompletedAt, + @JsonProperty("startedAt") @SerialName("startedAt") val startedAt: StartedAt, + @JsonProperty("updatedAt") @SerialName("updatedAt") val updatedAt: Int, + @JsonProperty("progress") @SerialName("progress") val progress: Int, + @JsonProperty("score") @SerialName("score") val score: Int, + @JsonProperty("private") @SerialName("private") val private: Boolean, + @JsonProperty("media") @SerialName("media") val media: Media, ) { fun toLibraryItem(): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( @@ -613,17 +623,20 @@ class AniListApi : SyncAPI() { } } + @Serializable data class Lists( - @JsonProperty("status") val status: String?, - @JsonProperty("entries") val entries: List + @JsonProperty("status") @SerialName("status") val status: String?, + @JsonProperty("entries") @SerialName("entries") val entries: List, ) + @Serializable data class MediaListCollection( - @JsonProperty("lists") val lists: List + @JsonProperty("lists") @SerialName("lists") val lists: List, ) + @Serializable data class Data( - @JsonProperty("MediaListCollection") val mediaListCollection: MediaListCollection + @JsonProperty("MediaListCollection") @SerialName("MediaListCollection") val mediaListCollection: MediaListCollection, ) private suspend fun getAniListAnimeListSmart(auth: AuthData): Array? { @@ -672,7 +685,6 @@ class AniListApi : SyncAPI() { private suspend fun getFullAniListList(auth: AuthData): FullAnilistList? { val userID = auth.user.id val mediaType = "ANIME" - val query = """ query (${'$'}userID: Int = $userID, ${'$'}MEDIA: MediaType = $mediaType) { MediaListCollection (userId: ${'$'}userID, type: ${'$'}MEDIA) { @@ -711,33 +723,44 @@ class AniListApi : SyncAPI() { } } } - } + } """ val text = postApi(auth.token, query) - return text?.toKotlinObject() + return tryParseJson(text) } suspend fun toggleLike(auth: AuthData, id: Int): Boolean { val q = """mutation (${'$'}animeId: Int = $id) { - ToggleFavourite (animeId: ${'$'}animeId) { - anime { - nodes { - id - title { - romaji - } - } - } - } - }""" + ToggleFavourite (animeId: ${'$'}animeId) { + anime { + nodes { + id + title { + romaji + } + } + } + } + }""" val data = postApi(auth.token, q) return data != "" } /** Used to query a saved MediaItem on the list to get the id for removal */ - data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null) - data class MediaListItem(@JsonProperty("MediaList") val mediaList: MediaListId? = null) - data class MediaListId(@JsonProperty("id") val id: Long? = null) + @Serializable + data class MediaListItemRoot( + @JsonProperty("data") @SerialName("data") val data: MediaListItem? = null, + ) + + @Serializable + data class MediaListItem( + @JsonProperty("MediaList") @SerialName("MediaList") val mediaList: MediaListId? = null, + ) + + @Serializable + data class MediaListId( + @JsonProperty("id") @SerialName("id") val id: Long? = null, + ) private suspend fun postDataAboutId( auth: AuthData, @@ -747,7 +770,6 @@ class AniListApi : SyncAPI() { progress: Int? ): Boolean { val userID = auth.user.id - val q = // Delete item if status type is None if (type == AniListStatusType.None) { @@ -791,22 +813,22 @@ class AniListApi : SyncAPI() { private suspend fun getUser(token: AuthToken): AniListUser? { val q = """ - { - Viewer { - id - name - avatar { - large - } - favourites { - anime { - nodes { - id - } + { + Viewer { + id + name + avatar { + large + } + favourites { + anime { + nodes { + id } } - } - }""" + } + } + }""" val data = postApi(token, q) if (data.isNullOrBlank()) return null val userData = parseJson(data) @@ -839,305 +861,356 @@ class AniListApi : SyncAPI() { return seasons.toList() } + @Serializable data class SeasonResponse( - @JsonProperty("data") val data: SeasonData, + @JsonProperty("data") @SerialName("data") val data: SeasonData, ) + @Serializable data class SeasonData( - @JsonProperty("Media") val media: SeasonMedia, + @JsonProperty("Media") @SerialName("Media") val media: SeasonMedia, ) + @Serializable + data class RecommendedMedia( + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("title") @SerialName("title") val title: MediaTitle?, + @JsonProperty("coverImage") @SerialName("coverImage") val coverImage: MediaCoverImage?, + ) + + @Serializable + data class CharacterMedia( + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("title") @SerialName("title") val title: MediaTitle?, + @JsonProperty("coverImage") @SerialName("coverImage") val coverImage: MediaCoverImage?, + ) + + @Serializable data class SeasonMedia( - @JsonProperty("id") val id: Int?, - @JsonProperty("title") val title: MediaTitle?, - @JsonProperty("idMal") val idMal: Int?, - @JsonProperty("format") val format: String?, - @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, - @JsonProperty("relations") val relations: SeasonEdges?, - @JsonProperty("coverImage") val coverImage: MediaCoverImage?, - @JsonProperty("duration") val duration: Int?, - @JsonProperty("episodes") val episodes: Int?, - @JsonProperty("genres") val genres: List?, - @JsonProperty("synonyms") val synonyms: List?, - @JsonProperty("averageScore") val averageScore: Int?, - @JsonProperty("isAdult") val isAdult: Boolean?, - @JsonProperty("trailer") val trailer: MediaTrailer?, - @JsonProperty("description") val description: String?, - @JsonProperty("characters") val characters: CharacterConnection?, - @JsonProperty("recommendations") val recommendations: RecommendationConnection?, + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("title") @SerialName("title") val title: MediaTitle?, + @JsonProperty("idMal") @SerialName("idMal") val idMal: Int?, + @JsonProperty("format") @SerialName("format") val format: String?, + @JsonProperty("nextAiringEpisode") @SerialName("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, + @JsonProperty("relations") @SerialName("relations") val relations: SeasonEdges?, + @JsonProperty("coverImage") @SerialName("coverImage") val coverImage: MediaCoverImage?, + @JsonProperty("duration") @SerialName("duration") val duration: Int?, + @JsonProperty("episodes") @SerialName("episodes") val episodes: Int?, + @JsonProperty("genres") @SerialName("genres") val genres: List?, + @JsonProperty("synonyms") @SerialName("synonyms") val synonyms: List?, + @JsonProperty("averageScore") @SerialName("averageScore") val averageScore: Int?, + @JsonProperty("isAdult") @SerialName("isAdult") val isAdult: Boolean?, + @JsonProperty("trailer") @SerialName("trailer") val trailer: MediaTrailer?, + @JsonProperty("description") @SerialName("description") val description: String?, + @JsonProperty("characters") @SerialName("characters") val characters: CharacterConnection?, + @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: RecommendationConnection?, ) + @Serializable data class RecommendationConnection( - @JsonProperty("edges") val edges: List = emptyList(), - @JsonProperty("nodes") val nodes: List = emptyList(), - //@JsonProperty("pageInfo") val pageInfo: PageInfo, + @JsonProperty("edges") @SerialName("edges") val edges: List = emptyList(), + @JsonProperty("nodes") @SerialName("nodes") val nodes: List = emptyList(), ) + @Serializable data class RecommendationEdge( - //@JsonProperty("rating") val rating: Int, - @JsonProperty("node") val node: Recommendation, + @JsonProperty("node") @SerialName("node") val node: Recommendation, ) + @Serializable data class Recommendation( - val id: Long, - @JsonProperty("mediaRecommendation") val mediaRecommendation: SeasonMedia?, + @JsonProperty("mediaRecommendation") @SerialName("mediaRecommendation") val mediaRecommendation: RecommendedMedia?, ) + @Serializable data class CharacterName( - @JsonProperty("name") val first: String?, - @JsonProperty("middle") val middle: String?, - @JsonProperty("last") val last: String?, - @JsonProperty("full") val full: String?, - @JsonProperty("native") val native: String?, - @JsonProperty("alternative") val alternative: List?, - @JsonProperty("alternativeSpoiler") val alternativeSpoiler: List?, - @JsonProperty("userPreferred") val userPreferred: String?, + @JsonProperty("name") @SerialName("name") val first: String?, + @JsonProperty("middle") @SerialName("middle") val middle: String?, + @JsonProperty("last") @SerialName("last") val last: String?, + @JsonProperty("full") @SerialName("full") val full: String?, + @JsonProperty("native") @SerialName("native") val native: String?, + @JsonProperty("alternative") @SerialName("alternative") val alternative: List?, + @JsonProperty("alternativeSpoiler") @SerialName("alternativeSpoiler") val alternativeSpoiler: List?, + @JsonProperty("userPreferred") @SerialName("userPreferred") val userPreferred: String?, ) + @Serializable data class CharacterImage( - @JsonProperty("large") val large: String?, - @JsonProperty("medium") val medium: String?, + @JsonProperty("large") @SerialName("large") val large: String?, + @JsonProperty("medium") @SerialName("medium") val medium: String?, ) + @Serializable data class Character( - @JsonProperty("name") val name: CharacterName?, - @JsonProperty("age") val age: String?, - @JsonProperty("image") val image: CharacterImage?, + @JsonProperty("name") @SerialName("name") val name: CharacterName?, + @JsonProperty("age") @SerialName("age") val age: String?, + @JsonProperty("image") @SerialName("image") val image: CharacterImage?, ) + @Serializable data class CharacterEdge( - @JsonProperty("id") val id: Int?, + @JsonProperty("id") @SerialName("id") val id: Int?, /** - MAIN - A primary character role in the media - - SUPPORTING - A supporting character role in the media - - BACKGROUND - A background character in the media + * MAIN - A primary character role in the media + * SUPPORTING - A supporting character role in the media + * BACKGROUND - A background character in the media */ - @JsonProperty("role") val role: String?, - @JsonProperty("name") val name: String?, - @JsonProperty("voiceActors") val voiceActors: List?, - @JsonProperty("favouriteOrder") val favouriteOrder: Int?, - @JsonProperty("media") val media: List?, - @JsonProperty("node") val node: Character?, + @JsonProperty("role") @SerialName("role") val role: String?, + @JsonProperty("name") @SerialName("name") val name: String?, + @JsonProperty("voiceActors") @SerialName("voiceActors") val voiceActors: List?, + @JsonProperty("favouriteOrder") @SerialName("favouriteOrder") val favouriteOrder: Int?, + @JsonProperty("media") @SerialName("media") val media: List?, + @JsonProperty("node") @SerialName("node") val node: Character?, ) + @Serializable data class StaffImage( - @JsonProperty("large") val large: String?, - @JsonProperty("medium") val medium: String?, + @JsonProperty("large") @SerialName("large") val large: String?, + @JsonProperty("medium") @SerialName("medium") val medium: String?, ) + @Serializable data class StaffName( - @JsonProperty("name") val first: String?, - @JsonProperty("middle") val middle: String?, - @JsonProperty("last") val last: String?, - @JsonProperty("full") val full: String?, - @JsonProperty("native") val native: String?, - @JsonProperty("alternative") val alternative: List?, - @JsonProperty("userPreferred") val userPreferred: String?, + @JsonProperty("name") @SerialName("name") val first: String?, + @JsonProperty("middle") @SerialName("middle") val middle: String?, + @JsonProperty("last") @SerialName("last") val last: String?, + @JsonProperty("full") @SerialName("full") val full: String?, + @JsonProperty("native") @SerialName("native") val native: String?, + @JsonProperty("alternative") @SerialName("alternative") val alternative: List?, + @JsonProperty("userPreferred") @SerialName("userPreferred") val userPreferred: String?, ) + @Serializable data class Staff( - @JsonProperty("image") val image: StaffImage?, - @JsonProperty("name") val name: StaffName?, - @JsonProperty("age") val age: Int?, + @JsonProperty("image") @SerialName("image") val image: StaffImage?, + @JsonProperty("name") @SerialName("name") val name: StaffName?, + @JsonProperty("age") @SerialName("age") val age: Int?, ) + @Serializable data class CharacterConnection( - @JsonProperty("edges") val edges: List?, - @JsonProperty("nodes") val nodes: List?, - //@JsonProperty("pageInfo") pageInfo: PageInfo + @JsonProperty("edges") @SerialName("edges") val edges: List?, + @JsonProperty("nodes") @SerialName("nodes") val nodes: List?, ) + @Serializable data class MediaTrailer( - @JsonProperty("id") val id: String?, - @JsonProperty("site") val site: String?, - @JsonProperty("thumbnail") val thumbnail: String?, + @JsonProperty("id") @SerialName("id") val id: String?, + @JsonProperty("site") @SerialName("site") val site: String?, + @JsonProperty("thumbnail") @SerialName("thumbnail") val thumbnail: String?, ) + @Serializable data class MediaCoverImage( - @JsonProperty("extraLarge") val extraLarge: String?, - @JsonProperty("large") val large: String?, - @JsonProperty("medium") val medium: String?, - @JsonProperty("color") val color: String?, + @JsonProperty("extraLarge") @SerialName("extraLarge") val extraLarge: String?, + @JsonProperty("large") @SerialName("large") val large: String?, + @JsonProperty("medium") @SerialName("medium") val medium: String?, + @JsonProperty("color") @SerialName("color") val color: String?, ) + @Serializable data class SeasonNextAiringEpisode( - @JsonProperty("episode") val episode: Int?, - @JsonProperty("timeUntilAiring") val timeUntilAiring: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("timeUntilAiring") @SerialName("timeUntilAiring") val timeUntilAiring: Int?, ) + @Serializable data class SeasonEdges( - @JsonProperty("edges") val edges: List?, + @JsonProperty("edges") @SerialName("edges") val edges: List?, ) + @Serializable data class SeasonEdge( - @JsonProperty("id") val id: Int?, - @JsonProperty("relationType") val relationType: String?, - @JsonProperty("node") val node: SeasonNode?, + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("relationType") @SerialName("relationType") val relationType: String?, + @JsonProperty("node") @SerialName("node") val node: SeasonNode?, ) + @Serializable data class AniListFavoritesMediaConnection( - @JsonProperty("nodes") val nodes: List, + @JsonProperty("nodes") @SerialName("nodes") val nodes: List, ) + @Serializable data class AniListFavourites( - @JsonProperty("anime") val anime: AniListFavoritesMediaConnection, + @JsonProperty("anime") @SerialName("anime") val anime: AniListFavoritesMediaConnection, ) + @Serializable data class MediaTitle( - @JsonProperty("romaji") val romaji: String?, - @JsonProperty("english") val english: String?, - @JsonProperty("native") val native: String?, - @JsonProperty("userPreferred") val userPreferred: String?, + @JsonProperty("romaji") @SerialName("romaji") val romaji: String?, + @JsonProperty("english") @SerialName("english") val english: String?, + @JsonProperty("native") @SerialName("native") val native: String?, + @JsonProperty("userPreferred") @SerialName("userPreferred") val userPreferred: String?, ) + @Serializable data class SeasonNode( - @JsonProperty("id") val id: Int, - @JsonProperty("format") val format: String?, - @JsonProperty("title") val title: Title?, - @JsonProperty("idMal") val idMal: Int?, - @JsonProperty("coverImage") val coverImage: CoverImage?, - @JsonProperty("averageScore") val averageScore: Int? -// @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("format") @SerialName("format") val format: String?, + @JsonProperty("title") @SerialName("title") val title: Title?, + @JsonProperty("idMal") @SerialName("idMal") val idMal: Int?, + @JsonProperty("coverImage") @SerialName("coverImage") val coverImage: CoverImage?, + @JsonProperty("averageScore") @SerialName("averageScore") val averageScore: Int?, ) + @Serializable data class AniListAvatar( - @JsonProperty("large") val large: String?, + @JsonProperty("large") @SerialName("large") val large: String?, ) + @Serializable data class AniListViewer( - @JsonProperty("id") val id: Int, - @JsonProperty("name") val name: String, - @JsonProperty("avatar") val avatar: AniListAvatar?, - @JsonProperty("favourites") val favourites: AniListFavourites?, + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("avatar") @SerialName("avatar") val avatar: AniListAvatar?, + @JsonProperty("favourites") @SerialName("favourites") val favourites: AniListFavourites?, ) + @Serializable data class AniListData( - @JsonProperty("Viewer") val viewer: AniListViewer?, + @JsonProperty("Viewer") @SerialName("Viewer") val viewer: AniListViewer?, ) + @Serializable data class AniListRoot( - @JsonProperty("data") val data: AniListData?, + @JsonProperty("data") @SerialName("data") val data: AniListData?, ) + @Serializable data class AniListUser( - @JsonProperty("id") val id: Int, - @JsonProperty("name") val name: String, - @JsonProperty("picture") val picture: String?, + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("picture") @SerialName("picture") val picture: String?, ) + @Serializable data class LikeNode( - @JsonProperty("id") val id: Int?, - //@JsonProperty("idMal") public int idMal; + @JsonProperty("id") @SerialName("id") val id: Int?, ) + @Serializable data class LikePageInfo( - @JsonProperty("total") val total: Int?, - @JsonProperty("currentPage") val currentPage: Int?, - @JsonProperty("lastPage") val lastPage: Int?, - @JsonProperty("perPage") val perPage: Int?, - @JsonProperty("hasNextPage") val hasNextPage: Boolean?, + @JsonProperty("total") @SerialName("total") val total: Int?, + @JsonProperty("currentPage") @SerialName("currentPage") val currentPage: Int?, + @JsonProperty("lastPage") @SerialName("lastPage") val lastPage: Int?, + @JsonProperty("perPage") @SerialName("perPage") val perPage: Int?, + @JsonProperty("hasNextPage") @SerialName("hasNextPage") val hasNextPage: Boolean?, ) + @Serializable data class LikeAnime( - @JsonProperty("nodes") val nodes: List?, - @JsonProperty("pageInfo") val pageInfo: LikePageInfo?, + @JsonProperty("nodes") @SerialName("nodes") val nodes: List?, + @JsonProperty("pageInfo") @SerialName("pageInfo") val pageInfo: LikePageInfo?, ) + @Serializable data class LikeFavourites( - @JsonProperty("anime") val anime: LikeAnime?, + @JsonProperty("anime") @SerialName("anime") val anime: LikeAnime?, ) + @Serializable data class LikeViewer( - @JsonProperty("favourites") val favourites: LikeFavourites?, + @JsonProperty("favourites") @SerialName("favourites") val favourites: LikeFavourites?, ) + @Serializable data class LikeData( - @JsonProperty("Viewer") val viewer: LikeViewer?, + @JsonProperty("Viewer") @SerialName("Viewer") val viewer: LikeViewer?, ) + @Serializable data class LikeRoot( - @JsonProperty("data") val data: LikeData?, + @JsonProperty("data") @SerialName("data") val data: LikeData?, ) + @Serializable data class AniListTitleHolder( - @JsonProperty("title") val title: Title?, - @JsonProperty("isFavourite") val isFavourite: Boolean?, - @JsonProperty("id") val id: Int?, - @JsonProperty("progress") val progress: Int?, - @JsonProperty("episodes") val episodes: Int?, - @JsonProperty("score") val score: Int?, - @JsonProperty("type") val type: AniListStatusType?, + @JsonProperty("title") @SerialName("title") val title: Title?, + @JsonProperty("isFavourite") @SerialName("isFavourite") val isFavourite: Boolean?, + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("progress") @SerialName("progress") val progress: Int?, + @JsonProperty("episodes") @SerialName("episodes") val episodes: Int?, + @JsonProperty("score") @SerialName("score") val score: Int?, + @JsonProperty("type") @SerialName("type") val type: AniListStatusType?, ) + @Serializable data class GetDataMediaListEntry( - @JsonProperty("progress") val progress: Int?, - @JsonProperty("status") val status: String?, - @JsonProperty("score") val score: Int?, + @JsonProperty("progress") @SerialName("progress") val progress: Int?, + @JsonProperty("status") @SerialName("status") val status: String?, + @JsonProperty("score") @SerialName("score") val score: Int?, ) + @Serializable data class Nodes( - @JsonProperty("id") val id: Int?, - @JsonProperty("mediaRecommendation") val mediaRecommendation: MediaRecommendation? + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("mediaRecommendation") @SerialName("mediaRecommendation") val mediaRecommendation: MediaRecommendation?, ) + @Serializable data class GetDataMedia( - @JsonProperty("isFavourite") val isFavourite: Boolean?, - @JsonProperty("episodes") val episodes: Int?, - @JsonProperty("title") val title: Title?, - @JsonProperty("mediaListEntry") val mediaListEntry: GetDataMediaListEntry? + @JsonProperty("isFavourite") @SerialName("isFavourite") val isFavourite: Boolean?, + @JsonProperty("episodes") @SerialName("episodes") val episodes: Int?, + @JsonProperty("title") @SerialName("title") val title: Title?, + @JsonProperty("mediaListEntry") @SerialName("mediaListEntry") val mediaListEntry: GetDataMediaListEntry?, ) + @Serializable data class Recommendations( - @JsonProperty("nodes") val nodes: List? + @JsonProperty("nodes") @SerialName("nodes") val nodes: List?, ) + @Serializable data class GetDataData( - @JsonProperty("Media") val media: GetDataMedia?, + @JsonProperty("Media") @SerialName("Media") val media: GetDataMedia?, ) + @Serializable data class GetDataRoot( - @JsonProperty("data") val data: GetDataData?, + @JsonProperty("data") @SerialName("data") val data: GetDataData?, ) + @Serializable data class GetSearchTitle( - @JsonProperty("romaji") val romaji: String?, + @JsonProperty("romaji") @SerialName("romaji") val romaji: String?, ) + @Serializable data class TrailerObject( - @JsonProperty("id") val id: String?, - @JsonProperty("thumbnail") val thumbnail: String?, - @JsonProperty("site") val site: String?, + @JsonProperty("id") @SerialName("id") val id: String?, + @JsonProperty("thumbnail") @SerialName("thumbnail") val thumbnail: String?, + @JsonProperty("site") @SerialName("site") val site: String?, ) + @Serializable data class GetSearchMedia( - @JsonProperty("id") val id: Int, - @JsonProperty("idMal") val idMal: Int?, - @JsonProperty("seasonYear") val seasonYear: Int, - @JsonProperty("title") val title: GetSearchTitle, - @JsonProperty("startDate") val startDate: StartedAt, - @JsonProperty("averageScore") val averageScore: Int?, - @JsonProperty("meanScore") val meanScore: Int?, - @JsonProperty("bannerImage") val bannerImage: String?, - @JsonProperty("trailer") val trailer: TrailerObject?, - @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, - @JsonProperty("recommendations") val recommendations: Recommendations?, - @JsonProperty("relations") val relations: SeasonEdges? + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("idMal") @SerialName("idMal") val idMal: Int?, + @JsonProperty("seasonYear") @SerialName("seasonYear") val seasonYear: Int, + @JsonProperty("title") @SerialName("title") val title: GetSearchTitle, + @JsonProperty("startDate") @SerialName("startDate") val startDate: StartedAt, + @JsonProperty("averageScore") @SerialName("averageScore") val averageScore: Int?, + @JsonProperty("meanScore") @SerialName("meanScore") val meanScore: Int?, + @JsonProperty("bannerImage") @SerialName("bannerImage") val bannerImage: String?, + @JsonProperty("trailer") @SerialName("trailer") val trailer: TrailerObject?, + @JsonProperty("nextAiringEpisode") @SerialName("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?, + @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: Recommendations?, + @JsonProperty("relations") @SerialName("relations") val relations: SeasonEdges?, ) + @Serializable data class GetSearchPage( - @JsonProperty("Page") val page: GetSearchData?, + @JsonProperty("Page") @SerialName("Page") val page: GetSearchData?, ) + @Serializable data class GetSearchData( - @JsonProperty("media") val media: List?, + @JsonProperty("media") @SerialName("media") val media: List?, ) + @Serializable data class GetSearchRoot( - @JsonProperty("data") val data: GetSearchPage?, + @JsonProperty("data") @SerialName("data") val data: GetSearchPage?, ) -} \ No newline at end of file +} From 5e85c80fd548eb76ba17487e9996e5bcdb3ea509 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:14:40 -0600 Subject: [PATCH 012/105] Replace some JVM specific stuff in JsHunter and JsUnpacker (#2990) --- .../kotlin/com/lagradost/cloudstream3/utils/JsHunter.kt | 4 ++-- .../kotlin/com/lagradost/cloudstream3/utils/JsUnpacker.kt | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsHunter.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsHunter.kt index 8aabeb1f1..4de7a4c87 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsHunter.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsHunter.kt @@ -23,7 +23,7 @@ class JsHunter(private val hunterJS: String) { */ fun dehunt(): String? { try { - val regex = Regex("""\}\("([^"]+)",[^,]+,\s*"([^"]+)",\s*(\d+),\s*(\d+)""", RegexOption.DOT_MATCHES_ALL) + val regex = Regex("""(?s)\}\("([^"]+)",[^,]+,\s*"([^"]+)",\s*(\d+),\s*(\d+)""") val match = regex.find(hunterJS) if (match != null && match.groupValues.size == 5) { val h = match.groupValues[1] @@ -77,4 +77,4 @@ class JsHunter(private val hunterJS: String) { } return result } -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsUnpacker.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsUnpacker.kt index 2dbbfddb0..20256e195 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsUnpacker.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsUnpacker.kt @@ -26,8 +26,7 @@ class JsUnpacker(packedJS: String?) { val js = packedJS ?: return null try { val match = Regex( - """\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", - RegexOption.DOT_MATCHES_ALL + """(?s)\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""" ).find(js) if (match != null && match.groupValues.size == 5) { val payload = match.groupValues[1].replace("\\'", "'") @@ -56,7 +55,7 @@ class JsUnpacker(packedJS: String?) { val x = unbase.unbase(word) val value = if (x in symtab.indices) symtab[x] else null if (!value.isNullOrEmpty()) { - decoded.replace( + decoded.setRange( wordMatch.range.first + replaceOffset, wordMatch.range.last + 1 + replaceOffset, value @@ -150,4 +149,4 @@ class JsUnpacker(packedJS: String?) { } } } -} \ No newline at end of file +} From b5e2a0eff41856cde86ef1e14e5ff861875eecea Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:43:39 +0000 Subject: [PATCH 013/105] Update user agent Fixes #2951 --- .../src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 086a97ef1..c34eb83f6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -89,7 +89,7 @@ annotation class SkipSerializationTest const val AllLanguagesName = "universal" const val USER_AGENT = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36" + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" class ErrorLoadingException(message: String? = null) : Exception(message) From 4b6bfa8bbd9d3922087595023773653a8e1e6740 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:48:49 -0600 Subject: [PATCH 014/105] Promote AcraApplication deprecations to error level (#2630) --- .../lagradost/cloudstream3/AcraApplication.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index bbe7d97de..b2ef28348 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -7,7 +7,7 @@ package com.lagradost.cloudstream3 @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) class AcraApplication { companion object { @@ -15,14 +15,14 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.context"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) val context get() = CloudStreamApp.context @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.removeKeys(folder)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) fun removeKeys(folder: String): Int? = CloudStreamApp.removeKeys(folder) @@ -30,7 +30,7 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(path, value)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) fun setKey(path: String, value: T) = CloudStreamApp.setKey(path, value) @@ -38,7 +38,7 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(folder, path, value)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) fun setKey(folder: String, path: String, value: T) = CloudStreamApp.setKey(folder, path, value) @@ -46,7 +46,7 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path, defVal)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) inline fun getKey(path: String, defVal: T?): T? = CloudStreamApp.getKey(path, defVal) @@ -54,7 +54,7 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) inline fun getKey(path: String): T? = CloudStreamApp.getKey(path) @@ -62,7 +62,7 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) inline fun getKey(folder: String, path: String): T? = CloudStreamApp.getKey(folder, path) @@ -70,7 +70,7 @@ class AcraApplication { @Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path, defVal)"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) inline fun getKey(folder: String, path: String, defVal: T?): T? = CloudStreamApp.getKey(folder, path, defVal) From fd078d75be02fbece4d88f43e4030894a2c9faf8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:31:18 -0600 Subject: [PATCH 015/105] [skip ci] Add expect/actual for WorkerThread (#2997) This will later be necessary for other targets as it only exists on JVM platforms. Other targets would just define it as a base annoation that doesn't do anything, allows us to keep thread safety on JVM platforms but still be able to compile for others that don't have it. --- .../com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt | 5 ++++- .../kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt | 4 +++- .../com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index e13bcf5ec..4cd8eefa6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -1,8 +1,10 @@ package com.lagradost.cloudstream3.mvvm +import androidx.annotation.AnyThread import com.lagradost.api.Log import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.utils.AppDebug +import com.lagradost.cloudstream3.utils.WorkerThread import kotlinx.coroutines.* import java.io.InterruptedIOException import java.net.SocketTimeoutException @@ -232,8 +234,9 @@ fun throwAbleToResource( } } +@AnyThread suspend fun safeApiCall( - apiCall: suspend () -> T, + @WorkerThread apiCall: suspend () -> T, ): Resource { return withContext(Dispatchers.IO) { try { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index d15ea129c..444709f88 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.utils import androidx.annotation.AnyThread import androidx.annotation.MainThread -import androidx.annotation.WorkerThread import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError @@ -10,6 +9,9 @@ import kotlinx.coroutines.* @AnyThread expect fun runOnMainThreadNative(@MainThread work: (() -> Unit)) + +internal expect annotation class WorkerThread() + object Coroutines { @AnyThread fun T.main(@MainThread work: suspend ((T) -> Unit)): Job { diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt new file mode 100644 index 000000000..2ae886f59 --- /dev/null +++ b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt @@ -0,0 +1,3 @@ +package com.lagradost.cloudstream3.utils + +internal actual typealias WorkerThread = androidx.annotation.WorkerThread From bc270d538c7a72d912836c3b2d5d5286cffcda4b Mon Sep 17 00:00:00 2001 From: Bnyro Date: Fri, 3 Jul 2026 20:41:43 +0200 Subject: [PATCH 016/105] feat(extractors): add streamcash.to extractor (#2994) --- .../cloudstream3/extractors/Streamcash.kt | 33 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 2 ++ 2 files changed, 35 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt new file mode 100644 index 000000000..2775c3062 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt @@ -0,0 +1,33 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.newExtractorLink + +@Prerelease +open class Streamcash: ExtractorApi() { + override val name: String = "Streamcash" + override val mainUrl: String = "https://streamcash.to" + open val cdnUrl: String = "https://cdn.streamcash.to" + override val requiresReferer: Boolean = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val id = url.removeSuffix("/").substringAfterLast("/") + callback.invoke( + newExtractorLink( + name = name, + source = name, + url = "$cdnUrl/videos/$id/index.m3u8", + type = ExtractorLinkType.M3U8 + ) + ) + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 7e1b9e9ed..1605138d9 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -271,6 +271,7 @@ import com.lagradost.cloudstream3.extractors.VidHidePro5 import com.lagradost.cloudstream3.extractors.VidHidePro6 import com.lagradost.cloudstream3.extractors.VidHideHub import com.lagradost.cloudstream3.extractors.Ryderjet +import com.lagradost.cloudstream3.extractors.Streamcash import com.lagradost.cloudstream3.extractors.VidMoxy import com.lagradost.cloudstream3.extractors.VidStack import com.lagradost.cloudstream3.extractors.VideoSeyred @@ -1090,6 +1091,7 @@ val extractorApis: AtomicMutableList = atomicListOf( Tantifilm(), Userload(), Supervideo(), + Streamcash(), // StreamSB.kt works // SBPlay(), From adb57c86b388e1e7203fe4a242f59f96a9036643 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Fri, 3 Jul 2026 20:43:40 +0200 Subject: [PATCH 017/105] feat(extractors): add firestream.to extractor (#2993) --- .../cloudstream3/extractors/Firestream.kt | 52 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 4 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt new file mode 100644 index 000000000..c8ef5d0ac --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt @@ -0,0 +1,52 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Prerelease +class Firestream : ExtractorApi() { + override val name: String = "Firestream" + override val mainUrl: String = "https://firestream.to" + override val requiresReferer: Boolean = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val id = url.removeSuffix("/").substringAfterLast("/") + val url = getExtractorUrl(id) + + val doc = app.get(url).document + val token = doc.selectFirst("script[id=token-blob]")!!.data() + + val videoResponse = + app.post("$mainUrl/api/videos/$id/resolve", json = mapOf("blob" to token)) + .parsed() + + callback.invoke( + newExtractorLink( + source = name, + name = name, + url = videoResponse.signedVideoUrl + ) + ) + } + + override fun getExtractorUrl(id: String): String { + return "$mainUrl/e/$id" + } + + @Serializable + private data class VideoResponse( + @SerialName("signedVideoUrl") + val signedVideoUrl: String, + ) +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 1605138d9..9524d04f6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -80,6 +80,7 @@ import com.lagradost.cloudstream3.extractors.FileMoonIn import com.lagradost.cloudstream3.extractors.FileMoonSx import com.lagradost.cloudstream3.extractors.FilemoonV2 import com.lagradost.cloudstream3.extractors.Filesim +import com.lagradost.cloudstream3.extractors.Firestream import com.lagradost.cloudstream3.extractors.Multimoviesshg import com.lagradost.cloudstream3.extractors.FlaswishCom import com.lagradost.cloudstream3.extractors.Flyfile @@ -1301,7 +1302,8 @@ val extractorApis: AtomicMutableList = atomicListOf( GUpload(), HlsWish(), ByseQekaho(), - Flyfile() + Flyfile(), + Firestream(), ) From 2cde74de277adbcc2555405bfef05a6c1773c32a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Fri, 3 Jul 2026 20:47:31 +0200 Subject: [PATCH 018/105] feat(extractors): fix vidara and update list of mirrors (#2992) --- .../extractors/{Streamup.kt => Vidara.kt} | 54 ++++++++++++++----- .../cloudstream3/utils/ExtractorApi.kt | 20 +++++-- 2 files changed, 56 insertions(+), 18 deletions(-) rename library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/{Streamup.kt => Vidara.kt} (61%) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt similarity index 61% rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt rename to library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt index ea85a005e..d74a1ccd6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt @@ -8,23 +8,45 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -class Streamix(): Streamup() { - override val name: String = "Streamix" - override val mainUrl = "https://streamix.so" +class Vidavaca : Vidara() { + override val mainUrl = "https://vidavaca.net" } -class Vidara(): Streamup() { +class VidaaraxNet : Vidara() { + override val mainUrl = "https://vidaarax.net" +} + +class VidaaraxCom : Vidara() { + override val mainUrl = "https://vidaarax.com" +} + +class Vidaratem : Vidara() { + override val mainUrl = "https://vidaratem.com" +} + +class Vidaraw : Vidara() { + override val mainUrl = "https://vidaraw.com" +} + +class Vidarax : Vidara() { + override val mainUrl = "https://vidarax.cc" +} + +class Vidaraa : Vidara() { + override val mainUrl = "https://vidaraa.cc" +} + +class VidaraSo : Vidara() { + override val mainUrl = "https://vidara.so" +} + +open class Vidara : ExtractorApi() { override val name: String = "Vidara" override val mainUrl = "https://vidara.to" - override val apiPath: String = "/api/stream" -} - -open class Streamup() : ExtractorApi() { - override val name: String = "Streamup" - override val mainUrl: String = "https://strmup.to" override val requiresReferer: Boolean = false - open val apiPath: String = "/ajax/stream" override suspend fun getUrl( url: String, @@ -33,8 +55,9 @@ open class Streamup() : ExtractorApi() { callback: (ExtractorLink) -> Unit ) { val fileCode = url.substringAfterLast("/") - val fileInfo = app.get("$mainUrl$apiPath?filecode=$fileCode") - .parsed() + val fileInfo = + app.post("$mainUrl/api/stream", json = mapOf("filecode" to fileCode, "device" to "web")) + .parsed() callback.invoke( newExtractorLink( @@ -52,18 +75,21 @@ open class Streamup() : ExtractorApi() { } } + @Serializable private data class StreamUpFileInfo( val title: String, val thumbnail: String, + @SerialName("streaming_url") @JsonProperty("streaming_url") val streamingUrl: String, val subtitles: List? ) + @Serializable private data class StreamUpSubtitle( + @SerialName("file_path") @JsonProperty("file_path") val filePath: String, - @JsonProperty("language") val language: String, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 9524d04f6..7ab533ece 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -229,12 +229,10 @@ import com.lagradost.cloudstream3.extractors.StreamWishExtractor import com.lagradost.cloudstream3.extractors.StreamhideCom import com.lagradost.cloudstream3.extractors.StreamhideTo import com.lagradost.cloudstream3.extractors.Streamhub2 -import com.lagradost.cloudstream3.extractors.Streamix import com.lagradost.cloudstream3.extractors.Streamlare import com.lagradost.cloudstream3.extractors.StreamoUpload import com.lagradost.cloudstream3.extractors.Streamplay import com.lagradost.cloudstream3.extractors.Streamsss -import com.lagradost.cloudstream3.extractors.Streamup import com.lagradost.cloudstream3.extractors.Streamwish2 import com.lagradost.cloudstream3.extractors.Strwish import com.lagradost.cloudstream3.extractors.Strwish2 @@ -289,7 +287,15 @@ import com.lagradost.cloudstream3.extractors.Vidoza import com.lagradost.cloudstream3.extractors.VinovoSi import com.lagradost.cloudstream3.extractors.VinovoTo import com.lagradost.cloudstream3.extractors.VidNest +import com.lagradost.cloudstream3.extractors.VidaaraxCom +import com.lagradost.cloudstream3.extractors.VidaaraxNet import com.lagradost.cloudstream3.extractors.Vidara +import com.lagradost.cloudstream3.extractors.VidaraSo +import com.lagradost.cloudstream3.extractors.Vidaraa +import com.lagradost.cloudstream3.extractors.Vidaratem +import com.lagradost.cloudstream3.extractors.Vidaraw +import com.lagradost.cloudstream3.extractors.Vidarax +import com.lagradost.cloudstream3.extractors.Vidavaca import com.lagradost.cloudstream3.extractors.Vide0Net import com.lagradost.cloudstream3.extractors.Vidsonic import com.lagradost.cloudstream3.extractors.VkExtractor @@ -1163,9 +1169,15 @@ val extractorApis: AtomicMutableList = atomicListOf( MoviehabNet(), Jeniusplay(), StreamoUpload(), - Streamup(), - Streamix(), Vidara(), + Vidavaca(), + Vidaraa(), + Vidaraw(), + Vidarax(), + VidaraSo(), + Vidaratem(), + VidaaraxCom(), + VidaaraxNet(), GamoVideo(), Gdriveplayerapi(), From 771a201d7609092d2340af96c1ef33bf810060e1 Mon Sep 17 00:00:00 2001 From: danzybae Date: Tue, 7 Jul 2026 01:24:48 +0700 Subject: [PATCH 019/105] Fix regex to match quality case insensitively (#3002) --- .../com/lagradost/cloudstream3/extractors/DoodExtractor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index bce017276..099406f16 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -112,7 +112,7 @@ open class DoodLaExtractor : ExtractorApi() { val response0 = req.text val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) val trueUrl = app.get(md5, referer = req.url).text + createHashTable() + "?token=" + md5.substringAfterLast("/") - val quality = Regex("\\d{3,4}p") + val quality = Regex("\\d{3,4}[pP]") .find(response0.substringAfter("").substringBefore("")) ?.groupValues ?.getOrNull(0) From 5bcac072e10c4bbe98e59e485216a6bbc99c2d43 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 6 Jul 2026 22:04:38 +0200 Subject: [PATCH 020/105] fix(extractors) fix gupload extractor (#3007) --- .../kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt index 7daaa55cc..1203b2167 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt @@ -23,16 +23,14 @@ open class GUpload: ExtractorApi() { ) { val response = app.get(url, referer = referer).text - val playerConfigEncoded = response.substringAfter("decodePayload('").substringBefore("');") - val playerConfigString = base64Decode(playerConfigEncoded).substringAfter("|") - + val playerConfigString = response.substringAfter("const config = ").substringBefore(";") val playerConfig = AppUtils.parseJson(playerConfigString) callback.invoke( newExtractorLink( source = name, name = name, - url = playerConfig.videoUrl.replace("\\", ""), + url = playerConfig.videoUrl, ) { Regex("/(\\d+p)\\.").find(playerConfig.videoUrl)?.groupValues?.get(1)?.let { quality = getQualityFromName(it) From 8828ee1718f7d2f442cf085352cdfa719504c7cb Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 6 Jul 2026 22:05:23 +0200 Subject: [PATCH 021/105] feat(extractors): add vids.st extractor (#3008) --- .../lagradost/cloudstream3/extractors/Vids.kt | 37 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 2 + 2 files changed, 39 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt new file mode 100644 index 000000000..33fcdfd3a --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt @@ -0,0 +1,37 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.newExtractorLink + +@Prerelease +open class Vids : ExtractorApi() { + override val name: String = "Vids" + override val mainUrl: String = " https://vids.st" + override val requiresReferer: Boolean = false + + private val streamUrlRegex = Regex("""const url = "(.*?)";""") + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val doc = app.get(url).text + val streamUrlMatch = + streamUrlRegex.find(doc) ?: throw RuntimeException("vids: failed to find stream link") + val streamUrl = streamUrlMatch.groupValues[1].replace("\\", "") + + callback.invoke( + newExtractorLink( + source = name, + name = name, + url = streamUrl + ) + ) + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 7ab533ece..8ef391050 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -297,6 +297,7 @@ import com.lagradost.cloudstream3.extractors.Vidaraw import com.lagradost.cloudstream3.extractors.Vidarax import com.lagradost.cloudstream3.extractors.Vidavaca import com.lagradost.cloudstream3.extractors.Vide0Net +import com.lagradost.cloudstream3.extractors.Vids import com.lagradost.cloudstream3.extractors.Vidsonic import com.lagradost.cloudstream3.extractors.VkExtractor import com.lagradost.cloudstream3.extractors.Voe @@ -1316,6 +1317,7 @@ val extractorApis: AtomicMutableList = atomicListOf( ByseQekaho(), Flyfile(), Firestream(), + Vids(), ) From bfb85ca5c96e33b1e068fa76ccc89bfac3ab6297 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:17:29 -0600 Subject: [PATCH 022/105] [skip ci] SerializationClassTester: skip abstract classes (#2960) Necessary for some such as LibrarySearchResponse and DownloadCached. Using the skip annotation causes it to also skip child classes which we don't want so just skip abstract classes since Instancio can't create an instance of an abstract class: "It is an abstract class and no subtype was provided". --- .../com/lagradost/cloudstream3/SerializationClassTester.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt index de6802635..84ef1fee0 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/SerializationClassTester.kt @@ -136,9 +136,9 @@ class SerializationClassTester { runCatching { Class.forName(it).kotlin }.getOrNull() }.filter { kClass -> // Not possible to use .hasAnnotation() on newer Android versions. - kClass.java.annotations.any { - it is Serializable - } && kClass.java.annotations.none { it is SkipSerializationTest } + kClass.java.annotations.any { it is Serializable } + && kClass.java.annotations.none { it is SkipSerializationTest } + && !kClass.isAbstract } } From e8c005dfeb8ead311cd2c8039b1e6b6ca0b7823a Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:05:07 +0000 Subject: [PATCH 023/105] [skip ci] Fix: Leading space in Vids (#3022) --- .../kotlin/com/lagradost/cloudstream3/extractors/Vids.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt index 33fcdfd3a..1312570d3 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.newExtractorLink @Prerelease open class Vids : ExtractorApi() { override val name: String = "Vids" - override val mainUrl: String = " https://vids.st" + override val mainUrl: String = "https://vids.st" override val requiresReferer: Boolean = false private val streamUrlRegex = Regex("""const url = "(.*?)";""") From 55b5017068efd32a11088d8dd6f7af9353a6854a Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:06:47 +0000 Subject: [PATCH 024/105] [skip ci] Fix: Sync episode setting, Closes #3014 (#3021) --- .../com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 09b6db50f..d9e9cbded 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1698,8 +1698,10 @@ class GeneratorPlayer : FullScreenPlayer() { if (settingsManager.getBoolean( ctx.getString(R.string.episode_sync_enabled_key), true ) - ) maxEpisodeSet = meta.episode - sync.modifyMaxEpisode(meta.totalEpisodeIndex ?: meta.episode) + ) { + maxEpisodeSet = meta.episode + sync.modifyMaxEpisode(meta.totalEpisodeIndex ?: meta.episode) + } } } From 94ce0d438548df4e916de5c869155705b1a5d7c3 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:09:34 +0000 Subject: [PATCH 025/105] Fix: Repo dismiss (#3020) --- .../cloudstream3/plugins/RepositoryManager.kt | 12 ++-- .../settings/extensions/ExtensionsFragment.kt | 70 ++++++++++--------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index 523798b6e..5868146c7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -141,13 +141,11 @@ object RepositoryManager { } } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { safeAsync { - app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 -> - it2.headers["Location"]?.let { url -> - if (url.startsWith("https://cutt.ly/404")) return@safeAsync null - if (url.removeSuffix("/") == "https://cutt.ly") return@safeAsync null - return@safeAsync url - } - } + val response = app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false) + val url = response.headers["Location"] ?: return@safeAsync null + if (url.startsWith("https://cutt.ly/404")) return@safeAsync null + if (url.removeSuffix("/") == "https://cutt.ly") return@safeAsync null + return@safeAsync url } } else null } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 9b3f381d0..ea0d5b2bd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -275,42 +275,46 @@ class ExtensionsFragment : BaseFragment( } } - binding.applyBtt.setOnClickListener secondListener@{ + binding.applyBtt.setOnClickListener secondListener@{ val name = binding.repoNameInput.text?.toString() val urlInput = binding.repoUrlInput.text?.toString() - ioSafe { - val url = urlInput?.let { it1 -> RepositoryManager.parseRepoUrl(it1) } - if (url.isNullOrBlank()) { - main { - showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) - } - } else { - val repository = RepositoryManager.parseRepository(url) - - // Exit if wrong repository - if (repository == null) { - showToast(R.string.no_repository_found_error, Toast.LENGTH_LONG) - return@ioSafe - } - - val fixedName = if (!name.isNullOrBlank()) name - else repository.name - val newRepo = RepositoryData(repository.iconUrl, fixedName, url) - RepositoryManager.addRepository(newRepo) - extensionViewModel.loadStats() - extensionViewModel.loadRepositories() - - val plugins = RepositoryManager.getRepoPlugins(newRepo) - if (plugins.isNullOrEmpty()) { - showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) - } else { - this@ExtensionsFragment.activity?.addRepositoryDialog( - newRepo - ) - } - } + if (urlInput.isNullOrEmpty()) { + showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT) + return@secondListener + } + ioSafe { + val url = RepositoryManager.parseRepoUrl(urlInput) + if (url.isNullOrBlank()) { + showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) + return@ioSafe + } + val repository = RepositoryManager.parseRepository(url) + + // Exit if wrong repository + if (repository == null) { + showToast(R.string.no_repository_found_error, Toast.LENGTH_LONG) + return@ioSafe + } + + val fixedName = if (!name.isNullOrBlank()) name + else repository.name + val newRepo = RepositoryData(repository.iconUrl, fixedName, url) + RepositoryManager.addRepository(newRepo) + extensionViewModel.loadStats() + extensionViewModel.loadRepositories() + + dialog.dismissSafe(activity) // Only dismiss if the repo was added + + val plugins = RepositoryManager.getRepoPlugins(newRepo) + if (plugins.isNullOrEmpty()) { + showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) + return@ioSafe + } + + this@ExtensionsFragment.activity?.addRepositoryDialog( + newRepo + ) } - dialog.dismissSafe(activity) } binding.cancelBtt.setOnClickListener { dialog.dismissSafe(activity) From 5bae7d35d8199eff2533001dfffb89a52f35035e Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:17:39 +0000 Subject: [PATCH 026/105] Fix: Subtitle ordering on names (#3012) --- .../com/lagradost/cloudstream3/utils/AppContextUtils.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 65519c0f1..c0be85a3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -365,8 +365,14 @@ object AppContextUtils { } } + /** Sort subtitles by names */ fun sortSubs(subs: Set): List { - return subs.sortedBy { it.name } + // Be aware, sorting by "$originalName $nameSuffix" causes "a (b) 1" < "a 1", + // where "originalName then nameSuffix" preserves "a 1" < "a (b) 1", because we do not compare '(' and '1'. + return subs + .sortedWith( + compareBy { subtitle: SubtitleData -> subtitle.originalName } + .thenBy { subtitle: SubtitleData -> subtitle.nameSuffix }) } fun Context.getApiSettings(): HashSet { From 894a18a831bdc554f527c9444d4876bbc21633a5 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:34:00 +0000 Subject: [PATCH 027/105] Feat: Loading apply button (#3025) --- .../cloudstream3/ui/account/AccountHelper.kt | 70 +++++++++------- .../cloudstream3/ui/player/GeneratorPlayer.kt | 83 +++++++++++-------- .../ui/settings/SettingsAccount.kt | 5 ++ .../settings/extensions/ExtensionsFragment.kt | 70 +++++++++------- .../lagradost/cloudstream3/utils/UIHelper.kt | 36 ++++++++ 5 files changed, 166 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt index 1d6b41e5b..7725bad91 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt @@ -37,8 +37,10 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.hideProgress import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod +import com.lagradost.cloudstream3.utils.UIHelper.showProgress object AccountHelper { fun showAccountEditDialog( @@ -164,7 +166,7 @@ object AccountHelper { canSetPin = true - binding.editProfilePhotoButton.setOnClickListener({ + binding.editProfilePhotoButton.setOnClickListener { val bottomSheetDialog = BottomSheetDialog(context) val sheetBinding = BottomInputDialogBinding.inflate(LayoutInflater.from(context)) bottomSheetDialog.setContentView(sheetBinding.root) @@ -174,42 +176,46 @@ object AccountHelper { text1.text = context.getString(R.string.edit_profile_image_title) nginxTextInput.hint = context.getString(R.string.edit_profile_image_hint) - applyBtt.setOnClickListener({ + applyBtt.setOnClickListener { val url = sheetBinding.nginxTextInput.text.toString() - if (url.isNotEmpty()) { - val imageLoader = ImageLoader(context) - val request = ImageRequest.Builder(context) - .data(url) - .allowHardware(false) - .listener( - onSuccess = { _, _ -> - currentEditAccount = currentEditAccount.copy(customImage = url) - binding.accountImage.loadImage(url) - showToast( - R.string.edit_profile_image_success, - Toast.LENGTH_SHORT - ) - bottomSheetDialog.dismiss() - }, - onError = { _, _ -> - showToast( - R.string.edit_profile_image_error_invalid, - Toast.LENGTH_SHORT - ) - } - ) - .build() - imageLoader.enqueue(request) - } else { + if (url.isEmpty()) { showToast(R.string.edit_profile_image_error_empty, Toast.LENGTH_SHORT) + return@setOnClickListener } + applyBtt.showProgress() + val imageLoader = ImageLoader(context) + val request = ImageRequest.Builder(context) + .data(url) + .allowHardware(false) + .listener( + onSuccess = { _, _ -> + currentEditAccount = currentEditAccount.copy(customImage = url) + binding.accountImage.loadImage(url) + showToast( + R.string.edit_profile_image_success, + Toast.LENGTH_SHORT + ) + bottomSheetDialog.dismissSafe() + }, + onError = { _, _ -> + showToast( + R.string.edit_profile_image_error_invalid, + Toast.LENGTH_SHORT + ) + applyBtt.hideProgress() + }, + onCancel = { + applyBtt.hideProgress() + } + ) + .build() + imageLoader.enqueue(request) + } + sheetBinding.cancelBtt.setOnClickListener { bottomSheetDialog.dismissSafe() - }) - sheetBinding.cancelBtt.setOnClickListener({ - bottomSheetDialog.dismissSafe() - }) + } } - }) + } } fun showPinInputDialog( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index d9e9cbded..b64355e23 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -117,8 +117,10 @@ import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding +import com.lagradost.cloudstream3.utils.UIHelper.hideProgress import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage +import com.lagradost.cloudstream3.utils.UIHelper.showProgress import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.downloader.DownloadUtils.getImageBitmapFromUrl import com.lagradost.cloudstream3.utils.setText @@ -790,47 +792,58 @@ class GeneratorPlayer : FullScreenPlayer() { } binding.applyBtt.setOnClickListener { - currentSubtitle?.let { currentSubtitle -> - providers.firstOrNull { it.idPrefix == currentSubtitle.idPrefix }?.let { api -> - ioSafe { - when (val apiResource = - Resource.fromResult(api.resource(currentSubtitle))) { - is Resource.Success -> { - val subtitles = apiResource.value.getSubtitles().map { resource -> - SubtitleData( - originalName = resource.name ?: getName( - currentSubtitle, - true - ), - nameSuffix = "", - url = resource.url, - origin = resource.origin, - mimeType = resource.url.toSubtitleMimeType(), - headers = currentSubtitle.headers, - languageCode = currentSubtitle.lang - ) - } - if (subtitles.isEmpty()) { - showToast(R.string.no_subtitles) - return@ioSafe - } - runOnMainThread { - addAndSelectSubtitles(*subtitles.toTypedArray()) - } - } + val currentSubtitle = currentSubtitle + if (currentSubtitle == null) { + dialog.dismissSafe() + return@setOnClickListener + } - is Resource.Failure -> { - showToast(apiResource.errorString) - } + val api = providers.firstOrNull { it.idPrefix == currentSubtitle.idPrefix } + if (api == null) { + dialog.dismissSafe() + return@setOnClickListener + } - is Resource.Loading -> { - // not possible - } + binding.applyBtt.showProgress() + ioSafe { + val apiResource = + Resource.fromResult(api.resource(currentSubtitle)) + binding.applyBtt.hideProgress() + when (apiResource) { + is Resource.Success -> { + val subtitles = apiResource.value.getSubtitles().map { resource -> + SubtitleData( + originalName = resource.name ?: getName( + currentSubtitle, + true + ), + nameSuffix = "", + url = resource.url, + origin = resource.origin, + mimeType = resource.url.toSubtitleMimeType(), + headers = currentSubtitle.headers, + languageCode = currentSubtitle.lang + ) } + if (subtitles.isEmpty()) { + showToast(R.string.no_subtitles) + return@ioSafe + } + dialog.dismissSafe() + runOnMainThread { + addAndSelectSubtitles(*subtitles.toTypedArray()) + } + } + + is Resource.Failure -> { + showToast(apiResource.errorString) + } + + is Resource.Loading -> { + // not possible } } } - dialog.dismissSafe() } dialog.setOnDismissListener { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 8d96a6b14..75ba55674 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -65,6 +65,8 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogTe import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard +import com.lagradost.cloudstream3.utils.UIHelper.hideProgress +import com.lagradost.cloudstream3.utils.UIHelper.showProgress import com.lagradost.cloudstream3.utils.setText import com.lagradost.cloudstream3.utils.txt import qrcode.QRCode @@ -348,6 +350,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { email = if (req.email) binding.loginEmailInput.text?.toString() else null, server = if (req.server) binding.loginServerInput.text?.toString() else null, ) + binding.applyBtt.showProgress() ioSafe { try { if (api.login(loginData)) { @@ -377,6 +380,8 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { api.name ) ) + } finally { + binding.applyBtt.hideProgress() } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index ea0d5b2bd..107227497 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -39,6 +39,8 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.hideProgress +import com.lagradost.cloudstream3.utils.UIHelper.showProgress import com.lagradost.cloudstream3.utils.setText class ExtensionsFragment : BaseFragment( @@ -275,45 +277,50 @@ class ExtensionsFragment : BaseFragment( } } - binding.applyBtt.setOnClickListener secondListener@{ + binding.applyBtt.setOnClickListener secondListener@{ val name = binding.repoNameInput.text?.toString() val urlInput = binding.repoUrlInput.text?.toString() if (urlInput.isNullOrEmpty()) { showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT) return@secondListener } + binding.applyBtt.showProgress() ioSafe { - val url = RepositoryManager.parseRepoUrl(urlInput) - if (url.isNullOrBlank()) { - showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) - return@ioSafe + try { + val url = RepositoryManager.parseRepoUrl(urlInput) + if (url.isNullOrBlank()) { + showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) + return@ioSafe + } + val repository = RepositoryManager.parseRepository(url) + + // Exit if wrong repository + if (repository == null) { + showToast(R.string.no_repository_found_error, Toast.LENGTH_LONG) + return@ioSafe + } + + val fixedName = if (!name.isNullOrBlank()) name + else repository.name + val newRepo = RepositoryData(repository.iconUrl, fixedName, url) + RepositoryManager.addRepository(newRepo) + extensionViewModel.loadStats() + extensionViewModel.loadRepositories() + + dialog.dismissSafe(activity) // Only dismiss if the repo was added + + val plugins = RepositoryManager.getRepoPlugins(newRepo) + if (plugins.isNullOrEmpty()) { + showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) + return@ioSafe + } + + this@ExtensionsFragment.activity?.addRepositoryDialog( + newRepo + ) + } finally { + binding.applyBtt.hideProgress() } - val repository = RepositoryManager.parseRepository(url) - - // Exit if wrong repository - if (repository == null) { - showToast(R.string.no_repository_found_error, Toast.LENGTH_LONG) - return@ioSafe - } - - val fixedName = if (!name.isNullOrBlank()) name - else repository.name - val newRepo = RepositoryData(repository.iconUrl, fixedName, url) - RepositoryManager.addRepository(newRepo) - extensionViewModel.loadStats() - extensionViewModel.loadRepositories() - - dialog.dismissSafe(activity) // Only dismiss if the repo was added - - val plugins = RepositoryManager.getRepoPlugins(newRepo) - if (plugins.isNullOrEmpty()) { - showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) - return@ioSafe - } - - this@ExtensionsFragment.activity?.addRepositoryDialog( - newRepo - ) } } binding.cancelBtt.setOnClickListener { @@ -321,6 +328,7 @@ class ExtensionsFragment : BaseFragment( } } + val isTv = isLayout(TV) binding.apply { addRepoButton.isGone = isTv diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index c12674816..69f22e8ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -65,9 +65,12 @@ import androidx.navigation.fragment.NavHostFragment import androidx.palette.graphics.Palette import androidx.preference.PreferenceManager import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.button.MaterialButton import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable import com.google.android.material.chip.ChipGroup +import com.google.android.material.progressindicator.CircularProgressIndicatorSpec +import com.google.android.material.progressindicator.IndeterminateDrawable import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast @@ -583,6 +586,39 @@ object UIHelper { } } + /** + * Source: https://stackoverflow.com/questions/70954321/circular-progress-indicator-inside-buttons-android-material-design + * + * Shows indeterminate progress bar on this button in place of where icon would be. + * By default the tint of progress bar is the same as iconTint. + * + * @param tintColor (@ColorInt Int) Sets custom progress bar tint color. + */ + fun MaterialButton.showProgress(@ColorInt tintColor: Int = this.iconTint.defaultColor) = + // Use runOnMainThreadNative to allow process on io threads, to make the code a bit cleaner + runOnMainThreadNative { + val spec = CircularProgressIndicatorSpec( + context, null, 0, + com.google.android.material.R.style.Widget_Material3_CircularProgressIndicator_ExtraSmall + ) + + spec.indicatorColors = intArrayOf(tintColor) + + val progressIndicatorDrawable = + IndeterminateDrawable.createCircularDrawable(context, spec) + + this.icon = progressIndicatorDrawable + if (this.getTag(R.id.text1) == null) + this.setTag(R.id.text1, this.text) + this.text = "" + } + + fun MaterialButton.hideProgress() = + runOnMainThreadNative { + this.text = this.getTag(R.id.text1) as? String + this.icon = null + } + /**id, stringRes */ @SuppressLint("RestrictedApi") fun View.popupMenuNoIcons( From 678726e9eeebb868edaa3f6ac469362deaff8216 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 8 Jul 2026 08:17:17 -0600 Subject: [PATCH 028/105] GogoHelper: use more clear name for private method (#3028) --- .../lagradost/cloudstream3/extractors/helper/GogoHelper.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt index 7bbe403f8..477c965ce 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt @@ -28,7 +28,7 @@ object GogoHelper { * @param id base64Decode(show_id) + IV * @return the encryption key */ - private fun getKey(id: String): String? { + private fun getEncryptionKey(id: String): String? { return safe { id.map { it.code.toString(16) @@ -64,7 +64,7 @@ object GogoHelper { * @param iv secret iv from site, required non-null if isUsingAdaptiveKeys is off * @param secretKey secret key for decryption from site, required non-null if isUsingAdaptiveKeys is off * @param secretDecryptKey secret key to decrypt the response json, required non-null if isUsingAdaptiveKeys is off - * @param isUsingAdaptiveKeys generates keys from IV and ID, see getKey() + * @param isUsingAdaptiveKeys generates keys from IV and ID, see [getEncryptionKey] * @param isUsingAdaptiveData generate encrypt-ajax data based on $("script[data-name='episode']")[0].dataset.value */ suspend fun extractVidstream( @@ -89,7 +89,7 @@ object GogoHelper { val foundIv = iv ?: (document ?: app.get(iframeUrl).document.also { document = it }) .select("""div.wrapper[class*=container]""") .attr("class").split("-").lastOrNull() ?: return@safeApiCall - val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall + val foundKey = secretKey ?: getEncryptionKey(base64Decode(id) + foundIv) ?: return@safeApiCall val foundDecryptKey = secretDecryptKey ?: foundKey val url = Url(iframeUrl) From 60cfeba7351f27ea52b64aaa983178a3dee9a2dc Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:13:50 +0000 Subject: [PATCH 029/105] Fix: Sync delay, Progress on sync button and runOnMainThreadNative fix (#3032) --- .../java/com/lagradost/cloudstream3/MainActivity.kt | 6 ++++-- .../lagradost/cloudstream3/ui/result/SyncViewModel.kt | 1 + .../java/com/lagradost/cloudstream3/utils/UIHelper.kt | 4 ++++ .../lagradost/cloudstream3/utils/Coroutines.android.kt | 10 ++++++++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 0cbed1809..7db33284e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -171,6 +171,7 @@ 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.UIHelper.setNavigationBarColorCompat +import com.lagradost.cloudstream3.utils.UIHelper.showProgress import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API @@ -1428,8 +1429,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa else -> { resultviewPreviewBookmark.isEnabled = false - resultviewPreviewBookmark.setIconResource(R.drawable.ic_baseline_bookmark_border_24) - resultviewPreviewBookmark.setText(R.string.loading) + resultviewPreviewBookmark.showProgress() + //resultviewPreviewBookmark.setIconResource(R.drawable.ic_baseline_bookmark_border_24) + //resultviewPreviewBookmark.setText(R.string.loading) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt index 6c5c64ff8..3161a1204 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt @@ -182,6 +182,7 @@ class SyncViewModel : ViewModel() { fun publishUserData() = ioSafe { Log.i(TAG, "publishUserData") val user = userData.value + _userDataResponse.postValue(Resource.Loading()) if (user is Resource.Success) { syncs.forEach { (prefix, id) -> repos.firstOrNull { it.idPrefix == prefix }?.updateStatus(id, user.value) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 69f22e8ef..a848bee26 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -597,6 +597,10 @@ object UIHelper { fun MaterialButton.showProgress(@ColorInt tintColor: Int = this.iconTint.defaultColor) = // Use runOnMainThreadNative to allow process on io threads, to make the code a bit cleaner runOnMainThreadNative { + // No need to set it again, as then it will reset the animation + if(this.icon is IndeterminateDrawable<*>) { + return@runOnMainThreadNative + } val spec = CircularProgressIndicatorSpec( context, null, 0, com.google.android.material.R.style.Widget_Material3_CircularProgressIndicator_ExtraSmall diff --git a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt index 048e7fc02..22bd3b20b 100644 --- a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt +++ b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt @@ -1,14 +1,20 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.os.Handler import android.os.Looper import androidx.annotation.AnyThread import androidx.annotation.MainThread +@SuppressLint("ThreadConstraint") // mainLooper.isCurrentThread does not switch the context @AnyThread actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { - val mainHandler = Handler(Looper.getMainLooper()) - mainHandler.post { + val mainLooper = Looper.getMainLooper() + if (mainLooper.isCurrentThread) { + // Do the work directly if we already are on the main thread, no need to enqueue it work() + } else { + // Otherwise post it to the other main thread + Handler(mainLooper).post(work) } } From 5cb939df69c6f6bda9aeb660e277c25ff3279119 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:29:06 +0000 Subject: [PATCH 030/105] Fix: Corrupted HSL files due to 404 (#3013) --- .../kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt index aa4515b30..d34f5f500 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt @@ -234,6 +234,10 @@ object M3u8Helper2 { body.close() if (tsData.isEmpty()) throw ErrorLoadingException("no data") + // Some sources respond with "error 404" or similar, this checks for small responses that + // looks like ASCII + if (tsData.size < 128 && tsData.all { it >= 0 }) throw ErrorLoadingException("ASCII found instead of data") + return if (isEncrypted) { getDecrypted(encryptionData, tsData, encryptionIv, index) } else { From a123c5935c867dbcbbcfa6a0eed07f5e862029c1 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:30:23 +0000 Subject: [PATCH 031/105] Fix slow startup in downloader --- .../java/com/lagradost/cloudstream3/plugins/PluginManager.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index de41cbc74..829d871d2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -512,6 +512,9 @@ object PluginManager { val res = dir.mkdirs() if (!res) { Log.w(TAG, "Failed to create local directories") + // We have tried to load local plugins, but exit early. + // This needs to be true to prevent the downloader waiting for plugins. + loadedLocalPlugins = true return } } From 480d48bea48f97696a15246fbd91b335c7a66fb3 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 8 Jul 2026 23:26:42 +0000 Subject: [PATCH 032/105] Fix: Switch focus on skip chapter (#3024) --- .../com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 5f5110552..20c37dcf4 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 @@ -1202,6 +1202,10 @@ open class FullScreenPlayer : AbstractPlayerFragment( } skipChapterButton.setOnClickListener { + // Switch focus for a better UX, as otherwise it is reset to a random button like "back button" + if(skipChapterButton.hasFocus()) { + playerPausePlay.requestFocus() + } player.handleEvent(CSPlayerEvent.SkipCurrentChapter) } From 694c9c44134d9116caffb10e443f15ae4e732e00 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:41:54 -0600 Subject: [PATCH 033/105] Add JsInterpreter to replace Rhino usage (#2812) --- gradle/libs.versions.toml | 5 +- library/build.gradle.kts | 1 + .../com/lagradost/cloudstream3/MainAPI.kt | 5 + .../cloudstream3/extractors/StreamTape.kt | 24 +- .../cloudstream3/extractors/Userload.kt | 30 +- .../cloudstream3/utils/JsInterpreter.kt | 1631 +++++++++++ .../cloudstream3/utils/JsInterpreterTest.kt | 2410 +++++++++++++++++ 7 files changed, 4063 insertions(+), 43 deletions(-) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt create mode 100644 library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0c8eef94..8935ab52a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ juniversalchardet = "2.5.0" kotlinGradlePlugin = "2.3.20" kotlinxAtomicfu = "0.33.0" kotlinxCollectionsImmutable = "0.4.0" -kotlinxCoroutinesCore = "1.11.0" +kotlinxCoroutines = "1.11.0" kotlinxDatetime = "0.8.0" kotlinxSerializationJson = "1.11.0" ksoup = "0.2.6" @@ -96,7 +96,8 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinGradlePlugin" } kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinxAtomicfu" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } -kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 23351f7d0..9b769d1e5 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -76,6 +76,7 @@ kotlin { commonTest.dependencies { implementation(libs.kotlin.test) + implementation(libs.kotlinx.coroutines.test) } val jvmCommonMain by creating { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index c34eb83f6..72d69dc45 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -804,6 +804,11 @@ fun fixTitle(str: String): String { * * Use like the following: rhino.evaluateString(scope, js, "JavaScript", 1, null) **/ +// Deprecate after next stable +/* @Deprecated( + message = "Use JsContext or evalJs instead.", + level = DeprecationLevel.WARNING, +) */ suspend fun getRhinoContext(): org.mozilla.javascript.Context { return Coroutines.mainWork { val rhino = org.mozilla.javascript.Context.enter() diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamTape.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamTape.kt index 211b5ecf9..979674863 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamTape.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamTape.kt @@ -4,8 +4,8 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.evalJs import com.lagradost.cloudstream3.utils.newExtractorLink -import org.mozilla.javascript.Context class Watchadsontape : StreamTape() { override var mainUrl = "https://watchadsontape.com" @@ -30,24 +30,14 @@ open class StreamTape : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { - var result = + val result = this.document.select("script").firstOrNull { it.html().contains("botlink').innerHTML") } - ?.html()?.lines()?.firstOrNull{ it.contains("botlink').innerHTML") }?.let { - val scriptContent = - it.substringAfter(").innerHTML").replaceFirst("=", "var url =") - val rhino = Context.enter() - rhino.setInterpretedMode(true) - val scope = rhino.initStandardObjects() - var result = "" - try { - rhino.evaluateString(scope, scriptContent, "url", 1, null) - result = scope.get("url", scope).toString() - }finally { - rhino.close() - } - result + ?.html()?.lines()?.firstOrNull { it.contains("botlink').innerHTML") }?.let { + val scriptContent = it.substringAfter(").innerHTML").replaceFirst("=", "var url =") + evalJs(scriptContent, "url")?.toString() } - if(!result.isNullOrEmpty()){ + + if (!result.isNullOrEmpty()) { val extractedUrl = "https:${result}&stream=1" return listOf( newExtractorLink( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Userload.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Userload.kt index 08dcb634e..ed2c20f31 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Userload.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Userload.kt @@ -3,9 +3,6 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* -import org.mozilla.javascript.Context -import org.mozilla.javascript.EvaluatorException -import org.mozilla.javascript.Scriptable open class Userload : ExtractorApi() { override var name = "Userload" @@ -32,20 +29,11 @@ open class Userload : ExtractorApi() { return array } - private fun evaluateMath(mathExpression : String): String { - val rhino = Context.enter() - rhino.initStandardObjects() - rhino.setInterpretedMode(true) - val scope: Scriptable = rhino.initStandardObjects() - return try { - rhino.evaluateString(scope, "eval($mathExpression)", "JavaScript", 1, null).toString() - } - catch (e: EvaluatorException){ - "" - } + private suspend fun evaluateMath(mathExpression: String): String { + return jsValueToString(evalJs("eval($mathExpression)")) } - private fun decodeVideoJs(text: String): List { + private suspend fun decodeVideoJs(text: String): List { text.replace("""\s+|/\*.*?\*/""".toRegex(), "") val data = text.split("""+(゚Д゚)[゚o゚]""")[1] val chars = data.split("""+ (゚Д゚)[゚ε゚]+""").drop(1) @@ -68,22 +56,16 @@ open class Userload : ExtractorApi() { subchar.add(splitInput(v).map { evaluateMath(it).substringBefore(".") }.toString().filter { it.isDigit() }) } var txtresult = "" - subchar.forEach{ + subchar.forEach { txtresult = txtresult.plus(it.toInt(8).toChar()) } val val1 = Regex(""""morocco="((.|\\n)*?)"&mycountry="""").find(txtresult)?.groups?.get(1)?.value.toString().drop(1).dropLast(1) val val2 = txtresult.substringAfter("""&mycountry="+""").substringBefore(")") - return listOf( - val1, - val2 - ) - - + return listOf(val1, val2) } override suspend fun getUrl(url: String, referer: String?): List? { - val extractedLinksList: MutableList = mutableListOf() val response = app.get(url).text @@ -113,4 +95,4 @@ open class Userload : ExtractorApi() { return extractedLinksList } -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt new file mode 100644 index 000000000..39d950ed1 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt @@ -0,0 +1,1631 @@ +package com.lagradost.cloudstream3.utils + +import androidx.annotation.VisibleForTesting +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl +import com.lagradost.cloudstream3.utils.StringUtils.encodeUrl +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.isActive +import kotlin.math.E +import kotlin.math.PI +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.cos +import kotlin.math.floor +import kotlin.math.ln +import kotlin.math.log10 +import kotlin.math.log2 +import kotlin.math.pow +import kotlin.math.round +import kotlin.math.sin +import kotlin.math.sqrt +import kotlin.math.tan +import kotlin.math.truncate +import kotlin.random.Random +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.TimeMark +import kotlin.time.TimeSource + +/** + * Lightweight pure-Kotlin JavaScript interpreter designed to replace Rhino for + * our own deobfuscation use-cases. + * + * Supports the subset of JS that appears in obfuscated video-hosting scripts: + * - Variable declarations (var / let / const) + * - String, number (including hex `0x..` and legacy octal `0..`) and boolean literals + * - Arithmetic / bitwise / comparison / logical operators + * - String concatenation with + + * - String methods: split, join, reverse, replace, charAt, charCodeAt, + * fromCharCode, substr, substring, slice, indexOf, + * trim, toLowerCase, toUpperCase, toString, length + * - Array literals and methods: join, reverse, split, push, pop, + * map, filter, forEach, length + * - parseInt / parseFloat / isNaN / isFinite + * - Math.* (sin, cos, floor, ceil, round, abs, pow, sqrt, log, max, min, random) + * - String.fromCharCode + * - Ternary operator (a ? b : c) + * - if / else / while / for statements + * - Function declarations and calls (named and anonymous) + * - return / break / continue + * - Template literals (back-tick strings) + * - instanceof + * - typeof + * + * Every evaluation is bounded by an execution budget (wall-clock time and/or instruction + * count, see [JS_DEFAULT_MAX_EXECUTION_TIME] / [JS_DEFAULT_MAX_INSTRUCTIONS]) so that + * untrusted/obfuscated scripts containing an infinite loop (such as `while(true){}`) + * cannot hang the calling thread forever. Note that since evaluation is synchronous, wrapping a call + * in `withTimeout` will not pre-empt it mid-flight (there's no suspension point for the + * coroutine machinery to act on) so the budget below is what actually guarantees the call + * returns. + * + * Usage: + * val result = evalJs("var x = 1+2; x") + * // result == 3.0 (numbers are always Double internally) + * + * // Drop-in replacement for the old Rhino-based evaluateString pattern: + * val ctx = JsContext() + * ctx.eval("var url = 'https:' + computeSuffix()") + * val url = ctx["url"] // retrieves the variable as a String + * + * // Or simpler: + * val url = evalJs("var url = 'https:' + computeSuffix()", "url") + */ + +/** Default wall-clock budget for a single [evalJs] / [JsContext.eval] call before it's aborted. */ +private val JS_DEFAULT_MAX_EXECUTION_TIME: Duration = 5.seconds + +/** Hard backstop on statements/expressions executed, independent of wall-clock time. */ +private const val JS_DEFAULT_MAX_INSTRUCTIONS: Long = 50_000_000L + +/** + * Convert any JS runtime value to its JavaScript string representation. + * Mirrors what JS `String(value)` would produce. + */ +@Prerelease +fun jsValueToString(v: Any?): String = toJsString(v) + +/** + * Stateful JS execution context. Keeps variables alive between [eval] calls, + * mimicking the Rhino "scope" object that extensions used to hold on to. + * + * Instances are created via [newJsContext], not by calling this constructor directly. + * + * @param maxExecutionTime wall-clock budget given to each [eval] call. Can still be + * changed after construction (e.g. from within [newJsContext]'s initializer + * block) as long as it's set before the first [eval]/[get]/[set] call, since the + * underlying interpreter is built lazily from whatever values these hold at that + * point. Changes made afterward have no effect. + * @param maxInstructions hard cap on statements/expressions executed per [eval] call, + * independent of wall-clock time. Same lazy-initialization caveat as + * [maxExecutionTime] applies. + * @param scope the [CoroutineScope] this context's cancellation is tied to. Supplied + * automatically by [newJsContext] from the caller's own coroutine context. + */ +@Prerelease +class JsContext internal constructor( + var maxExecutionTime: Duration = JS_DEFAULT_MAX_EXECUTION_TIME, + var maxInstructions: Long = JS_DEFAULT_MAX_INSTRUCTIONS, + private val scope: CoroutineScope, +) { + /** + * Built lazily so that any changes made to [maxExecutionTime]/[maxInstructions] inside + * [newJsContext]'s initializer block (before the first [eval]/[get]/[set] call) are + * still in effect when JsInterpreter is actually constructed. Once this has been + * accessed once, further changes to the two vars above have no effect. + */ + private val interpreter: JsInterpreter by lazy { + JsInterpreter(maxExecutionTime, maxInstructions, scope) + } + + /** + * Evaluate [code] in this context. Returns the last expression value. + * + * @throws CancellationException if this context's underlying coroutine scope + * has been cancelled, e.g. by an enclosing `withTimeout`. + */ + @Throws(CancellationException::class) + suspend fun eval(code: String): Any? = interpreter.eval(code) + + /** + * Retrieve a variable set by previously evaluated code, or via [set]. + * Returns `null` if never set. + */ + operator fun get(name: String): Any? = interpreter.getVar(name) + + /** Expose a Kotlin value to subsequently evaluated JS code under the name [name]. */ + operator fun set(name: String, value: Any?) = interpreter.setVar(name, value) +} + +/** + * Creates a new [JsContext], running [initializer] on it before returning. + * + * The context's cancellation is automatically tied to whichever coroutine calls this + * function. If that coroutine is later cancelled (e.g. an enclosing `withTimeout` + * expires), any in-flight or subsequent [JsContext.eval] call on the returned + * context will throw [CancellationException]. + * + * [JsContext.maxExecutionTime] / [JsContext.maxInstructions] can be changed from + * within [initializer] (via `this.maxExecutionTime = ...`) and will take effect, since + * the underlying interpreter isn't built until the context is first used. + * + * Usage: + * val ctx = newJsContext { + * maxInstructions = 10_000 + * set("x", 1.0) + * eval("x + 1") + * } + */ +@Prerelease +suspend fun newJsContext( + initializer: suspend JsContext.() -> Unit = {}, +): JsContext { + val scope = CoroutineScope(currentCoroutineContext()) + return JsContext(scope = scope).apply { initializer() } +} + +/** + * Evaluate [js] and return its last value, or the value of [variable] if specified. + * Convenience wrapper for one-shot evaluations, equivalent to the old + * `rhino.evaluateString(scope, js, ...)`. + * + * Cancellation is automatically tied to whichever coroutine calls this function. If + * that coroutine is cancelled (e.g. an enclosing `withTimeout` expires), this call + * throws [CancellationException]. + * + * @param js The JavaScript code to evaluate. + * @param variable Optional variable name to retrieve from the scope after evaluation. + * @param maxExecutionTime wall-clock budget before the script is forcibly aborted. + * Defaults to [JS_DEFAULT_MAX_EXECUTION_TIME]; pass a smaller value for time-sensitive + * call sites (e.g. inside a `withTimeout`, which cannot itself interrupt this call). + * @param maxInstructions hard cap on statements/expressions executed, independent of + * wall-clock time. Defaults to [JS_DEFAULT_MAX_INSTRUCTIONS]. + * @return The last expression value, or the named variable value if [variable] is specified. + * Returns [Unit] on evaluation failure, timeout, or when the result is JS undefined. + * JS null is represented as Kotlin null. Use [jsValueToString] to convert to a JS string. + */ +@Prerelease +@Throws(CancellationException::class) +suspend fun evalJs( + js: String, + variable: String? = null, + maxExecutionTime: Duration = JS_DEFAULT_MAX_EXECUTION_TIME, + maxInstructions: Long = JS_DEFAULT_MAX_INSTRUCTIONS, +): Any? { + val scope = CoroutineScope(currentCoroutineContext()) + return evalJsInternal(js, variable, maxExecutionTime, maxInstructions, scope) +} + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal fun evalJsInternal( + js: String, + variable: String? = null, + maxExecutionTime: Duration = JS_DEFAULT_MAX_EXECUTION_TIME, + maxInstructions: Long = JS_DEFAULT_MAX_INSTRUCTIONS, + scope: CoroutineScope? = null, +): Any? { + val interpreter = JsInterpreter(maxExecutionTime, maxInstructions, scope) + val result = interpreter.eval(js) + return if (variable != null) interpreter.getVar(variable) else result +} + +private enum class TT { + NUMBER, STRING, IDENT, PLUS, MINUS, STAR, SLASH, PERCENT, POW, EQ, EQEQ, EQEQEQ, + NEQ, NEQEQ, LT, LTEQ, GT, GTEQ, AND, OR, NOT, AMP, PIPE, CARET, TILDE, + LSHIFT, RSHIFT, URSHIFT, PLUSEQ, MINUSEQ, STAREQ, SLASHEQ, PERCENTEQ, POWEQ, + PLUSPLUS, MINUSMINUS, DOT, COMMA, SEMI, COLON, QUESTION, LPAREN, RPAREN, + LBRACE, RBRACE, LBRACKET, RBRACKET, EOF +} + +private data class Token(val type: TT, val raw: String, val pos: Int) + +private class Lexer(private val src: String) { + var pos = 0 + private val tokens = mutableListOf() + private var idx = 0 + + init { tokenize() } + + private fun tokenize() { + while (pos < src.length) { + skipWhitespaceAndComments() + if (pos >= src.length) break + val c = src[pos] + when { + c.isDigit() || (c == '.' && pos + 1 < src.length && src[pos + 1].isDigit()) -> readNumber() + c == '"' || c == '\'' || c == '`' -> readString(c) + c.isLetter() || c == '_' || c == '$' -> readIdent() + else -> readOp() + } + } + tokens.add(Token(TT.EOF, "", pos)) + } + + private fun skipWhitespaceAndComments() { + while (pos < src.length) { + val c = src[pos] + if (c.isWhitespace()) { pos++; continue } + if (c == '/' && pos + 1 < src.length) { + when (src[pos + 1]) { + '/' -> { pos = src.indexOf('\n', pos).let { if (it < 0) src.length else it + 1 }; continue } + '*' -> { + val end = src.indexOf("*/", pos + 2) + pos = if (end < 0) src.length else end + 2; continue + } + else -> {} + } + } + break + } + } + + private fun readNumber() { + val start = pos + if (pos + 1 < src.length && src[pos] == '0' && + (src[pos + 1] == 'x' || src[pos + 1] == 'X') + ) { + pos += 2 + while (pos < src.length && src[pos].isLetterOrDigit()) pos++ + } else { + while (pos < src.length && (src[pos].isDigit() || src[pos] == '.')) pos++ + if (pos < src.length && (src[pos] == 'e' || src[pos] == 'E')) { + pos++ + if (pos < src.length && (src[pos] == '+' || src[pos] == '-')) pos++ + while (pos < src.length && src[pos].isDigit()) pos++ + } + } + tokens.add(Token(TT.NUMBER, src.substring(start, pos), start)) + } + + private fun readString(quote: Char) { + val start = pos++ + val sb = StringBuilder() + if (quote == '`') { + // template literal. We flatten it to a plain string (no interpolation yet) + while (pos < src.length && src[pos] != '`') { + if (src[pos] == '\\' && pos + 1 < src.length) { + sb.append(unescape(src[++pos])); pos++ + } else sb.append(src[pos++]) + } + if (pos < src.length) pos++ // consume closing ` + } else { + while (pos < src.length && src[pos] != quote) { + if (src[pos] == '\\' && pos + 1 < src.length) { + sb.append(unescape(src[++pos])); pos++ + } else sb.append(src[pos++]) + } + if (pos < src.length) pos++ + } + tokens.add(Token(TT.STRING, sb.toString(), start)) + } + + private fun unescape(c: Char): Char = when (c) { + 'n' -> '\n'; 'r' -> '\r'; 't' -> '\t'; 'b' -> '\b' + '\'' -> '\''; '"' -> '"'; '\\' -> '\\'; '`' -> '`' + else -> c + } + + private fun readIdent() { + val start = pos + while (pos < src.length && (src[pos].isLetterOrDigit() || src[pos] == '_' || src[pos] == '$')) pos++ + tokens.add(Token(TT.IDENT, src.substring(start, pos), start)) + } + + private fun readOp() { + val start = pos + val c = src[pos++] + val next = if (pos < src.length) src[pos] else '\u0000' + val nn = if (pos + 1 < src.length) src[pos + 1] else '\u0000' + fun adv(t: TT, n: Int = 0): Token { pos += n; return Token(t, "", start) } + val tok = when (c) { + '+' -> when (next) { '+' -> adv(TT.PLUSPLUS, 1); '=' -> adv(TT.PLUSEQ, 1); else -> Token(TT.PLUS, "+", start) } + '-' -> when (next) { '-' -> adv(TT.MINUSMINUS, 1); '=' -> adv(TT.MINUSEQ, 1); else -> Token(TT.MINUS, "-", start) } + '*' -> when { + next == '*' && nn == '=' -> adv(TT.POWEQ, 2) + next == '*' -> adv(TT.POW, 1) + next == '=' -> adv(TT.STAREQ, 1) + else -> Token(TT.STAR, "*", start) + } + '/' -> if (next == '=') adv(TT.SLASHEQ, 1) else Token(TT.SLASH, "/", start) + '%' -> if (next == '=') adv(TT.PERCENTEQ, 1) else Token(TT.PERCENT, "%", start) + '=' -> when { next == '=' && nn == '=' -> adv(TT.EQEQEQ, 2); next == '=' -> adv(TT.EQEQ, 1); else -> Token(TT.EQ, "=", start) } + '!' -> when { next == '=' && nn == '=' -> adv(TT.NEQEQ, 2); next == '=' -> adv(TT.NEQ, 1); else -> Token(TT.NOT, "!", start) } + '<' -> when { next == '<' -> adv(TT.LSHIFT, 1); next == '=' -> adv(TT.LTEQ, 1); else -> Token(TT.LT, "<", start) } + '>' -> when { next == '>' && nn == '>' -> adv(TT.URSHIFT, 2); next == '>' -> adv(TT.RSHIFT, 1); next == '=' -> adv(TT.GTEQ, 1); else -> Token(TT.GT, ">", start) } + '&' -> if (next == '&') adv(TT.AND, 1) else Token(TT.AMP, "&", start) + '|' -> if (next == '|') adv(TT.OR, 1) else Token(TT.PIPE, "|", start) + '^' -> Token(TT.CARET, "^", start) + '~' -> Token(TT.TILDE, "~", start) + '.' -> Token(TT.DOT, ".", start) + ',' -> Token(TT.COMMA, ",", start) + ';' -> Token(TT.SEMI, ";", start) + ':' -> Token(TT.COLON, ":", start) + '?' -> Token(TT.QUESTION, "?", start) + '(' -> Token(TT.LPAREN, "(", start) + ')' -> Token(TT.RPAREN, ")", start) + '{' -> Token(TT.LBRACE, "{", start) + '}' -> Token(TT.RBRACE, "}", start) + '[' -> Token(TT.LBRACKET, "[", start) + ']' -> Token(TT.RBRACKET, "]", start) + else -> Token(TT.SEMI, "", start) // swallow unknown + } + tokens.add(tok) + } + + fun peek(offset: Int = 0): Token = tokens.getOrElse(idx + offset) { tokens.last() } + fun consume(): Token = tokens[idx++] + fun expect(t: TT): Token { + val tok = consume() + if (tok.type != t) throw RuntimeException("Expected $t got ${tok.type} ('${tok.raw}') at ${tok.pos}") + return tok + } + fun matchIf(t: TT): Boolean = if (peek().type == t) { consume(); true } else false +} + +private sealed class Node +private data class NumLit(val v: Double) : Node() +private data class StrLit(val v: String) : Node() +private data class BoolLit(val v: Boolean) : Node() +private object NullLit : Node() +private object UndefinedLit : Node() +private data class Ident(val name: String) : Node() +private data class ArrayLit(val elems: List) : Node() +private data class ObjLit(val pairs: List>) : Node() +private data class MemberExpr(val obj: Node, val prop: Node, val computed: Boolean) : Node() +private data class CallExpr(val callee: Node, val args: List) : Node() +private data class NewExpr(val callee: Node, val args: List) : Node() +private data class UnaryExpr(val op: String, val expr: Node, val prefix: Boolean) : Node() +private data class BinExpr(val op: String, val left: Node, val right: Node) : Node() +private data class AssignExpr(val op: String, val left: Node, val right: Node) : Node() +private data class CondExpr(val test: Node, val cons: Node, val alt: Node) : Node() +private data class TypeofExpr(val expr: Node) : Node() +private data class SeqExpr(val exprs: List) : Node() +private data class FuncExpr(val name: String?, val params: List, val body: List) : Node() +private data class VarDecl(val decls: List>) : Node() +private data class ExprStmt(val expr: Node) : Node() +private data class BlockStmt(val stmts: List) : Node() +private data class ReturnStmt(val expr: Node?) : Node() +private data class IfStmt(val test: Node, val cons: Node, val alt: Node?) : Node() +private data class WhileStmt(val test: Node, val body: Node) : Node() +private data class ForStmt(val init: Node?, val test: Node?, val update: Node?, val body: Node) : Node() +private data class ForInStmt(val decl: String, val obj: Node, val body: Node) : Node() +private object BreakStmt : Node() +private object ContinueStmt : Node() +private data class TryCatch(val body: List, val catchParam: String?, val catchBody: List?, val finallyBody: List?) : Node() +private data class ThrowStmt(val expr: Node) : Node() + +private class Parser(private val lex: Lexer) { + + fun parseProgram(): List { + val stmts = mutableListOf() + while (lex.peek().type != TT.EOF) stmts.add(parseStatement()) + return stmts + } + + private fun parseStatement(): Node { + return when (lex.peek().type) { + TT.LBRACE -> parseBlock() + TT.SEMI -> { lex.consume(); BlockStmt(emptyList()) } + TT.IDENT -> when (lex.peek().raw) { + "var", "let", "const" -> parseVarDecl() + "function" -> parseFunctionDecl() + "return" -> parseReturn() + "if" -> parseIf() + "while" -> parseWhile() + "for" -> parseFor() + "break" -> { lex.consume(); lex.matchIf(TT.SEMI); BreakStmt } + "continue" -> { lex.consume(); lex.matchIf(TT.SEMI); ContinueStmt } + "try" -> parseTryCatch() + "throw" -> parseThrow() + "typeof" -> parseExprStmt() + else -> parseExprStmt() + } + else -> parseExprStmt() + } + } + + private fun parseBlock(): BlockStmt { + lex.expect(TT.LBRACE) + val stmts = mutableListOf() + while (lex.peek().type != TT.RBRACE && lex.peek().type != TT.EOF) stmts.add(parseStatement()) + lex.expect(TT.RBRACE) + return BlockStmt(stmts) + } + + private fun parseVarDecl(): VarDecl { + lex.consume() // var / let / const + val decls = mutableListOf>() + do { + val name = lex.expect(TT.IDENT).raw + val init = if (lex.matchIf(TT.EQ)) parseAssign() else null + decls.add(name to init) + } while (lex.matchIf(TT.COMMA)) + lex.matchIf(TT.SEMI) + return VarDecl(decls) + } + + private fun parseFunctionDecl(): Node { + lex.consume() // "function" + val name = if (lex.peek().type == TT.IDENT) lex.consume().raw else null + val params = parseParams() + val body = parseBlock().stmts + return if (name != null) VarDecl(listOf(name to FuncExpr(name, params, body))) else FuncExpr(name, params, body) + } + + private fun parseParams(): List { + lex.expect(TT.LPAREN) + val params = mutableListOf() + while (lex.peek().type != TT.RPAREN && lex.peek().type != TT.EOF) { + params.add(lex.expect(TT.IDENT).raw) + if (!lex.matchIf(TT.COMMA)) break + } + lex.expect(TT.RPAREN) + return params + } + + private fun parseReturn(): ReturnStmt { + lex.consume() + val expr = if (lex.peek().type == TT.SEMI || lex.peek().type == TT.RBRACE || lex.peek().type == TT.EOF) null else parseAssign() + lex.matchIf(TT.SEMI) + return ReturnStmt(expr) + } + + private fun parseIf(): IfStmt { + lex.consume() + lex.expect(TT.LPAREN) + val test = parseAssign() + lex.expect(TT.RPAREN) + val cons = parseStatement() + val alt = if (lex.peek().type == TT.IDENT && lex.peek().raw == "else") { lex.consume(); parseStatement() } else null + return IfStmt(test, cons, alt) + } + + private fun parseWhile(): WhileStmt { + lex.consume() + lex.expect(TT.LPAREN) + val test = parseAssign() + lex.expect(TT.RPAREN) + return WhileStmt(test, parseStatement()) + } + + private fun parseFor(): Node { + lex.consume() + lex.expect(TT.LPAREN) + // for..in check + if (lex.peek().type == TT.IDENT && (lex.peek().raw == "var" || lex.peek().raw == "let" || lex.peek().raw == "const")) { + val savedIdx = lex.peek() + lex.consume() + val varName = lex.expect(TT.IDENT).raw + if (lex.peek().type == TT.IDENT && lex.peek().raw == "in") { + lex.consume() + val obj = parseAssign() + lex.expect(TT.RPAREN) + return ForInStmt(varName, obj, parseStatement()) + } + // backtrack is hard so we just reconstruct as normal for + val init: Node? = if (lex.peek().type != TT.SEMI) { + val initExpr = if (lex.matchIf(TT.EQ)) parseAssign() else null + VarDecl(listOf(varName to initExpr)) + } else VarDecl(listOf(varName to null)) + lex.matchIf(TT.SEMI) + val test = if (lex.peek().type != TT.SEMI) parseAssign() else null + lex.matchIf(TT.SEMI) + val update = if (lex.peek().type != TT.RPAREN) parseAssign() else null + lex.expect(TT.RPAREN) + return ForStmt(init, test, update, parseStatement()) + } + val init = if (lex.peek().type != TT.SEMI) parseAssign() else null + lex.matchIf(TT.SEMI) + val test = if (lex.peek().type != TT.SEMI) parseAssign() else null + lex.matchIf(TT.SEMI) + val update = if (lex.peek().type != TT.RPAREN) parseAssign() else null + lex.expect(TT.RPAREN) + return ForStmt(init, test, update, parseStatement()) + } + + private fun parseTryCatch(): TryCatch { + lex.consume() // "try" + val body = parseBlock().stmts + var catchParam: String? = null + var catchBody: List? = null + if (lex.peek().type == TT.IDENT && lex.peek().raw == "catch") { + lex.consume() + if (lex.matchIf(TT.LPAREN)) { + catchParam = lex.expect(TT.IDENT).raw + lex.expect(TT.RPAREN) + } + catchBody = parseBlock().stmts + } + val finallyBody = if (lex.peek().type == TT.IDENT && lex.peek().raw == "finally") { + lex.consume(); parseBlock().stmts + } else null + return TryCatch(body, catchParam, catchBody, finallyBody) + } + + private fun parseThrow(): ThrowStmt { + lex.consume() + val expr = parseAssign() + lex.matchIf(TT.SEMI) + return ThrowStmt(expr) + } + + private fun parseExprStmt(): Node { + val expr = parseSeq() + lex.matchIf(TT.SEMI) + return ExprStmt(expr) + } + + private fun parseSeq(): Node { + val first = parseAssign() + if (lex.peek().type != TT.COMMA) return first + val exprs = mutableListOf(first) + while (lex.matchIf(TT.COMMA)) exprs.add(parseAssign()) + return SeqExpr(exprs) + } + + private fun parseAssign(): Node { + val left = parseTernary() + val op = when (lex.peek().type) { + TT.EQ -> "="; TT.PLUSEQ -> "+="; TT.MINUSEQ -> "-="; TT.STAREQ -> "*="; TT.SLASHEQ -> "/="; TT.PERCENTEQ -> "%="; TT.POWEQ -> "**=" + else -> return left + } + lex.consume() + return AssignExpr(op, left, parseAssign()) + } + + private fun parseTernary(): Node { + val test = parseOr() + if (!lex.matchIf(TT.QUESTION)) return test + val cons = parseAssign() + lex.expect(TT.COLON) + return CondExpr(test, cons, parseAssign()) + } + + private fun parseOr(): Node = parseBin(::parseAnd, TT.OR to "||") + private fun parseAnd(): Node = parseBin(::parseBitor, TT.AND to "&&") + private fun parseBitor(): Node = parseBin(::parseBitxor, TT.PIPE to "|") + private fun parseBitxor(): Node = parseBin(::parseBitand, TT.CARET to "^") + private fun parseBitand(): Node = parseBin(::parseEq, TT.AMP to "&") + private fun parseEq(): Node = parseBin(::parseRel, TT.EQEQ to "==", TT.EQEQEQ to "===", TT.NEQ to "!=", TT.NEQEQ to "!==") + private fun parseRel(): Node { + var left = parseShift() + while (true) { + left = when { + lex.peek().type == TT.LT -> { lex.consume(); BinExpr("<", left, parseShift()) } + lex.peek().type == TT.LTEQ -> { lex.consume(); BinExpr("<=", left, parseShift()) } + lex.peek().type == TT.GT -> { lex.consume(); BinExpr(">", left, parseShift()) } + lex.peek().type == TT.GTEQ -> { lex.consume(); BinExpr(">=", left, parseShift()) } + lex.peek().type == TT.IDENT && lex.peek().raw == "in" -> { lex.consume(); BinExpr("in", left, parseShift()) } + lex.peek().type == TT.IDENT && lex.peek().raw == "instanceof" -> { lex.consume(); BinExpr("instanceof", left, parseShift()) } + else -> break + } + } + return left + } + private fun parseShift(): Node = parseBin(::parseAdd, TT.LSHIFT to "<<", TT.RSHIFT to ">>", TT.URSHIFT to ">>>") + private fun parseAdd(): Node = parseBin(::parseMul, TT.PLUS to "+", TT.MINUS to "-") + private fun parseMul(): Node = parseBin(::parsePow, TT.STAR to "*", TT.SLASH to "/", TT.PERCENT to "%") + + /** `**` binds tighter than `* / %` and is right-associative: `2 ** 3 ** 2 == 2 ** (3 ** 2)`. */ + private fun parsePow(): Node { + val base = parseUnary() + if (lex.peek().type == TT.POW) { + lex.consume() + return BinExpr("**", base, parsePow()) + } + return base + } + + private fun parseBin(next: () -> Node, vararg ops: Pair): Node { + var left = next() + while (true) { + val op = ops.firstOrNull { it.first == lex.peek().type } ?: break + lex.consume() + left = BinExpr(op.second, left, next()) + } + return left + } + + private fun parseUnary(): Node { + return when (lex.peek().type) { + TT.MINUS -> { lex.consume(); UnaryExpr("-", parseUnary(), true) } + TT.PLUS -> { lex.consume(); UnaryExpr("+", parseUnary(), true) } + TT.NOT -> { lex.consume(); UnaryExpr("!", parseUnary(), true) } + TT.TILDE -> { lex.consume(); UnaryExpr("~", parseUnary(), true) } + TT.PLUSPLUS -> { lex.consume(); UnaryExpr("++", parsePostfix(), true) } + TT.MINUSMINUS -> { lex.consume(); UnaryExpr("--", parsePostfix(), true) } + TT.IDENT -> when (lex.peek().raw) { + "typeof" -> { lex.consume(); TypeofExpr(parseUnary()) } + "void" -> { lex.consume(); parseUnary(); UndefinedLit } + "new" -> parseNew() + else -> parsePostfix() + } + else -> parsePostfix() + } + } + + private fun parseNew(): Node { + lex.consume() // "new" + val callee = parsePrimary() + val args = if (lex.peek().type == TT.LPAREN) parseArgs() else emptyList() + var expr: Node = NewExpr(callee, args) + // Allow member access and calls on the constructed object: new Foo().bar, new Foo()[0] + while (true) { + expr = when (lex.peek().type) { + TT.DOT -> { + lex.consume() + val prop = lex.expect(TT.IDENT).raw + MemberExpr(expr, StrLit(prop), false) + } + TT.LBRACKET -> { + lex.consume() + val prop = parseAssign() + lex.expect(TT.RBRACKET) + MemberExpr(expr, prop, true) + } + TT.LPAREN -> CallExpr(expr, parseArgs()) + else -> break + } + } + return expr + } + + private fun parsePostfix(): Node { + var expr = parseCall() + while (true) { + expr = when (lex.peek().type) { + TT.PLUSPLUS -> { lex.consume(); UnaryExpr("++", expr, false) } + TT.MINUSMINUS -> { lex.consume(); UnaryExpr("--", expr, false) } + else -> break + } + } + return expr + } + + private fun parseCall(): Node { + var expr = parsePrimary() + while (true) { + expr = when (lex.peek().type) { + TT.LPAREN -> CallExpr(expr, parseArgs()) + TT.DOT -> { + lex.consume() + val prop = lex.expect(TT.IDENT).raw + MemberExpr(expr, StrLit(prop), false) + } + TT.LBRACKET -> { + lex.consume() + val prop = parseAssign() + lex.expect(TT.RBRACKET) + MemberExpr(expr, prop, true) + } + else -> break + } + } + return expr + } + + private fun parseArgs(): List { + lex.expect(TT.LPAREN) + val args = mutableListOf() + while (lex.peek().type != TT.RPAREN && lex.peek().type != TT.EOF) { + args.add(parseAssign()) + if (!lex.matchIf(TT.COMMA)) break + } + lex.expect(TT.RPAREN) + return args + } + + private fun parsePrimary(): Node { + val tok = lex.peek() + return when (tok.type) { + TT.NUMBER -> { + lex.consume() + val raw = tok.raw + val value = when { + raw.startsWith("0x") || raw.startsWith("0X") -> + raw.drop(2).toLongOrNull(16)?.toDouble() ?: Double.NaN + // Legacy octal literal, a leading zero followed only by octal digits + // (0-7), e.g. "010" == 8. If any digit is 8/9 it's just decimal ("08" == 8). + raw.length > 1 && raw[0] == '0' && raw.all { it in '0'..'7' } -> + raw.toLongOrNull(8)?.toDouble() ?: Double.NaN + else -> raw.toDoubleOrNull() ?: Double.NaN + } + NumLit(value) + } + TT.STRING -> { lex.consume(); StrLit(tok.raw) } + TT.LPAREN -> { + lex.consume() + val expr = parseSeq() + lex.expect(TT.RPAREN) + expr + } + TT.LBRACKET -> { + lex.consume() + val elems = mutableListOf() + while (lex.peek().type != TT.RBRACKET && lex.peek().type != TT.EOF) { + if (lex.peek().type == TT.COMMA) { + // Elision, a "hole" in the array, e.g. the middle slot of [1,,3]. + // Reads back as undefined but still occupies a slot/length. + elems.add(UndefinedLit) + lex.consume() + } else { + elems.add(parseAssign()) + if (lex.peek().type == TT.COMMA) lex.consume() else break + } + } + lex.expect(TT.RBRACKET) + ArrayLit(elems) + } + TT.LBRACE -> { + lex.consume() + val pairs = mutableListOf>() + while (lex.peek().type != TT.RBRACE && lex.peek().type != TT.EOF) { + val key = when (lex.peek().type) { + TT.IDENT, TT.STRING -> lex.consume().raw + TT.NUMBER -> lex.consume().raw + else -> lex.consume().raw + } + lex.expect(TT.COLON) + pairs.add(key to parseAssign()) + lex.matchIf(TT.COMMA) + } + lex.expect(TT.RBRACE) + ObjLit(pairs) + } + TT.IDENT -> { + val name = tok.raw + lex.consume() + when (name) { + "true" -> BoolLit(true) + "false" -> BoolLit(false) + "null" -> NullLit + "undefined" -> UndefinedLit + "function" -> { + val fname = if (lex.peek().type == TT.IDENT) lex.consume().raw else null + val params = parseParams() + FuncExpr(fname, params, parseBlock().stmts) + } + else -> Ident(name) + } + } + else -> { lex.consume(); UndefinedLit } + } + } +} + +private class ReturnSignal(val value: Any?) : Throwable() +private object BreakSignal : Throwable() +private object ContinueSignal : Throwable() +private class ThrowSignal(val value: Any?) : Throwable() + +/** + * Thrown when the enclosing [CoroutineScope] is cancelled (e.g. by [withTimeout]). + * Extends [CancellationException] so coroutines recognize and handle it correctly. + * + * We use a dedicated subclass rather than [CancellationException] directly so that + * tests can use [assertFailsWith]<[JsCancellationException]> to verify that cancellation + * originated from the interpreter's scope check rather than some other source. The class + * is [internal] rather than private for the same reason. + * + * The [TryCatch] handler in [JsInterpreter.execNode] explicitly rethrows this before + * its generic `catch (Exception)` clause, so a JS script's own try/catch block cannot + * swallow it and keep a cancelled loop alive. + */ +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal class JsCancellationException(message: String) : CancellationException(message) + +/** + * Internal signal thrown once a script exceeds its execution budget (time or instruction + * count), or the enclosing [CoroutineScope] has been cancelled. + * + * Deliberately extends [Throwable] rather than [Exception]. The interpreter's own + * `try`/`catch` node handling (see [JsInterpreter.execNode]'s `TryCatch` branch) only catches + * `Exception`, so a JS script can't wrap an infinite loop in its own try/catch and swallow this, + * keeping the loop alive forever. + */ +private class JsExecutionLimitExceeded(message: String) : Throwable(message) + +private fun toNumber(v: Any?): Double = when (v) { + null -> 0.0 // In JS Number(null) === 0 + is Unit -> Double.NaN // In JS Number(undefined) === NaN + is Double -> v + is Boolean -> if (v) 1.0 else 0.0 + is String -> stringToNumber(v) + is JsList -> stringToNumber(toJsString(v)) + else -> Double.NaN +} + +/** + * Returns the numeric value of [c] in the given [radix] (2..36), or -1 if [c] isn't a valid + * digit for that radix. Only handles ASCII '0'-'9'/'a'-'z'/'A'-'Z', which is all JS + * number syntax (and parseInt) ever recognizes anyway. + */ +private fun digitValue(c: Char, radix: Int): Int { + val d = when (c) { + in '0'..'9' -> c - '0' + in 'a'..'z' -> c - 'a' + 10 + in 'A'..'Z' -> c - 'A' + 10 + else -> return -1 + } + return if (d < radix) d else -1 +} + +/** Mirrors the JS ToNumber(string) abstract operation closely enough for our use-cases. */ +private fun stringToNumber(s: String): Double { + val trimmed = s.trim() + if (trimmed.isEmpty()) return 0.0 // In JS Number("") === 0, Number(" ") === 0 + when (trimmed) { + "Infinity", "+Infinity" -> return Double.POSITIVE_INFINITY + "-Infinity" -> return Double.NEGATIVE_INFINITY + } + return when { + trimmed.startsWith("0x") || trimmed.startsWith("0X") -> trimmed.drop(2).toLongOrNull(16)?.toDouble() ?: Double.NaN + trimmed.startsWith("0o") || trimmed.startsWith("0O") -> trimmed.drop(2).toLongOrNull(8)?.toDouble() ?: Double.NaN + trimmed.startsWith("0b") || trimmed.startsWith("0B") -> trimmed.drop(2).toLongOrNull(2)?.toDouble() ?: Double.NaN + else -> trimmed.toDoubleOrNull() ?: Double.NaN + } +} + +private fun toBoolean(v: Any?): Boolean = when (v) { + null, is Unit -> false + is Boolean -> v + is Double -> v != 0.0 && !v.isNaN() + is String -> v.isNotEmpty() + is JsList -> true + is JsObject -> true + else -> true +} + +/** Array.prototype.join/toString treats holes/null/undefined elements as the empty string. */ +private fun joinElement(e: Any?): String = if (e == null || e is Unit) "" else toJsString(e) + +private fun toJsString(v: Any?): String = when (v) { + null -> "null" + is Unit -> "undefined" + is Boolean -> v.toString() + is Double -> if (v == floor(v) && !v.isInfinite()) v.toLong().toString() else v.toString() + is String -> v + is JsList -> v.elements.joinToString(",") { joinElement(it) } + is JsObject -> "[object Object]" + is NativeFn -> v.toString() + is JsFunction -> "function ${v.name ?: ""}() { [native code] }" + else -> v.toString() +} + +private fun strictEq(a: Any?, b: Any?): Boolean = when { + a is Double && b is Double -> a == b + a is String && b is String -> a == b + a is Boolean && b is Boolean -> a == b + a == null && b == null -> true + a is Unit && b is Unit -> true + else -> a === b +} + +private fun looseEq(a: Any?, b: Any?): Boolean { + if (strictEq(a, b)) return true + // null == undefined + val aNullish = a == null || a is Unit + val bNullish = b == null || b is Unit + if (aNullish || bNullish) return aNullish && bNullish + // number coercion + val an = a is Double || a is Boolean + val bn = b is Double || b is Boolean + if (an || bn) return toNumber(a) == toNumber(b) + if (a is String || b is String) return toJsString(a) == toJsString(b) + return false +} + +private class JsList(val elements: MutableList = mutableListOf()) { + var length: Int + get() = elements.size + set(v) { while (elements.size < v) elements.add(Unit); while (elements.size > v) elements.removeAt(elements.size - 1) } + + operator fun get(idx: Int): Any? = elements.getOrElse(idx) { Unit } + operator fun set(idx: Int, v: Any?) { while (elements.size <= idx) elements.add(Unit); elements[idx] = v } +} + +private class JsObject(val props: MutableMap = mutableMapOf(), var constructor: JsFunction? = null) + +private class JsFunction( + val name: String?, + val params: List, + val body: List, + val closure: Scope, +) + +private class Scope(val parent: Scope? = null) { + val vars = mutableMapOf() + + fun get(name: String): Any? { + if (vars.containsKey(name)) return vars[name] + return parent?.get(name) ?: Unit + } + + fun set(name: String, value: Any?) { + val scope = findOwner(name) + if (scope != null) scope.vars[name] = value else vars[name] = value + } + + fun define(name: String, value: Any?) { vars[name] = value } + + private fun findOwner(name: String): Scope? = + if (vars.containsKey(name)) this else parent?.findOwner(name) +} + +private class JsInterpreter( + private val maxExecutionTime: Duration = JS_DEFAULT_MAX_EXECUTION_TIME, + private val maxInstructions: Long = JS_DEFAULT_MAX_INSTRUCTIONS, + private val scope: CoroutineScope? = null, +) { + private val globalScope = Scope() + + // Execution budget, reset at the start of every top-level eval() call. + private var instructionCount = 0L + private var startMark: TimeMark = TimeSource.Monotonic.markNow() + + init { installGlobals() } + + private fun installGlobals() { + val mathObj = JsObject(mutableMapOf( + "PI" to PI, "E" to E, + "floor" to nativeFn("floor") { args -> floor(toNumber(args.getOrNull(0))) }, + "ceil" to nativeFn("ceil") { args -> ceil(toNumber(args.getOrNull(0))) }, + "round" to nativeFn("round") { args -> round(toNumber(args.getOrNull(0))) }, + "abs" to nativeFn("abs") { args -> abs(toNumber(args.getOrNull(0))) }, + "sqrt" to nativeFn("sqrt") { args -> sqrt(toNumber(args.getOrNull(0))) }, + "pow" to nativeFn("pow") { args -> toNumber(args.getOrNull(0)).pow(toNumber(args.getOrNull(1))) }, + "log" to nativeFn("log") { args -> ln(toNumber(args.getOrNull(0))) }, + "sin" to nativeFn("sin") { args -> sin(toNumber(args.getOrNull(0))) }, + "cos" to nativeFn("cos") { args -> cos(toNumber(args.getOrNull(0))) }, + "tan" to nativeFn("tan") { args -> tan(toNumber(args.getOrNull(0))) }, + "max" to nativeFn("max") { args -> args.maxOfOrNull { toNumber(it) } ?: Double.NEGATIVE_INFINITY }, + "min" to nativeFn("min") { args -> args.minOfOrNull { toNumber(it) } ?: Double.POSITIVE_INFINITY }, + "random" to nativeFn("random") { _ -> Random.nextDouble() }, + "trunc" to nativeFn("trunc") { args -> truncate(toNumber(args.getOrNull(0))) }, + "log2" to nativeFn("log2") { args -> log2(toNumber(args.getOrNull(0))) }, + "log10" to nativeFn("log10") { args -> log10(toNumber(args.getOrNull(0))) }, + )) + globalScope.define("Math", mathObj) + + // For String.fromCharCode + val stringObj = JsObject(mutableMapOf( + "fromCharCode" to nativeFn("fromCharCode") { args -> args.joinToString("") { toNumber(it).toInt().toChar().toString() } } + )) + globalScope.define("String", stringObj) + + globalScope.define("parseInt", nativeFn("parseInt") { args -> + val s = toJsString(args.getOrNull(0)).trim() + var i = 0 + var negative = false + if (i < s.length && (s[i] == '+' || s[i] == '-')) { + negative = s[i] == '-' + i++ + } + var radix = args.getOrNull(1)?.let { toNumber(it).toInt() } ?: 0 + if (radix != 0 && (radix < 2 || radix > 36)) { + Double.NaN + } else { + val stripHexPrefix = radix == 0 || radix == 16 + if (radix == 0) radix = 10 + if (stripHexPrefix && i + 1 < s.length && s[i] == '0' && (s[i + 1] == 'x' || s[i + 1] == 'X')) { + radix = 16 + i += 2 + } + val start = i + while (i < s.length && digitValue(s[i], radix) != -1) i++ + if (i == start) { + Double.NaN + } else { + var value = 0.0 + for (idx in start until i) value = value * radix + digitValue(s[idx], radix) + if (negative) -value else value + } + } + }) + globalScope.define("parseFloat", nativeFn("parseFloat") { args -> toNumber(args.getOrNull(0)) }) + globalScope.define("isNaN", nativeFn("isNaN") { args -> toNumber(args.getOrNull(0)).isNaN() }) + globalScope.define("isFinite", nativeFn("isFinite") { args -> toNumber(args.getOrNull(0)).isFinite() }) + globalScope.define("decodeURIComponent", nativeFn("decodeURIComponent") { args -> toJsString(args.getOrNull(0)).decodeUrl() }) + globalScope.define("encodeURIComponent", nativeFn("encodeURIComponent") { args -> toJsString(args.getOrNull(0)).encodeUrl() }) + globalScope.define("escape", nativeFn("escape") { args -> toJsString(args.getOrNull(0)).encodeUrl() }) + globalScope.define("unescape", nativeFn("unescape") { args -> toJsString(args.getOrNull(0)).decodeUrl() }) + // Nested eval() reuses the current budget rather than resetting it, otherwise a + // script could keep itself alive forever via `while(true){ eval("1") }`. + globalScope.define("eval", nativeFn("eval") { args -> evalInternal(toJsString(args.getOrNull(0))) }) + globalScope.define("undefined", Unit) + globalScope.define("NaN", Double.NaN) + globalScope.define("Infinity", Double.POSITIVE_INFINITY) + + globalScope.define("Array", nativeFn("Array") { args -> + if (args.size == 1 && args[0] is Double) JsList(MutableList((args[0] as Double).toInt()) { Unit }) + else JsList(args.toMutableList()) + }) + + globalScope.define("Object", NativeFn({ args -> + // Object() called as a function, wrap primitive or return object as-is + when (val v = args.getOrNull(0)) { + null, is Unit -> JsObject() + is JsObject -> v + is JsList -> v + else -> JsObject() + } + }, "Object", mutableMapOf( + "keys" to nativeFn("keys") { args -> + when (val o = args.getOrNull(0)) { + is JsObject -> JsList(o.props.keys.map { it as Any? }.toMutableList()) + else -> JsList() + } + }, + "values" to nativeFn("values") { args -> + when (val o = args.getOrNull(0)) { + is JsObject -> JsList(o.props.values.toMutableList()) + else -> JsList() + } + } + ))) + + globalScope.define("Function", nativeFn("Function") { _ -> nativeFn("anonymous") { Unit } }) + globalScope.define("Number", nativeFn("Number") { args -> toNumber(args.getOrNull(0)) }) + globalScope.define("Boolean", nativeFn("Boolean") { args -> toBoolean(args.getOrNull(0)) }) + + // console.log (no-op for silence, but avoids errors) + val consoleObj = JsObject(mutableMapOf( + "log" to nativeFn("log") { _ -> Unit }, + "error" to nativeFn("error") { _ -> Unit }, + "warn" to nativeFn("warn") { _ -> Unit }, + )) + globalScope.define("console", consoleObj) + } + + private fun nativeFn(name: String, fn: (List) -> Any?): Any? = NativeFn(fn, name) + + fun eval(code: String): Any? { + instructionCount = 0 + startMark = TimeSource.Monotonic.markNow() + return evalInternal(code) + } + + /** Runs [code] against the current budget, without resetting it. */ + private fun evalInternal(code: String): Any? { + return try { + val lexer = Lexer(code) + val parser = Parser(lexer) + val stmts = parser.parseProgram() + var last: Any? = Unit + for (stmt in stmts) last = execNode(stmt, globalScope) + last + } catch (r: ReturnSignal) { + r.value + } catch (e: JsCancellationException) { + // CancellationException must never be swallowed. It signals that the + // enclosing coroutine has been cancelled and must propagate so that the + // coroutine can clean up correctly. + throw e + } catch (t: Throwable) { + logError(t) + Unit + } + } + + /** + * Called on every statement execution. Throws [JsExecutionLimitExceeded] once the + * script has used up its time or instruction budget, or the enclosing [CoroutineScope] + * has been cancelled. This is what lets something like `evalJs("while(true){}")` + * return instead of burning the CPU forever. + * + * [JsExecutionLimitExceeded] extends [Throwable] rather than [Exception], so a JS + * script cannot catch it with its own try/catch block (the [TryCatch] handler in + * [execNode] only catches [Exception]). + */ + private fun checkBudget() { + instructionCount++ + if (instructionCount >= maxInstructions) { + throw JsExecutionLimitExceeded("script exceeded max instruction count of $maxInstructions") + } + if (scope != null && !scope.isActive) { + throw JsCancellationException("script cancelled: coroutine scope is no longer active") + } + // Only sample the clock every 1024 ticks. Calling elapsedNow() on every single + // statement would add measurable overhead to normal (non-runaway) scripts. + if (instructionCount and 0x3FFL == 0L && startMark.elapsedNow() >= maxExecutionTime) { + throw JsExecutionLimitExceeded("script exceeded max execution time of $maxExecutionTime") + } + } + + fun getVar(name: String): Any? = globalScope.get(name).let { if (it is Unit) null else it } + fun setVar(name: String, value: Any?) = globalScope.define(name, value) + + private fun execNode(node: Node, scope: Scope): Any? { + checkBudget() + return when (node) { + is VarDecl -> { + for ((name, init) in node.decls) scope.define(name, init?.let { evalExpr(it, scope) }) + Unit + } + is ExprStmt -> evalExpr(node.expr, scope) + is BlockStmt -> { + val inner = Scope(scope) + var last: Any? = Unit + for (s in node.stmts) last = execNode(s, inner) + last + } + is ReturnStmt -> throw ReturnSignal(node.expr?.let { evalExpr(it, scope) }) + is IfStmt -> { + if (toBoolean(evalExpr(node.test, scope))) execNode(node.cons, scope) + else node.alt?.let { execNode(it, scope) } + } + is WhileStmt -> { + try { + while (toBoolean(evalExpr(node.test, scope))) { + try { execNode(node.body, scope) } catch (_: ContinueSignal) {} + } + } catch (_: BreakSignal) {} + Unit + } + is ForStmt -> { + val inner = Scope(scope) + node.init?.let { execNode(it, inner) } + try { + while (node.test == null || toBoolean(evalExpr(node.test, inner))) { + try { execNode(node.body, inner) } catch (_: ContinueSignal) {} + node.update?.let { evalExpr(it, inner) } + } + } catch (_: BreakSignal) {} + Unit + } + is ForInStmt -> { + val obj = evalExpr(node.obj, scope) + val inner = Scope(scope) + try { + when (obj) { + is JsObject -> for (key in obj.props.keys) { + inner.define(node.decl, key) + try { execNode(node.body, inner) } catch (_: ContinueSignal) {} + } + is JsList -> for (i in obj.elements.indices) { + inner.define(node.decl, i.toDouble()) + try { execNode(node.body, inner) } catch (_: ContinueSignal) {} + } + else -> {} + } + } catch (_: BreakSignal) {} + Unit + } + is TryCatch -> { + try { + for (s in node.body) execNode(s, scope) + } catch (ts: ThrowSignal) { + if (node.catchBody != null) { + val inner = Scope(scope) + if (node.catchParam != null) inner.define(node.catchParam, ts.value) + for (s in node.catchBody) execNode(s, inner) + } + } catch (e: JsCancellationException) { + // CancellationException must never be swallowed by a JS try/catch. + // It must propagate so withTimeout and structured concurrency + // work correctly. + throw e + } catch (_: Exception) { + // swallow other exceptions inside try (e.g. runtime errors) + } finally { + node.finallyBody?.forEach { execNode(it, scope) } + } + Unit + } + is ThrowStmt -> throw ThrowSignal(evalExpr(node.expr, scope)) + is BreakStmt -> throw BreakSignal + is ContinueStmt -> throw ContinueSignal + else -> evalExpr(node, scope) + } + } + + private fun evalExpr(node: Node, scope: Scope): Any? = when (node) { + is NumLit -> node.v + is StrLit -> node.v + is BoolLit -> node.v + NullLit -> null + UndefinedLit -> Unit + is Ident -> scope.get(node.name) + is ArrayLit -> JsList(node.elems.map { evalExpr(it, scope) }.toMutableList()) + is ObjLit -> JsObject(node.pairs.associate { (k, v) -> k to evalExpr(v, scope) }.toMutableMap()) + is FuncExpr -> { + val fn = JsFunction(node.name, node.params, node.body, scope) + if (node.name != null) scope.define(node.name, fn) + fn + } + is SeqExpr -> node.exprs.fold(Unit as Any?) { _, e -> evalExpr(e, scope) } + is TypeofExpr -> { + val v = try { evalExpr(node.expr, scope) } catch (_: Exception) { Unit } + when (v) { + is Unit -> "undefined" + null -> "object" + is Double -> "number" + is Boolean -> "boolean" + is String -> "string" + is JsFunction, is NativeFn -> "function" + else -> "object" + } + } + is UnaryExpr -> evalUnary(node, scope) + is BinExpr -> evalBinary(node, scope) + is AssignExpr -> evalAssign(node, scope) + is CondExpr -> if (toBoolean(evalExpr(node.test, scope))) evalExpr(node.cons, scope) else evalExpr(node.alt, scope) + is MemberExpr -> evalMember(node, scope) + is CallExpr -> evalCall(node, scope) + is NewExpr -> evalNew(node, scope) + else -> Unit + } + + private fun evalUnary(node: UnaryExpr, scope: Scope): Any? { + if (node.op == "++" || node.op == "--") { + val delta = if (node.op == "++") 1.0 else -1.0 + val old = toNumber(evalExpr(node.expr, scope)) + val newVal = old + delta + assignTo(node.expr, newVal, scope) + return if (node.prefix) newVal else old + } + val v = evalExpr(node.expr, scope) + return when (node.op) { + "-" -> -toNumber(v) + "+" -> toNumber(v) + "!" -> !toBoolean(v) + "~" -> toNumber(v).toLong().inv().toDouble() + else -> Unit + } + } + + private fun evalBinary(node: BinExpr, scope: Scope): Any? { + // Short-circuit + if (node.op == "&&") { + val l = evalExpr(node.left, scope) + return if (!toBoolean(l)) l else evalExpr(node.right, scope) + } + if (node.op == "||") { + val l = evalExpr(node.left, scope) + return if (toBoolean(l)) l else evalExpr(node.right, scope) + } + val l = evalExpr(node.left, scope) + val r = evalExpr(node.right, scope) + return when (node.op) { + "+" -> { + val lp = if (l is JsList || l is JsObject || l is NativeFn || l is JsFunction) toJsString(l) else l + val rp = if (r is JsList || r is JsObject || r is NativeFn || r is JsFunction) toJsString(r) else r + if (lp is String || rp is String) toJsString(lp) + toJsString(rp) + else toNumber(lp) + toNumber(rp) + } + "-" -> toNumber(l) - toNumber(r) + "*" -> toNumber(l) * toNumber(r) + "/" -> toNumber(l) / toNumber(r) + "%" -> toNumber(l) % toNumber(r) + "**" -> toNumber(l).pow(toNumber(r)) + "<" -> toNumber(l) < toNumber(r) + "<=" -> toNumber(l) <= toNumber(r) + ">" -> toNumber(l) > toNumber(r) + ">=" -> toNumber(l) >= toNumber(r) + "==" -> looseEq(l, r) + "!=" -> !looseEq(l, r) + "===" -> strictEq(l, r) + "!==" -> !strictEq(l, r) + "&" -> (toNumber(l).toLong() and toNumber(r).toLong()).toDouble() + "|" -> (toNumber(l).toLong() or toNumber(r).toLong()).toDouble() + "^" -> (toNumber(l).toLong() xor toNumber(r).toLong()).toDouble() + "<<" -> (toNumber(l).toLong() shl toNumber(r).toInt()).toDouble() + ">>" -> (toNumber(l).toLong() shr toNumber(r).toInt()).toDouble() + ">>>" -> (toNumber(l).toInt().toLong() and 0xFFFFFFFFL ushr toNumber(r).toInt()).toDouble() + "instanceof" -> when (r) { + is NativeFn -> when (r.name) { + "Array" -> l is JsList + "Object" -> l is JsObject || l is JsList + "Function" -> l is JsFunction || l is NativeFn + "String" -> l is String + "Number" -> l is Double + "Boolean" -> l is Boolean + else -> false + } + is JsFunction -> l is JsObject && l.constructor === r + else -> false + } + "in" -> when (r) { + is JsObject -> toJsString(l) in r.props + is JsList -> toNumber(l).toInt().let { it >= 0 && it < r.elements.size } + else -> false + } + else -> Unit + } + } + + private fun evalAssign(node: AssignExpr, scope: Scope): Any? { + val right = evalExpr(node.right, scope) + val value = if (node.op == "=") right else { + val left = evalExpr(node.left, scope) + when (node.op) { + "+=" -> if (left is String || right is String) toJsString(left) + toJsString(right) else toNumber(left) + toNumber(right) + "-=" -> toNumber(left) - toNumber(right) + "*=" -> toNumber(left) * toNumber(right) + "/=" -> toNumber(left) / toNumber(right) + "%=" -> toNumber(left) % toNumber(right) + "**=" -> toNumber(left).pow(toNumber(right)) + else -> right + } + } + assignTo(node.left, value, scope) + return value + } + + private fun assignTo(target: Node, value: Any?, scope: Scope) { + when (target) { + is Ident -> scope.set(target.name, value) + is MemberExpr -> { + val obj = evalExpr(target.obj, scope) + val key = propKey(target.prop, target.computed, scope) + when (obj) { + is JsObject -> obj.props[key] = value + is JsList -> { + val idx = key.toIntOrNull() + if (idx != null) obj[idx] = value + else if (key == "length") obj.length = toNumber(value).toInt() + } + else -> {} + } + } + // Anything else (e.g. `true++` or `5 = 3`) is not a valid assignment target in + // JS. This would be a SyntaxError/ReferenceError there, so we fail the same way + // here rather than silently doing nothing. + else -> throw RuntimeException("Invalid assignment target: $target") + } + } + + /** Resolves a MemberExpr property node to its string key without unsafe casts. */ + private fun propKey(prop: Node, computed: Boolean, scope: Scope): String = + if (computed) toJsString(evalExpr(prop, scope)) + else (prop as? StrLit)?.v ?: toJsString(evalExpr(prop, scope)) + + private fun evalMember(node: MemberExpr, scope: Scope): Any? { + val obj = evalExpr(node.obj, scope) + val key = propKey(node.prop, node.computed, scope) + return getMember(obj, key) + } + + private fun getMember(obj: Any?, key: String): Any? = when (obj) { + is NativeFn -> obj.props[key] ?: Unit + is JsObject -> obj.props[key] ?: Unit + is JsList -> when (key) { + "length" -> obj.length.toDouble() + "join" -> nativeFn("join") { args -> + val sep = args.getOrNull(0)?.let { toJsString(it) } ?: "," + obj.elements.joinToString(sep) { joinElement(it) } + } + "reverse" -> nativeFn("reverse") { _ -> obj.elements.reverse(); obj } + "push" -> nativeFn("push") { args -> args.forEach { obj.elements.add(it) }; obj.elements.size.toDouble() } + "pop" -> nativeFn("pop") { _ -> if (obj.elements.isEmpty()) Unit else obj.elements.removeAt(obj.elements.size - 1) } + "shift" -> nativeFn("shift") { _ -> if (obj.elements.isEmpty()) Unit else obj.elements.removeAt(0) } + "unshift" -> nativeFn("unshift") { args -> args.reversed().forEach { obj.elements.add(0, it) }; obj.elements.size.toDouble() } + "slice" -> nativeFn("slice") { args -> + val start = args.getOrNull(0)?.let { toNumber(it).toInt() } ?: 0 + val end = args.getOrNull(1)?.let { toNumber(it).toInt() } ?: obj.elements.size + val s = if (start < 0) maxOf(0, obj.elements.size + start) else minOf(start, obj.elements.size) + val e = if (end < 0) maxOf(0, obj.elements.size + end) else minOf(end, obj.elements.size) + JsList(obj.elements.subList(maxOf(0, s), maxOf(s, e)).toMutableList()) + } + "splice" -> nativeFn("splice") { args -> + val start = args.getOrNull(0)?.let { toNumber(it).toInt() }?.let { if (it < 0) maxOf(0, obj.elements.size + it) else minOf(it, obj.elements.size) } ?: 0 + val deleteCount = args.getOrNull(1)?.let { toNumber(it).toInt() }?.coerceIn(0, obj.elements.size - start) ?: (obj.elements.size - start) + val removed = JsList(obj.elements.subList(start, start + deleteCount).toMutableList()) + repeat(deleteCount) { obj.elements.removeAt(start) } + args.drop(2).forEachIndexed { i, v -> obj.elements.add(start + i, v) } + removed + } + "indexOf" -> nativeFn("indexOf") { args -> + val v = args.getOrNull(0); val start = args.getOrNull(1)?.let { toNumber(it).toInt() } ?: 0 + obj.elements.indexOfFirst { strictEq(it, v) }.let { if (it < start) -1.0 else it.toDouble() } + } + "map" -> nativeFn("map") { args -> + val fn = args.getOrNull(0) + JsList(obj.elements.mapIndexed { i, v -> callAny(fn, listOf(v, i.toDouble(), obj), null) }.toMutableList()) + } + "filter" -> nativeFn("filter") { args -> + val fn = args.getOrNull(0) + JsList(obj.elements.filterIndexed { i, v -> toBoolean(callAny(fn, listOf(v, i.toDouble(), obj), null)) }.toMutableList()) + } + "forEach" -> nativeFn("forEach") { args -> + val fn = args.getOrNull(0) + obj.elements.forEachIndexed { i, v -> callAny(fn, listOf(v, i.toDouble(), obj), null) } + Unit + } + "reduce" -> nativeFn("reduce") { args -> + val fn = args.getOrNull(0) + var acc: Any? = if (args.size > 1) args[1] else obj.elements.firstOrNull() ?: Unit + val startIdx = if (args.size > 1) 0 else 1 + for (i in startIdx until obj.elements.size) acc = callAny(fn, listOf(acc, obj.elements[i], i.toDouble(), obj), null) + acc + } + "concat" -> nativeFn("concat") { args -> + val result = JsList(obj.elements.toMutableList()) + args.forEach { a -> when (a) { is JsList -> result.elements.addAll(a.elements); else -> result.elements.add(a) } } + result + } + "find" -> nativeFn("find") { args -> + val fn = args.getOrNull(0) + obj.elements.firstOrNull { toBoolean(callAny(fn, listOf(it), null)) } ?: Unit + } + "some" -> nativeFn("some") { args -> + val fn = args.getOrNull(0) + obj.elements.any { toBoolean(callAny(fn, listOf(it), null)) } + } + "every" -> nativeFn("every") { args -> + val fn = args.getOrNull(0) + obj.elements.all { toBoolean(callAny(fn, listOf(it), null)) } + } + "sort" -> nativeFn("sort") { args -> + val fn = args.getOrNull(0) + if (fn == null) obj.elements.sortWith { a, b -> toJsString(a).compareTo(toJsString(b)) } + else obj.elements.sortWith { a, b -> toNumber(callAny(fn, listOf(a, b), null)).toInt() } + obj + } + "includes" -> nativeFn("includes") { args -> obj.elements.any { looseEq(it, args.getOrNull(0)) } } + "toString" -> nativeFn("toString") { _ -> obj.elements.joinToString(",") { joinElement(it) } } + "flat" -> nativeFn("flat") { _ -> + val result = JsList() + obj.elements.forEach { if (it is JsList) result.elements.addAll(it.elements) else result.elements.add(it) } + result + } + else -> key.toIntOrNull()?.let { obj[it] } ?: Unit + } + is String -> when (key) { + "length" -> obj.length.toDouble() + "split" -> nativeFn("split") { args -> + val sep = args.getOrNull(0) + when { + sep == null || sep is Unit -> JsList(mutableListOf(obj)) + sep is String && sep.isEmpty() -> JsList(obj.map { it.toString() as Any? }.toMutableList()) + sep is String -> JsList(obj.split(sep).map { it as Any? }.toMutableList()) + else -> JsList(obj.split(toJsString(sep)).map { it as Any? }.toMutableList()) + } + } + "join" -> nativeFn("join") { args -> obj } // strings don't have join but just in case + "replace" -> nativeFn("replace") { args -> + val from = args.getOrNull(0); val to = toJsString(args.getOrNull(1)) + when (from) { + is String -> obj.replaceFirst(from, to) + else -> obj.replace(toJsString(from), to) + } + } + "replaceAll" -> nativeFn("replaceAll") { args -> + val from = args.getOrNull(0); val to = toJsString(args.getOrNull(1)) + obj.replace(toJsString(from), to) + } + "indexOf" -> nativeFn("indexOf") { args -> obj.indexOf(toJsString(args.getOrNull(0))).toDouble() } + "lastIndexOf" -> nativeFn("lastIndexOf") { args -> obj.lastIndexOf(toJsString(args.getOrNull(0))).toDouble() } + "includes" -> nativeFn("includes") { args -> obj.contains(toJsString(args.getOrNull(0))) } + "startsWith" -> nativeFn("startsWith") { args -> obj.startsWith(toJsString(args.getOrNull(0))) } + "endsWith" -> nativeFn("endsWith") { args -> obj.endsWith(toJsString(args.getOrNull(0))) } + "slice" -> nativeFn("slice") { args -> + val start = args.getOrNull(0)?.let { toNumber(it).toInt() }?.let { if (it < 0) maxOf(0, obj.length + it) else minOf(it, obj.length) } ?: 0 + val end = args.getOrNull(1)?.let { toNumber(it).toInt() }?.let { if (it < 0) maxOf(0, obj.length + it) else minOf(it, obj.length) } ?: obj.length + if (end <= start) "" else obj.substring(start, end) + } + "substr" -> nativeFn("substr") { args -> + val start = args.getOrNull(0)?.let { toNumber(it).toInt() }?.let { if (it < 0) maxOf(0, obj.length + it) else minOf(it, obj.length) } ?: 0 + val len = args.getOrNull(1)?.let { toNumber(it).toInt() } ?: (obj.length - start) + if (len <= 0) "" else obj.substring(start, minOf(start + len, obj.length)) + } + "substring" -> nativeFn("substring") { args -> + val a = args.getOrNull(0)?.let { toNumber(it).toInt().coerceIn(0, obj.length) } ?: 0 + val b = args.getOrNull(1)?.let { toNumber(it).toInt().coerceIn(0, obj.length) } ?: obj.length + obj.substring(minOf(a, b), maxOf(a, b)) + } + "charAt" -> nativeFn("charAt") { args -> + val i = args.getOrNull(0)?.let { toNumber(it).toInt() } ?: 0 + if (i < 0 || i >= obj.length) "" else obj[i].toString() + } + "charCodeAt" -> nativeFn("charCodeAt") { args -> + val i = args.getOrNull(0)?.let { toNumber(it).toInt() } ?: 0 + if (i < 0 || i >= obj.length) Double.NaN else obj[i].code.toDouble() + } + "codePointAt" -> nativeFn("codePointAt") { args -> + val i = args.getOrNull(0)?.let { toNumber(it).toInt() } ?: 0 + if (i < 0 || i >= obj.length) Double.NaN else obj[i].code.toDouble() + } + "toUpperCase", "toLocaleUpperCase" -> nativeFn("toUpperCase") { _ -> obj.uppercase() } + "toLowerCase", "toLocaleLowerCase" -> nativeFn("toLowerCase") { _ -> obj.lowercase() } + "trim" -> nativeFn("trim") { _ -> obj.trim() } + "trimStart", "trimLeft" -> nativeFn("trimStart") { _ -> obj.trimStart() } + "trimEnd", "trimRight" -> nativeFn("trimEnd") { _ -> obj.trimEnd() } + "repeat" -> nativeFn("repeat") { args -> obj.repeat(toNumber(args.getOrNull(0)).toInt().coerceAtLeast(0)) } + "padStart" -> nativeFn("padStart") { args -> + val len = toNumber(args.getOrNull(0)).toInt(); val pad = args.getOrNull(1)?.let { toJsString(it) } ?: " " + if (obj.length >= len) obj else (pad.repeat(len) + obj).takeLast(len) + } + "padEnd" -> nativeFn("padEnd") { args -> + val len = toNumber(args.getOrNull(0)).toInt(); val pad = args.getOrNull(1)?.let { toJsString(it) } ?: " " + if (obj.length >= len) obj else (obj + pad.repeat(len)).take(len) + } + "toString" -> nativeFn("toString") { _ -> obj } + "valueOf" -> nativeFn("valueOf") { _ -> obj } + "match" -> nativeFn("match") { args -> + val pattern = toJsString(args.getOrNull(0)) + try { + val result = Regex(pattern).find(obj) + if (result == null) null + else JsList(result.groupValues.map { it as Any? }.toMutableList()) + } catch (_: Exception) { null } + } + else -> key.toIntOrNull()?.let { + if (it >= 0 && it < obj.length) obj[it].toString() else Unit + } ?: Unit + } + is Double -> when (key) { + "toString" -> nativeFn("toString") { args -> + val radix = args.getOrNull(0)?.let { toNumber(it).toInt() } ?: 10 + if (radix == 10) toJsString(obj) else obj.toLong().toString(radix) + } + "toFixed" -> nativeFn("toFixed") { args -> + val digits = (args.getOrNull(0)?.let { toNumber(it).toInt() } ?: 0).coerceIn(0, 20) + val factor = 10.0.pow(digits) + val rounded = round(obj * factor) / factor + val sign = if (rounded < 0) "-" else "" + val absVal = abs(rounded) + val intPart = absVal.toLong() + val fracPart = round((absVal - intPart) * factor).toLong() + val fracStr = fracPart.toString().padStart(digits, '0') + if (digits == 0) "$sign$intPart" else "$sign$intPart.$fracStr" + } + else -> Unit + } + else -> Unit + } + + private fun evalCall(node: CallExpr, scope: Scope): Any? { + val (callee, thisVal) = resolveCallee(node.callee, scope) + val args = node.args.map { evalExpr(it, scope) } + return callAny(callee, args, thisVal) + } + + private fun resolveCallee(calleeNode: Node, scope: Scope): Pair { + return if (calleeNode is MemberExpr) { + val obj = evalExpr(calleeNode.obj, scope) + val key = propKey(calleeNode.prop, calleeNode.computed, scope) + getMember(obj, key) to obj + } else { + evalExpr(calleeNode, scope) to null + } + } + + private fun evalNew(node: NewExpr, scope: Scope): Any? { + val callee = evalExpr(node.callee, scope) + val args = node.args.map { evalExpr(it, scope) } + // NativeFn constructors (e.g. Array) return their value directly + if (callee is NativeFn) return callee.fn(args) + val thisVal = JsObject(constructor = callee as? JsFunction) + val result = callAny(callee, args, thisVal) + // JS 'new' returns the constructed object unless the constructor explicitly returns an object + return if (result is JsObject) result else thisVal + } + + private fun callAny(callee: Any?, args: List, thisVal: Any?): Any? = when (callee) { + is NativeFn -> callee.fn(args) + is JsFunction -> { + val fnScope = Scope(callee.closure) + fnScope.define("this", thisVal) + fnScope.define("arguments", JsList(args.toMutableList())) + callee.params.forEachIndexed { i, p -> fnScope.define(p, args.getOrElse(i) { Unit }) } + try { + var last: Any? = Unit + for (s in callee.body) last = execNode(s, fnScope) + last + } catch (r: ReturnSignal) { + r.value + } + } + else -> Unit + } +} + +// Wrapper so we can store Kotlin lambdas as "callable" values +private class NativeFn(val fn: (List) -> Any?, val name: String, val props: MutableMap = mutableMapOf()) { + override fun toString(): String = "function $name() { [native code] }" +} diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt new file mode 100644 index 000000000..182ea6c95 --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt @@ -0,0 +1,2410 @@ +package com.lagradost.cloudstream3.utils + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.math.E +import kotlin.math.PI +import kotlin.math.abs +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlin.time.TimeSource + +class JsInterpreterTest { + + private fun bool(code: String, variable: String? = null): Boolean = evalJsInternal(code, variable) as? Boolean ?: false + private fun num(code: String, variable: String? = null): Double = evalJsInternal(code, variable) as? Double ?: Double.NaN + private fun str(code: String, variable: String? = null): String = jsValueToString(evalJsInternal(code, variable)) + + private fun assertApprox(expected: Double, actual: Double, tol: Double = 1e-9) { + assertTrue(abs(actual - expected) <= tol, "Expected $expected ± $tol but was $actual") + } + + @Test + fun integerLiteral() { + assertEquals(42.0, num("42")) + } + + @Test + fun negativeLiteral() { + assertEquals(-7.0, num("-7")) + } + + @Test + fun floatLiteral() { + assertApprox(3.14, num("3.14")) + } + + @Test + fun hexLiteral() { + assertEquals(255.0, num("0xff")) + } + + @Test + fun stringLiteralDouble() { + assertEquals("hi", str("\"hi\"")) + } + + @Test + fun stringLiteralSingle() { + assertEquals("hi", str("'hi'")) + } + + @Test + fun templateLiteral() { + assertEquals("hi", str("`hi`")) + } + + @Test + fun booleanTrue() { + assertTrue(bool("true")) + } + + @Test + fun booleanFalse() { + assertFalse(bool("false")) + } + + @Test + fun nullLiteral() { + assertNull(evalJsInternal("null")) + } + + @Test + fun undefinedLiteral() { + assertEquals(Unit, evalJsInternal("undefined")) + } + + @Test + fun addition() { + assertEquals(5.0, num("2+3")) + } + + @Test + fun subtraction() { + assertEquals(1.0, num("3-2")) + } + + @Test + fun multiplication() { + assertEquals(6.0, num("2*3")) + } + + @Test + fun division() { + assertEquals(2.5, num("5/2")) + } + + @Test + fun modulo() { + assertEquals(1.0, num("7%3")) + } + + @Test + fun operatorPrecedence() { + assertEquals(7.0, num("1+2*3")) + } + + @Test + fun parenthesesOverride() { + assertEquals(9.0, num("(1+2)*3")) + } + + @Test + fun unaryMinus() { + assertEquals(-5.0, num("-(2+3)")) + } + + @Test + fun unaryPlusCoerces() { + assertEquals(3.0, num("+'3'")) + } + + @Test + fun stringMinusNumberCoercesToNumber() { + // "3" - 1 => 2 (JS coerces string to number for subtraction) + assertEquals(2.0, num("'3'-1")) + } + + @Test + fun stringTimesStringCoercesToNumber() { + // "3" * "4" => 12 + assertEquals(12.0, num("'3'*'4'")) + } + + @Test + fun nanArithmetic() { + // NaN + 1 => NaN + assertTrue(num("NaN+1").isNaN()) + } + + @Test + fun infinityPositive() { + assertTrue(num("Infinity").isInfinite() && num("Infinity") > 0) + } + + @Test + fun infinityArithmetic() { + assertTrue(num("Infinity+1").isInfinite()) + } + + @Test + fun divisionByZeroIsInfinity() { + assertTrue(num("1/0").isInfinite() && num("1/0") > 0) + } + + @Test + fun bitwiseAnd() { + assertEquals(2.0, num("6&3")) + } + + @Test + fun bitwiseOr() { + assertEquals(7.0, num("5|3")) + } + + @Test + fun bitwiseXor() { + assertEquals(6.0, num("5^3")) + } + + @Test + fun bitwiseNot() { + assertEquals(-6.0, num("~5")) + } + + @Test + fun bitwiseNotOnNegative() { + assertEquals(4.0, num("~(-5)")) + } + + @Test + fun bitwiseNotTruncatesFloat() { + // ~3.9 == ~3 == -4 + assertEquals(-4.0, num("~3.9")) + } + + @Test + fun leftShift() { + assertEquals(20.0, num("5<<2")) + } + + @Test + fun rightShift() { + assertEquals(1.0, num("5>>2")) + } + + @Test + fun unsignedRightShift() { + assertEquals(1.0, num("5>>>2")) + } + + @Test + fun unsignedRightShiftOnNegative() { + // (-1 >>> 0) in JS == 4294967295 (treats as unsigned 32-bit) + assertEquals(4294967295.0, num("-1>>>0")) + } + + @Test + fun bitwiseOrWithZeroTruncatesFloat() { + // 3.9|0 == 3 (common JS truncation idiom) + assertEquals(3.0, num("3.9|0")) + } + + @Test + fun lessThanTrue() { + assertTrue(bool("1<2")) + } + + @Test + fun lessThanFalse() { + assertFalse(bool("2<1")) + } + + @Test + fun greaterThanTrue() { + assertTrue(bool("2>1")) + } + + @Test + fun lessThanOrEqualEqual() { + assertTrue(bool("2<=2")) + } + + @Test + fun greaterThanOrEqualEqual() { + assertTrue(bool("2>=2")) + } + + @Test + fun looseEqNumberString() { + assertTrue(bool("1=='1'")) + } + + @Test + fun looseEqNullUndefined() { + // null == undefined is true in JS + assertTrue(bool("null==undefined")) + } + + @Test + fun looseEqNullNotZero() { + // null == 0 is false in JS + assertFalse(bool("null==0")) + } + + @Test + fun looseNeqWorks() { + assertTrue(bool("1!='2'")) + } + + @Test + fun strictEqSameType() { + assertTrue(bool("1===1")) + } + + @Test + fun strictEqDifferentType() { + assertFalse(bool("1==='1'")) + } + + @Test + fun strictNeq() { + assertTrue(bool("1!=='1'")) + } + + @Test + fun instanceofArrayTrue() { + assertTrue(bool("[] instanceof Array")) + } + + @Test + fun instanceofArrayFalseForObject() { + assertFalse(bool("({}) instanceof Array")) + } + + @Test + fun instanceofObjectTrueForPlainObject() { + assertTrue(bool("({}) instanceof Object")) + } + + @Test + fun instanceofObjectTrueForArray() { + // Arrays are objects in JS + assertTrue(bool("[] instanceof Object")) + } + + @Test + fun instanceofFunctionTrue() { + assertTrue(bool("(function(){}) instanceof Function")) + } + + @Test + fun instanceofFunctionFalseForArray() { + assertFalse(bool("[] instanceof Function")) + } + + @Test + fun instanceofUserDefinedConstructorTrue() { + assertTrue(bool("function Dog(){} var d = new Dog(); d instanceof Dog")) + } + + @Test + fun instanceofUserDefinedConstructorFalseForOtherClass() { + assertFalse(bool("function Cat(){} function Dog(){} var d = new Dog(); d instanceof Cat")) + } + + @Test + fun instanceofUserDefinedConstructorFalseForPlainObject() { + assertFalse(bool("function Dog(){} ({}) instanceof Dog")) + } + + @Test + fun instanceofUserDefinedConstructorWithProperties() { + val code = """ + function Point(x, y) { this.x = x; this.y = y; } + var p = new Point(1, 2); + (p instanceof Point) && p.x === 1 && p.y === 2 + """.trimIndent() + assertTrue(bool(code)) + } + + @Test + fun inOperatorOnObject() { + assertTrue(bool("'a' in {a:1}")) + } + + @Test + fun inOperatorOnObjectMissing() { + assertFalse(bool("'z' in {a:1}")) + } + + @Test + fun inOperatorOnArray() { + assertTrue(bool("0 in [10,20]")) + } + + @Test + fun logicalAndRightWhenLeftTruthy() { + assertEquals(2.0, num("1&&2")) + } + + @Test + fun logicalAndLeftWhenLeftFalsy() { + assertEquals(0.0, num("0&&2")) + } + + @Test + fun logicalOrLeftWhenTruthy() { + assertEquals(1.0, num("1||2")) + } + + @Test + fun logicalOrRightWhenLeftFalsy() { + assertEquals(2.0, num("0||2")) + } + + @Test + fun logicalNot() { + assertTrue(bool("!false")) + } + + @Test + fun logicalNotOnZero() { + assertTrue(bool("!0")) + } + + @Test + fun logicalNotOnEmptyString() { + assertTrue(bool("!''")) + } + + @Test + fun logicalNotOnNonEmptyString() { + assertFalse(bool("!'x'")) + } + + @Test + fun ternaryTrueBranch() { + assertEquals(1.0, num("true?1:2")) + } + + @Test + fun ternaryFalseBranch() { + assertEquals(2.0, num("false?1:2")) + } + + @Test + fun nestedTernary() { + // 1>2 ? 'a' : 3>2 ? 'b' : 'c' => 'b' + assertEquals("b", str("1>2?'a':3>2?'b':'c'")) + } + + @Test + fun varDeclarationAndRead() { + assertEquals(10.0, num("var x=10; x")) + } + + @Test + fun letDeclaration() { + assertEquals("hello", str("let s='hello'; s")) + } + + @Test + fun constDeclaration() { + assertApprox(3.14, num("const PI=3.14; PI")) + } + + @Test + fun multiVarDeclaration() { + assertEquals(3.0, num("var a=1, b=2; a+b")) + } + + @Test + fun assignmentPlusEquals() { + assertEquals(15.0, num("var x=10; x+=5; x")) + } + + @Test + fun assignmentMinusEquals() { + assertEquals(5.0, num("var x=10; x-=5; x")) + } + + @Test + fun assignmentTimesEquals() { + assertEquals(20.0, num("var x=4; x*=5; x")) + } + + @Test + fun assignmentDivideEquals() { + assertEquals(5.0, num("var x=10; x/=2; x")) + } + + @Test + fun assignmentModuloEquals() { + assertEquals(1.0, num("var x=7; x%=3; x")) + } + + @Test + fun prefixIncrement() { + assertEquals(6.0, num("var x=5; ++x")) + } + + @Test + fun postfixIncrementReturnsOldValue() { + assertEquals(5.0, num("var x=5; x++")) + } + + @Test + fun postfixIncrementMutatesVariable() { + assertEquals(6.0, num("var x=5; x++; x")) + } + + @Test + fun prefixDecrement() { + assertEquals(4.0, num("var x=5; --x")) + } + + @Test + fun postfixDecrement() { + assertEquals(5.0, num("var x=5; x--")) + } + + @Test + fun postfixDecrementMutates() { + assertEquals(4.0, num("var x=5; x--; x")) + } + + @Test + fun commaOperatorReturnsLast() { + // Sequence/comma expression: (1, 2, 3) => 3 + assertEquals(3.0, num("(1,2,3)")) + } + + @Test + fun typeofNumber() { + assertEquals("number", str("typeof 42")) + } + + @Test + fun typeofString() { + assertEquals("string", str("typeof 'x'")) + } + + @Test + fun typeofBoolean() { + assertEquals("boolean", str("typeof true")) + } + + @Test + fun typeofUndefined() { + assertEquals("undefined", str("typeof undefined")) + } + + @Test + fun typeofFunction() { + assertEquals("function", str("typeof function(){}")) + } + + @Test + fun typeofNull() { + // typeof null === "object" in JS + assertEquals("object", str("typeof null")) + } + + @Test + fun typeofObject() { + assertEquals("object", str("typeof {}")) + } + + @Test + fun typeofArray() { + assertEquals("object", str("typeof []")) + } + + @Test + fun typeofUndeclaredVariable() { + // typeof on an undeclared variable should not throw, returns "undefined" + assertEquals("undefined", str("typeof neverDeclaredXyz")) + } + + @Test + fun voidOperator() { + assertEquals(Unit, evalJsInternal("void 0")) + } + + @Test + fun voidOperatorOnExpression() { + assertEquals(Unit, evalJsInternal("void (1+2)")) + } + + @Test + fun stringConcatenation() { + assertEquals("ab", str("'a'+'b'")) + } + + @Test + fun numberPlusStringCoercesToString() { + assertEquals("1x", str("1+'x'")) + } + + @Test + fun stringLength() { + assertEquals(5.0, num("'hello'.length")) + } + + @Test + fun stringCharAt() { + assertEquals("e", str("'hello'.charAt(1)")) + } + + @Test + fun stringCharAtOutOfRange() { + assertEquals("", str("'hello'.charAt(99)")) + } + + @Test + fun stringCharCodeAt() { + assertEquals(104.0, num("'hello'.charCodeAt(0)")) + } + + @Test + fun stringCodePointAt() { + assertEquals(104.0, num("'hello'.codePointAt(0)")) + } + + @Test + fun stringBracketIndexing() { + // 'hello'[1] === 'e' + assertEquals("e", str("'hello'[1]")) + } + + @Test + fun stringBracketIndexingFirst() { + assertEquals("h", str("'hello'[0]")) + } + + @Test + fun stringIndexOfFound() { + assertEquals(1.0, num("'hello'.indexOf('e')")) + } + + @Test + fun stringIndexOfNotFound() { + assertEquals(-1.0, num("'hello'.indexOf('z')")) + } + + @Test + fun stringLastIndexOf() { + assertEquals(3.0, num("'abcabc'.lastIndexOf('a')")) + } + + @Test + fun stringLastIndexOfNotFound() { + assertEquals(-1.0, num("'hello'.lastIndexOf('z')")) + } + + @Test + fun stringSlice() { + assertEquals("ell", str("'hello'.slice(1,4)")) + } + + @Test + fun stringSliceNegativeIndex() { + assertEquals("lo", str("'hello'.slice(-2)")) + } + + @Test + fun stringSliceNegativeEnd() { + assertEquals("hel", str("'hello'.slice(0,-2)")) + } + + @Test + fun stringSubstr() { + assertEquals("ell", str("'hello'.substr(1,3)")) + } + + @Test + fun stringSubstring() { + assertEquals("ell", str("'hello'.substring(1,4)")) + } + + @Test + fun stringSubstringSwapsArgs() { + // substring swaps start/end if start > end + assertEquals("ell", str("'hello'.substring(4,1)")) + } + + @Test + fun stringSplitAndJoin() { + assertEquals("a-b-c", str("'a|b|c'.split('|').join('-')")) + } + + @Test + fun stringSplitEmptySepGivesChars() { + assertEquals("h,e,l,l,o", str("'hello'.split('').join(',')")) + } + + @Test + fun stringReplaceFirstOccurrence() { + assertEquals("xbc", str("'abc'.replace('a','x')")) + } + + @Test + fun stringReplaceAll() { + assertEquals("xbxbxb", str("'ababab'.replace('a','x').replace('a','x').replace('a','x')")) + } + + @Test + fun stringReplaceAllMethod() { + assertEquals("xbxbxb", str("'ababab'.replaceAll('a','x')")) + } + + @Test + fun stringToUpperCase() { + assertEquals("HELLO", str("'hello'.toUpperCase()")) + } + + @Test + fun stringToLowerCase() { + assertEquals("hello", str("'HELLO'.toLowerCase()")) + } + + @Test + fun stringTrim() { + assertEquals("hi", str("' hi '.trim()")) + } + + @Test + fun stringTrimStart() { + assertEquals("hi ", str("' hi '.trimStart()")) + } + + @Test + fun stringTrimEnd() { + assertEquals(" hi", str("' hi '.trimEnd()")) + } + + @Test + fun stringRepeat() { + assertEquals("aaa", str("'a'.repeat(3)")) + } + + @Test + fun stringRepeatZero() { + assertEquals("", str("'a'.repeat(0)")) + } + + @Test + fun stringPadStart() { + assertEquals("005", str("'5'.padStart(3,'0')")) + } + + @Test + fun stringPadEnd() { + assertEquals("500", str("'5'.padEnd(3,'0')")) + } + + @Test + fun stringPadStartNoOpWhenLongEnough() { + assertEquals("hello", str("'hello'.padStart(3,'0')")) + } + + @Test + fun stringIncludes() { + assertTrue(bool("'hello'.includes('ell')")) + } + + @Test + fun stringIncludesFalse() { + assertFalse(bool("'hello'.includes('xyz')")) + } + + @Test + fun stringStartsWith() { + assertTrue(bool("'hello'.startsWith('hel')")) + } + + @Test + fun stringEndsWith() { + assertTrue(bool("'hello'.endsWith('llo')")) + } + + @Test + fun stringFromCharCode() { + assertEquals("A", str("String.fromCharCode(65)")) + } + + @Test + fun stringFromCharCodeMultiple() { + assertEquals("Hi", str("String.fromCharCode(72,105)")) + } + + @Test + fun stringToString() { + assertEquals("hello", str("'hello'.toString()")) + } + + @Test + fun stringMatch() { + // match returns array of groups; [0] is the full match + assertEquals("ell", str("'hello'.match('ell')[0]")) + } + + @Test + fun stringMatchNoMatch() { + assertNull(evalJsInternal("'hello'.match('xyz')")) + } + + @Test + fun numberToStringRadix16() { + assertEquals("ff", str("(255).toString(16)")) + } + + @Test + fun numberToStringRadix2() { + assertEquals("1010", str("(10).toString(2)")) + } + + @Test + fun numberToFixed() { + assertEquals("3.14", str("(3.14159).toFixed(2)")) + } + + @Test + fun toFixedZeroDigits() { + assertEquals("4", str("(3.6).toFixed(0)")) + } + + @Test + fun toFixedTwoDigits() { + assertEquals("3.14", str("(3.14159).toFixed(2)")) + } + + @Test + fun toFixedPadsWithZeroes() { + assertEquals("3.10", str("(3.1).toFixed(2)")) + } + + @Test + fun toFixedNegativeNumber() { + assertEquals("-3.14", str("(-3.14159).toFixed(2)")) + } + + @Test + fun toFixedNegativeBetweenZeroAndMinusOne() { + assertEquals("-0.50", str("(-0.5).toFixed(2)")) + } + + @Test + fun toFixedWholeNumber() { + assertEquals("5.00", str("(5).toFixed(2)")) + } + + @Test + fun toFixedZeroValue() { + assertEquals("0.00", str("(0).toFixed(2)")) + } + + @Test + fun arrayLiteralAndLength() { + assertEquals(3.0, num("[1,2,3].length")) + } + + @Test + fun arrayIndexAccess() { + assertEquals(2.0, num("[1,2,3][1]")) + } + + @Test + fun arrayJoin() { + assertEquals("1,2,3", str("[1,2,3].join(',')")) + } + + @Test + fun arrayJoinDefaultSep() { + assertEquals("1,2,3", str("[1,2,3].join()")) + } + + @Test + fun arrayReverse() { + assertEquals("3,2,1", str("[1,2,3].reverse().join(',')")) + } + + @Test + fun arrayPushReturnsNewLength() { + assertEquals(4.0, num("var a=[1,2,3]; a.push(4)")) + } + + @Test + fun arrayPushMutates() { + assertEquals("1,2,3,4", str("var a=[1,2,3]; a.push(4); a.join(',')")) + } + + @Test + fun arrayPopRemovesLastElement() { + assertEquals(3.0, num("var a=[1,2,3]; a.pop()")) + } + + @Test + fun arrayPopMutates() { + assertEquals("1,2", str("var a=[1,2,3]; a.pop(); a.join(',')")) + } + + @Test + fun arrayShift() { + assertEquals(1.0, num("var a=[1,2,3]; a.shift()")) + } + + @Test + fun arrayShiftMutates() { + assertEquals("2,3", str("var a=[1,2,3]; a.shift(); a.join(',')")) + } + + @Test + fun arrayUnshift() { + assertEquals(4.0, num("var a=[2,3,4]; a.unshift(1)")) + } + + @Test + fun arrayUnshiftMutates() { + assertEquals("1,2,3,4", str("var a=[2,3,4]; a.unshift(1); a.join(',')")) + } + + @Test + fun arraySlice() { + assertEquals("2,3", str("[1,2,3,4].slice(1,3).join(',')")) + } + + @Test + fun arraySliceNegative() { + assertEquals("3,4", str("[1,2,3,4].slice(-2).join(',')")) + } + + @Test + fun arraySpliceRemove() { + assertEquals("2,3", str("var a=[1,2,3,4]; a.splice(1,2).join(',')")) + } + + @Test + fun arraySpliceMutates() { + assertEquals("1,4", str("var a=[1,2,3,4]; a.splice(1,2); a.join(',')")) + } + + @Test + fun arraySpliceInsert() { + assertEquals("1,9,8,4", str("var a=[1,2,3,4]; a.splice(1,2,9,8); a.join(',')")) + } + + @Test + fun arrayMap() { + assertEquals("2,4,6", str("[1,2,3].map(function(x){return x*2}).join(',')")) + } + + @Test + fun arrayFilter() { + assertEquals("2,4", str("[1,2,3,4].filter(function(x){return x%2===0}).join(',')")) + } + + @Test + fun arrayReduce() { + assertEquals(10.0, num("[1,2,3,4].reduce(function(acc,x){return acc+x},0)")) + } + + @Test + fun arrayReduceNoInitial() { + assertEquals(10.0, num("[1,2,3,4].reduce(function(acc,x){return acc+x})")) + } + + @Test + fun arrayForEachSideEffect() { + assertEquals(6.0, num("var s=0; [1,2,3].forEach(function(x){s+=x}); s")) + } + + @Test + fun arrayFind() { + assertEquals(3.0, num("[1,2,3,4].find(function(x){return x>2})")) + } + + @Test + fun arrayFindNotFound() { + assertEquals(Unit, evalJsInternal("[1,2,3].find(function(x){return x>9})")) + } + + @Test + fun arrayIndexOf() { + assertEquals(2.0, num("[10,20,30].indexOf(30)")) + } + + @Test + fun arrayIndexOfNotFound() { + assertEquals(-1.0, num("[10,20,30].indexOf(99)")) + } + + @Test + fun arrayIncludes() { + assertTrue(bool("[1,2,3].includes(2)")) + } + + @Test + fun arrayIncludesFalse() { + assertFalse(bool("[1,2,3].includes(9)")) + } + + @Test + fun arrayConcat() { + assertEquals("1,2,3,4", str("[1,2].concat([3,4]).join(',')")) + } + + @Test + fun arraySome() { + assertTrue(bool("[1,2,3].some(function(x){return x>2})")) + } + + @Test + fun arraySomeFalse() { + assertFalse(bool("[1,2,3].some(function(x){return x>9})")) + } + + @Test + fun arrayEvery() { + assertFalse(bool("[1,2,3].every(function(x){return x>2})")) + } + + @Test + fun arrayEveryTrue() { + assertTrue(bool("[3,4,5].every(function(x){return x>2})")) + } + + @Test + fun arraySortDefault() { + // Default sort is lexicographic: [10,9,2] => [10,2,9] + assertEquals("10,2,9", str("[10,9,2].sort().join(',')")) + } + + @Test + fun arraySortWithComparator() { + assertEquals("1,2,10", str("[10,1,2].sort(function(a,b){return a-b}).join(',')")) + } + + @Test + fun arrayFlat() { + assertEquals("1,2,3,4", str("[[1,2],[3,4]].flat().join(',')")) + } + + @Test + fun arrayToString() { + assertEquals("1,2,3", str("[1,2,3].toString()")) + } + + @Test + fun newArrayWithSize() { + assertEquals(5.0, num("new Array(5).length")) + } + + @Test + fun objectPropertyAccessWithDot() { + assertEquals(1.0, num("var o={a:1}; o.a")) + } + + @Test + fun objectPropertyAccessWithBracket() { + assertEquals(2.0, num("var o={b:2}; o['b']")) + } + + @Test + fun objectPropertyAssignment() { + assertEquals(99.0, num("var o={}; o.x=99; o.x")) + } + + @Test + fun objectKeys() { + assertEquals("a,b", str("Object.keys({a:1,b:2}).join(',')")) + } + + @Test + fun objectValues() { + assertEquals("1,2", str("Object.values({a:1,b:2}).join(',')")) + } + + @Test + fun objectToStringCoercion() { + // ({}) + "" => "[object Object]" + assertEquals("[object Object]", str("({})+''")) + + // []+{} => "[object Object]" + assertEquals("[object Object]", str("[]+{}")) + } + + @Test + fun ifTrueBranch() { + assertEquals(1.0, num("var r=0; if(true){r=1} r")) + } + + @Test + fun ifFalseUsesElse() { + assertEquals(2.0, num("var r=0; if(false){r=1}else{r=2} r")) + } + + @Test + fun ifElseIfChain() { + assertEquals(2.0, num("var x=5; var r=0; if(x<3){r=1}else if(x<7){r=2}else{r=3} r")) + } + + @Test + fun whileLoop() { + assertEquals(10.0, num("var i=0; while(i<10){i++} i")) + } + + @Test + fun whileBreak() { + assertEquals(5.0, num("var i=0; while(true){if(i===5)break; i++} i")) + } + + @Test + fun whileContinue() { + assertEquals(25.0, num("var i=0; var s=0; while(i<10){i++; if(i%2===0)continue; s+=i} s")) + } + + @Test + fun forLoop() { + assertEquals(10.0, num("var s=0; for(var i=1;i<=4;i++){s+=i} s")) + } + + @Test + fun forLoopWithBreak() { + assertEquals(3.0, num("var i; for(i=0;i<10;i++){if(i===3)break} i")) + } + + @Test + fun forLoopNoInitTestUpdate() { + // All three parts optional; behaves like while(true) with internal break + assertEquals(3.0, num("var i=0; for(;;){if(i>=3)break; i++} i")) + } + + @Test + fun forInOverObjectKeys() { + assertEquals("a,b,c", str("var o={a:1,b:2,c:3}; var keys=[]; for(var k in o){keys.push(k)} keys.sort().join(',')")) + } + + @Test + fun forInOverArrayGivesIndices() { + assertEquals("0,1,2", str("var a=[10,20,30]; var idx=[]; for(var i in a){idx.push(i)} idx.join(',')")) + } + + @Test + fun namedFunctionDeclarationAndCall() { + assertEquals(7.0, num("function add(a,b){return a+b} add(3,4)")) + } + + @Test + fun anonymousFunctionExpression() { + assertEquals(12.0, num("var mul=function(a,b){return a*b}; mul(3,4)")) + } + + @Test + fun recursiveFunction() { + assertEquals(120.0, num("function fact(n){if(n<=1)return 1; return n*fact(n-1)} fact(5)")) + } + + @Test + fun closureCapturesOuterVariable() { + assertEquals(3.0, num("var c=0; function inc(){c+=1} inc();inc();inc(); c")) + } + + @Test + fun immediatelyInvokedFunctionExpression() { + assertEquals(9.0, num("(function(x){return x*x})(3)")) + } + + @Test + fun functionAsArgument() { + assertEquals(6.0, num("function apply(f,x){return f(x)} apply(function(n){return n+1},5)")) + } + + @Test + fun nestedClosure() { + val code = """ + function makeAdder(n) { + return function(x) { return x + n; } + } + var add5 = makeAdder(5); + add5(3) + """.trimIndent() + assertEquals(8.0, num(code)) + } + + @Test + fun closureCounterFactory() { + val code = """ + function makeCounter() { + var count = 0; + return function() { count += 1; return count; } + } + var c = makeCounter(); + c(); c(); c() + """.trimIndent() + assertEquals(3.0, num(code)) + } + + @Test + fun argumentsObject() { + val code = """ + function sum() { + var total = 0; + for(var i=0; i= 0.0 && r < 1.0, "Math.random() should be in [0,1) but was $r") + } + + @Test + fun mathRandomProducesDifferentValues() { + val results = (1..20).map { num("Math.random()") }.toSet() + assertTrue(results.size > 1, "Math.random() produced identical values across 20 calls") + } + + @Test + fun parseIntDecimal() { + assertEquals(42.0, num("parseInt('42')")) + } + + @Test + fun parseIntHex() { + assertEquals(255.0, num("parseInt('ff',16)")) + } + + @Test + fun parseIntBinary() { + assertEquals(5.0, num("parseInt('101',2)")) + } + + @Test + fun parseIntInvalid() { + assertTrue(num("parseInt('abc')").isNaN()) + } + + @Test + fun parseIntLeadingWhitespace() { + assertEquals(42.0, num("parseInt(' 42 ')")) + } + + @Test + fun parseFloat() { + assertApprox(3.14, num("parseFloat('3.14')")) + } + + @Test + fun isNanTrue() { + assertTrue(bool("isNaN(NaN)")) + } + + @Test + fun isNanFalse() { + assertFalse(bool("isNaN(1)")) + } + + @Test + fun isFiniteFalse() { + assertFalse(bool("isFinite(Infinity)")) + } + + @Test + fun isFiniteTrue() { + assertTrue(bool("isFinite(1)")) + } + + @Test + fun consoleLogDoesNotThrow() { + // console.log is a no-op; just ensure it runs without exception + assertEquals(Unit, evalJsInternal("console.log('test', 1, 2)")) + } + + @Test + fun decodeURIComponentBasic() { + assertEquals("hello world", str("decodeURIComponent('hello%20world')")) + } + + @Test + fun encodeURIComponentBasic() { + assertTrue(str("encodeURIComponent('hello world')").contains("%")) + } + + @Test + fun tryCatchSwallowsThrownValue() { + assertEquals(42.0, num("var r=0; try{throw 42}catch(e){r=e} r")) + } + + @Test + fun finallyAlwaysRuns() { + assertEquals(99.0, num("var r=0; try{throw 1}catch(e){}finally{r=99} r")) + } + + @Test + fun tryWithoutThrowSkipsCatch() { + assertEquals(1.0, num("var r=0; try{r=1}catch(e){r=99} r")) + } + + @Test + fun tryCatchThrowString() { + assertEquals("oops", str("var r=''; try{throw 'oops'}catch(e){r=e} r")) + } + + @Test + fun tryCatchThrowObject() { + assertEquals(404.0, num("var r=0; try{throw {code:404}}catch(e){r=e.code} r")) + } + + @Test + fun evaluateMathSimpleAddition() { + assertEquals("5", jsValueToString(evalJsInternal("eval(2+3)"))) + } + + @Test + fun evaluateMathNestedParens() { + assertEquals("12", jsValueToString(evalJsInternal("eval((2+4)*2)"))) + } + + @Test + fun evaluateMathProducesCharCode() { + val code = "eval(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)" + assertEquals(65.0, (evalJsInternal(code) as? Double) ?: 0.0) + } + + @Test + fun evalJsWithVariableReturnsNamedVar() { + assertEquals(42.0, num("var x = 42", "x")) + } + + @Test + fun evalJsWithVariableAfterComputation() { + assertEquals(7.0, num("var x = 1 + 2 * 3", "x")) + } + + @Test + fun evalJsWithVariableStringValue() { + assertEquals("https://example.com", str("var url = 'https://example.com'", "url")) + } + + @Test + fun evalJsWithVariableNullValue() { + assertNull(evalJsInternal("var x = null", "x")) + } + + @Test + fun evalJsWithVariableReturnsNullForUndefined() { + assertNull(evalJsInternal("var x = 42", "y")) + } + + @Test + fun evalJsWithVariableUnitWhenNoVariable() { + assertEquals(Unit, evalJsInternal("var x = 42")) + } + + @Test + fun evalJsWithVariableAfterMultipleStatements() { + assertEquals(15.0, num("var x = 0; for(var i=1;i<=5;i++){x+=i}", "x")) + } + + @Test + fun emptyArrayPlusEmptyArrayIsEmptyString() { + // [] + [] => "" + assertEquals("", str("[]+[]")) + } + + @Test + fun unaryPlusEmptyArrayIsZero() { + // +[] => 0 + assertEquals(0.0, num("+[]")) + } + + @Test + fun notArrayIsFalse() { + // ![] => false (array is truthy, so ![] is false) + assertFalse(bool("![]")) + } + + @Test + fun doubleNotArrayIsTrue() { + // !![] => true + assertTrue(bool("!![]")) + } + + @Test + fun unaryPlusDoubleNotArrayIsOne() { + // +!![] => 1 + assertEquals(1.0, num("+!![]")) + } + + @Test + fun unaryPlusNotArrayIsZero() { + // +![] => 0 + assertEquals(0.0, num("+![]")) + } + + @Test + fun falseCoercedToString() { + // ![]+[] => "false" + assertEquals("false", str("![]+[]")) + } + + @Test + fun trueCoercedToString() { + // !![]+[] => "true" + assertEquals("true", str("!![]+[]")) + } + + @Test + fun falseStringViaStringConcat() { + // (![]+""): false + "" => "false" + assertEquals("false", str("![]+''")) + } + + @Test + fun trueStringViaStringConcat() { + // (!![]+""): true + "" => "true" + assertEquals("true", str("!![]+''")) + } + + @Test + fun undefinedCoercedToString() { + assertEquals("undefined", str("[][0]+[]")) + } + + @Test + fun charF() { + // (![]+[])[0] => "false"[0] => "f" + assertEquals("f", str("(![]+[])[0]")) + } + + @Test + fun charA() { + // (![]+[])[1] => "false"[1] => "a" + assertEquals("a", str("(![]+[])[1]")) + } + + @Test + fun charL() { + // (![]+[])[2] => "false"[2] => "l" + assertEquals("l", str("(![]+[])[2]")) + } + + @Test + fun charS() { + // (![]+[])[3] => "false"[3] => "s" + assertEquals("s", str("(![]+[])[3]")) + } + + @Test + fun charE() { + // (![]+[])[4] => "false"[4] => "e" + assertEquals("e", str("(![]+[])[4]")) + } + + @Test + fun charT() { + // (!![]+[])[0] => "true"[0] => "t" + assertEquals("t", str("(!![]+[])[0]")) + } + + @Test + fun charR() { + // (!![]+[])[1] => "true"[1] => "r" + assertEquals("r", str("(!![]+[])[1]")) + } + + @Test + fun charU() { + // (!![]+[])[2] => "true"[2] => "u" + assertEquals("u", str("(!![]+[])[2]")) + } + + @Test + fun indexViaArithmetic() { + // (![]+[])[+[]] => "false"[0] => "f" (index built from +[]) + assertEquals("f", str("(![]+[])[+[]]")) + } + + @Test + fun indexOneViaArithmetic() { + // (![]+[])[+!![]] => "false"[1] => "a" + assertEquals("a", str("(![]+[])[+!![]]")) + } + + @Test + fun arrayToStringCoercion() { + // [1,2,3]+[] => "1,2,3" + assertEquals("1,2,3", str("[1,2,3]+[]")) + } + + @Test + fun objectStringCharO() { + // ([]+{})[1] => "[object Object]"[1] => "o" + assertEquals("o", str("([]+{})[1]")) + } + + @Test + fun objectStringCharB() { + // ([]+{})[2] => "[object Object]"[2] => "b" + assertEquals("b", str("([]+{})[2]")) + } + + @Test + fun filterFunctionToString() { + // []["filter"]+"" => "function filter() { [native code] }" + assertEquals("function filter() { [native code] }", str("[]['filter']+''")) + } + + @Test + fun filterStringCharF() { + // ([]["filter"]+[])[0] => "function filter() { [native code] }"[0] => "f" + assertEquals("f", str("([]['filter']+[])[0]")) + } + + @Test + fun filterStringCharU() { + // ([]["filter"]+[])[1] => "u" + assertEquals("u", str("([]['filter']+[])[1]")) + } + + @Test + fun filterStringCharN() { + // ([]["filter"]+[])[2] => "n" + assertEquals("n", str("([]['filter']+[])[2]")) + } + + @Test + fun filterStringCharC() { + // ([]["filter"]+[])[3] => "c" + assertEquals("c", str("([]['filter']+[])[3]")) + } + + @Test + fun filterStringCharI() { + assertEquals("i", str("([]['filter']+[])[5]")) + } + + @Test + fun nativeCodeBracketChar() { + // "function filter() { [native code] }" contains '[' at index 20 + val s = "function filter() { [native code] }" + val idx = s.indexOf('[') + assertEquals("[", str("([]['filter']+[])[$idx]")) + } + + @Test + fun nativeCodeSpaceChar() { + // space at index 8 + assertEquals(" ", str("([]['filter']+[])[8]")) + } + + @Test + fun buildsNumberViaAddition() { + // +!![] + +!![] + +!![] => 3 + assertEquals(3.0, num("+!![]+!![]+!![]")) + } + + @Test + fun buildsNumberTen() { + assertEquals("10", str("(+!![])+[+[]]")) + } + + @Test + fun stringFromCharCodeViaNativeExtraction() { + assertEquals("A", str("String['fromCharCode'](65)")) + } + + @Test + fun fullAlphaFromFalseTrue() { + assertEquals("ftaseru", str(""" + var f = ![]+[]; + var t = !![]+[]; + f[0]+t[0]+f[1]+f[3]+f[4]+t[1]+t[2] + """.trimIndent())) + } + + @Test + fun functionToStringContainsNativeCode() { + // Any array method coerced to string should contain "native code" + assertTrue(str("[]['map']+''").contains("native code")) + } + + @Test + fun functionToStringContainsFunctionKeyword() { + assertTrue(str("[]['join']+''").startsWith("function")) + } + + @Test + fun typeofCoercion() { + // typeof([]) + [] => "object" + assertEquals("object", str("typeof([])+[]")) + } + + @Test + fun typeofFunctionCoercion() { + // typeof([]['filter']) => "function" + assertEquals("function", str("typeof([]['filter'])")) + } + + @Test + fun hexEncodedStringDecoding() { + val code = """ + var encoded = '48|65|6c|6c|6f'; + var decoded = encoded.split('|').map(function(h){ + return String.fromCharCode(parseInt(h, 16)); + }).join(''); + decoded + """.trimIndent() + assertEquals("Hello", str(code)) + } + + @Test + fun charCodeArrayToString() { + val code = """ + var codes = [72, 101, 108, 108, 111]; + var s = ''; + for(var i=0; i 0) { + r = alpha[n % 16] + r; + n = Math.floor(n / 16); + } + return r || '0'; + } + toBase16(255) + """.trimIndent() + assertEquals("ff", str(code)) + } + + @Test + fun xorDeobfuscation() { + val code = """ + var _0x1 = function(s) { + return s.split('').map(function(c) { + return String.fromCharCode(c.charCodeAt(0) ^ 1); + }).join(''); + }; + _0x1('ifmmp') + """.trimIndent() + val expected = "ifmmp".map { (it.code xor 1).toChar() }.joinToString("") + assertEquals(expected, str(code)) + } + + @Test + fun symtabLookupPattern() { + val code = """ + var symtab = ['hello', '', 'world', 'foo']; + var tokens = '0 2'.split(' '); + var result = tokens.map(function(w){ + var idx = parseInt(w,10); + var v = symtab[idx]; + return (v !== undefined && v !== '') ? v : w; + }).join(' '); + result + """.trimIndent() + assertEquals("hello world", str(code)) + } + + @Test + fun hunterDecoderDufHelper() { + val code = """ + function duf(d, e) { + var str = '0123456789abcdefghijklmnopqrstuvwxyz'; + var h = str.substring(0, e); + var j = 0.0; + var rev = d.split('').reverse().join(''); + for(var c=0; c= 0) j += idx * Math.pow(e, c); + } + return Math.floor(j); + } + duf('z', 36) + """.trimIndent() + assertEquals(35.0, num(code)) + } + + @Test + fun chainedStringMethods() { + assertEquals("OLLEH", str("'hello'.split('').reverse().join('').toUpperCase()")) + } + + @Test + fun deeplyNestedArithmetic() { + assertEquals(39.0, num("((((1+1)*3)+((2*3)+1))*3)")) + } + + @Test + fun closureOverLoopVariable() { + val code = """ + var fns = []; + for(var i=0; i<3; i++){ + (function(j){ fns.push(function(){return j;}); })(i); + } + fns[0]()+fns[1]()+fns[2]() + """.trimIndent() + assertEquals(3.0, num(code)) + } + + @Test + fun multilineStringConcatenation() { + val code = """ + var a = 'foo'; + var b = 'bar'; + var c = a + b; + c + """.trimIndent() + assertEquals("foobar", str(code)) + } + + @Test + fun bitwiseTruncationPattern() { + assertEquals(5.0, num("(11/2)|0")) + } + + @Test + fun infiniteLoopIsAbortedByExecutionBudget() { + assertEquals(Unit, evalJsInternal("while(true){}")) + } + + @Test + fun infiniteLoopIsAbortedByTimeBudget() { + val mark = TimeSource.Monotonic.markNow() + val result = evalJsInternal("while(true){}", maxExecutionTime = 200.milliseconds) + assertEquals(Unit, result) + // Generous upper bound just to avoid flakiness on slow machines. + assertTrue(mark.elapsedNow() < 2.seconds) + } + + @Test + fun infiniteLoopIsAbortedByTinyInstructionBudget() { + // A tiny instruction cap but a generous time budget, the instruction count is what + // should abort this, not the clock. + assertEquals(Unit, evalJsInternal("while(true){}", maxExecutionTime = 60.seconds, maxInstructions = 1000)) + } + + @Test + fun finiteLoopCompletesNormally() { + assertEquals(45.0, num("var s=0; for (var i=0;i<10;i++){ s+=i; }", "s")) + } + + @Test + fun exponentiation() { + assertEquals(1024.0, num("2**10")) + } + + @Test + fun exponentiationIsRightAssociative() { + // 2 ** (3 ** 2) == 2 ** 9 == 512, NOT (2 ** 3) ** 2 == 64 + assertEquals(512.0, num("2**3**2")) + } + + @Test + fun exponentiationWithNegativeExponent() { + assertEquals(0.5, num("2**-1")) + } + + @Test + fun exponentiationOverflowsToInfinity() { + assertTrue(num("10**1000").isInfinite()) + } + + @Test + fun assignmentPowEquals() { + assertEquals(8.0, num("var x=2; x**=3", "x")) + } + + @Test + fun legacyOctalLiteral() { + // 010 (octal) == 8 + assertEquals(8.0, num("010")) + } + + @Test + fun legacyOctalLiteralArithmetic() { + // 010 (octal, 8) - 03 (octal, 3) == 5 + assertEquals(5.0, num("010 - 03")) + } + + @Test + fun octalLikeLiteralWithNonOctalDigitIsDecimal() { + // "08" contains a non-octal digit (8), so JS treats it as plain decimal 8. + assertEquals(8.0, num("08")) + } + + @Test + fun arrayElisionsCountTowardLength() { + assertEquals(3.0, num("[,,,].length")) + } + + @Test + fun arrayTrailingCommaDoesNotAddElement() { + assertEquals(2.0, num("[1,2,].length")) + } + + @Test + fun arrayTrailingElisionAfterCommaAddsAHole() { + assertEquals(3.0, num("[1,2,,].length")) + } + + @Test + fun arrayHoleJoinsAsEmptyString() { + // Array.prototype.join treats holes/undefined/null as "", not the literal "undefined". + assertEquals("1,,3", str("[1,,3].join(',')")) + } + + @Test + fun parseIntStopsAtExponentNotation() { + // 0.0000005 stringifies to "5e-7"; parseInt reads the leading "5" and stops at "e". + assertEquals(5.0, num("parseInt(0.0000005)")) + } + + @Test + fun parseIntWithExplicitRadix() { + assertEquals(255.0, num("parseInt('ff', 16)")) + } + + @Test + fun parseIntStopsAtFirstNonDigit() { + assertEquals(42.0, num("parseInt('42px')")) + } + + @Test + fun parseIntInvalidInputIsNaN() { + assertTrue(num("parseInt('xyz')").isNaN()) + } + + @Test + fun emptyStringCoercesToZero() { + assertEquals(0.0, num("\"\" - 0")) + } + + @Test + fun emptyStringMinusNegatedEmptyStringIsZero() { + assertEquals(0.0, evalJsInternal("\"\" - - \"\"")) + } + + @Test + fun emptyStringMinusNumber() { + assertEquals(-1.0, evalJsInternal("\"\" - 1")) + } + + @Test + fun nullCoercesToZeroNotNaN() { + assertEquals(1.0, num("null + 1")) + } + + @Test + fun undefinedCoercesToNaNNotZero() { + // Unlike null, undefined coerces to NaN, not 0. + assertTrue(num("undefined + 1").isNaN()) + } + + @Test + fun postfixIncrementOnNonLvalueFailsGracefully() { + assertEquals(2.0, evalJsInternal("true+1")) + // `true++` has no valid assignment target (a SyntaxError in real JS); evalJs falls + // back to Unit instead of returning a bogus number. + assertEquals(Unit, evalJsInternal("true++")) + } + + @Test + fun booleanAdditionCoercesToNumber() { + assertEquals(1.0, num("true + false")) + } + + @Test + fun arrayPlusArrayConcatenatesAsStrings() { + assertEquals("1,2,34,5,6", str("[1, 2, 3] + [4, 5, 6]")) + } + + @Test + fun commaOperatorWithTwoOperands() { + assertEquals(2.0, num("10,2")) + } + + @Test + fun doubleNegationOfEmptyStringIsFalse() { + assertFalse(bool("!!\"\"")) + } + + @Test + fun looseEqualityBooleanVsNonNumericStringIsFalse() { + assertFalse(bool("true == \"true\"")) + } + + @Test + fun nullPlusZeroIsZero() { + assertEquals(0.0, num("null + 0")) + } + + @Test + fun zeroDividedByZeroIsNaN() { + assertTrue(num("0/0").isNaN()) + } + + @Test + fun exponentiationInfinityComparisonIsFalse() { + assertFalse(bool("1/0 > 10 ** 1000")) + } + + @Test + fun nullMinusZeroThenStringConcat() { + assertEquals("00", str("(null - 0) + \"0\"")) + } + + @Test + fun nonNumericStringMinusNumberThenAddBoolean() { + assertTrue(num("true + (\"true\" - 0) ").isNaN()) + } + + @Test + fun negatedTruthyNumbersSumToZero() { + assertEquals(0.0, num("!5 + !5")) + } + + @Test + fun numberAdditionThenStringConcatenation() { + assertEquals("33", str("1 + 2 + \"3\"")) + } + + @Test + fun typeofNaNIsNumber() { + assertEquals("number", str("typeof NaN")) + } + + @Test + fun undefinedPlusFalseIsNaN() { + assertTrue(num("undefined + false").isNaN()) + } + + @Test + fun logicalAndShortCircuitsOnFalsyEmptyString() { + assertEquals("", str("\"\" && -0")) + } + + @Test + fun combinedCoercionOfNaNEmptyStringAndArrayHoleIsZero() { + assertEquals(0.0, evalJsInternal("+!!NaN * \"\" - - [,]")) + } + + /** Returns a [CoroutineScope] backed by a plain [Job] with no dispatcher attached. */ + private fun activeScope(): CoroutineScope = CoroutineScope(Job()) + + @Test + fun scopeEvalJsFiniteScriptReturnsCorrectResult() { + // Normal script with an active scope should behave identically to plain evalJs. + val scope = activeScope() + val result = evalJsInternal("var s=0; for(var i=1;i<=10;i++){s+=i}", "s", scope = scope) + assertEquals(55.0, result as? Double ?: 0.0) + scope.cancel() + } + + @Test + fun scopeEvalJsStringResultWithActiveScope() { + val scope = activeScope() + val result = jsValueToString(evalJsInternal("'hello'.split('').reverse().join('')", scope = scope)) + assertEquals("olleh", result) + scope.cancel() + } + + @Test + fun scopeEvalJsVariableLookupWithActiveScope() { + val scope = activeScope() + val result = evalJsInternal("var x = 21 * 2", "x", scope = scope) + assertEquals(42.0, result as? Double ?: 0.0) + scope.cancel() + } + + @Test + fun scopeEvalJsCancelledBeforeCallThrowsJsCancellationException() { + // Cancel the scope before calling evalJs. The very first budget check sees + // isActive==false and throws JsCancellationException immediately. + val scope = activeScope() + scope.cancel() + assertFailsWith { + evalJsInternal("var x = 1 + 2", "x", scope = scope) + } + } + + @Test + fun scopeEvalJsInfiniteLoopAbortedWhenScopeCancelled() { + // Pre-cancel the scope and confirm an infinite loop aborts well within the + // time budget. A sub-1s return against a 5s budget proves it was + // scope cancellation, not the clock, that stopped the script. + val scope = activeScope() + scope.cancel() + val mark = TimeSource.Monotonic.markNow() + assertFailsWith { + evalJsInternal("while(true){}", scope = scope) + } + assertTrue( + mark.elapsedNow() < 1.seconds, + "Expected abort well before 5s time budget; elapsed: ${mark.elapsedNow()}", + ) + } + + @Test + fun scopeEvalJsInfiniteLoopAbortedByOwnBudgetEvenWithActiveScope() { + // Even with an active (never-cancelled) scope the internal budget still fires. + val scope = activeScope() + val mark = TimeSource.Monotonic.markNow() + val result = evalJsInternal("while(true){}", maxExecutionTime = 200.milliseconds, scope = scope) + assertEquals(Unit, result) + assertTrue(mark.elapsedNow() < 2.seconds) + scope.cancel() + } + + @Test + fun scopeEvalJsJsTryCatchCannotSwallowCancellation() { + // A JS try/catch must not be able to intercept the cancellation signal and keep + // the infinite loop alive. JsCancellationException extends CancellationException + // which the TryCatch node handler explicitly rethrows before catch(_: Exception). + val scope = activeScope() + scope.cancel() + val mark = TimeSource.Monotonic.markNow() + assertFailsWith { + evalJsInternal("while(true){ try{ throw 1; }catch(e){} }", scope = scope) + } + assertTrue( + mark.elapsedNow() < 1.seconds, + "JS try/catch appears to have swallowed the cancellation; elapsed: ${mark.elapsedNow()}", + ) + } + + @Test + fun scopeEvalJsCancelledScopeThrowsJsCancellationException() { + // A cancelled scope must propagate CancellationException so that withTimeout + // and structured concurrency see the cancellation correctly. Swallowing it + // silently as Unit would break withTimeout. + val scope = activeScope() + scope.cancel() + assertFailsWith { + evalJsInternal("1+1", scope = scope) + } + } + + @Test + fun suspendEvalJsWithTimeoutCancelsInfiniteLoop() = runTest { + /** + * activeScope() provides a real CoroutineScope with a real Job, so withTimeout + * fires after a genuine 300ms rather than instantly via the virtual clock. + * The test coroutine suspends at done.receive(), keeping runTest alive until + * the background coroutine finishes. + * + * We measure elapsed time inside the coroutine. If withTimeout fired before + * evalJs started, elapsed would be ~0ms. ~300ms proves evalJs was genuinely + * running and cancelled mid-execution, not trivially before it began. + */ + var elapsed = Duration.ZERO + val done = Channel() + activeScope().launch { + assertFailsWith { + withTimeout(300.milliseconds) { + val mark = TimeSource.Monotonic.markNow() + try { + evalJs("while(true){}") + } finally { + elapsed = mark.elapsedNow() + } + } + } + + done.send(Unit) + } + + done.receive() + assertTrue(elapsed > 200.milliseconds, "evalJs should have run for ~300ms but elapsed: $elapsed") + assertTrue(elapsed < 1.seconds, "evalJs ran too long: $elapsed") + } + + @Test + fun newJsContextWithNoInitializerIsUsable() = runTest { + val ctx = newJsContext() + assertEquals(3.0, ctx.eval("1+2") as? Double ?: 0.0) + } + + @Test + fun newJsContextInitializerRunsBeforeReturning() = runTest { + val ctx = newJsContext { + set("x", 10.0) + eval("var y = x + 1") + } + + assertEquals(11.0, ctx["y"] as? Double ?: 0.0) + } + + @Test + fun jsContextGetReturnsNullForNeverSetVariable() = runTest { + val ctx = newJsContext() + assertNull(ctx["neverSet"]) + } + + @Test + fun jsContextSetThenGetRoundTripsSameValue() = runTest { + val ctx = newJsContext() + ctx["greeting"] = "hello" + assertEquals("hello", ctx["greeting"]) + } + + @Test + fun jsContextSetOverwritesPreviousValue() = runTest { + val ctx = newJsContext() + ctx["x"] = 1.0 + ctx["x"] = 2.0 + assertEquals(2.0, ctx["x"] as? Double ?: 0.0) + } + + @Test + fun jsContextSetNullIsRetrievedAsNull() = runTest { + val ctx = newJsContext() + ctx["x"] = null + assertNull(ctx["x"]) + } + + @Test + fun jsContextGetAfterEvalSetsVariable() = runTest { + val ctx = newJsContext() + ctx.eval("var fromJs = 42") + assertEquals(42.0, ctx["fromJs"] as? Double ?: 0.0) + } + + @Test + fun jsContextPersistsVariablesAcrossManyEvalCalls() = runTest { + val ctx = newJsContext() + ctx.eval("var total = 0") + repeat(5) { ctx.eval("total += 1") } + assertEquals(5.0, ctx["total"] as? Double ?: 0.0) + } + + @Test + fun jsContextFunctionDeclaredInOneEvalUsableInAnother() = runTest { + val ctx = newJsContext() + ctx.eval("function double(n) { return n * 2; }") + val result = ctx.eval("double(21)") + assertEquals(42.0, result as? Double ?: 0.0) + } + + @Test + fun jsContextArrayMutatedAcrossEvalsPersists() = runTest { + val ctx = newJsContext() + ctx.eval("var arr = [1,2,3]") + ctx.eval("arr.push(4)") + assertEquals("1,2,3,4", ctx.eval("arr.join(',')") as? String) + } + + @Test + fun jsContextEvalReturnsLastStatementValue() = runTest { + val ctx = newJsContext() + assertEquals(9.0, ctx.eval("var a = 3; a * a") as? Double ?: 0.0) + } + + @Test + fun jsContextEvalReturnsUnitForDeclarationOnlyStatement() = runTest { + val ctx = newJsContext() + assertEquals(Unit, ctx.eval("var a = 3")) + } + + @Test + fun jsContextEvalReturnsStringValue() = runTest { + val ctx = newJsContext() + assertEquals("ab", ctx.eval("'a' + 'b'")) + } + + @Test + fun separateJsContextsDoNotShareVariables() = runTest { + val ctx1 = newJsContext { set("x", 1.0) } + val ctx2 = newJsContext { set("x", 2.0) } + assertEquals(1.0, ctx1["x"] as? Double ?: 0.0) + assertEquals(2.0, ctx2["x"] as? Double ?: 0.0) + } + + @Test + fun separateJsContextsDoNotShareFunctions() = runTest { + val ctx1 = newJsContext { eval("function f(){ return 1; }") } + val ctx2 = newJsContext() + assertEquals(1.0, ctx1.eval("f()") as? Double ?: 0.0) + // f was never declared in ctx2, so calling it should not crash and yields Unit. + assertEquals(Unit, ctx2.eval("typeof f === 'function' ? f() : undefined")) + } + + @Test + fun jsContextEvalDefaultBudgetHandlesNormalScript() = runTest { + val ctx = newJsContext() + val result = ctx.eval("var s=0; for(var i=1;i<=1000;i++){s+=i} s") + assertEquals(500500.0, result as? Double ?: 0.0) + } + + @Test + fun jsContextEvalInfiniteLoopAbortedByDefaultInstructionBudget() = runTest { + val ctx = newJsContext() + assertEquals(Unit, ctx.eval("while(true){}")) + } + + @Test + fun jsContextEvalInfiniteLoopAbortedByCustomTimeBudget() = runTest { + val ctx = newJsContext() + ctx.maxExecutionTime = 200.milliseconds + val mark = TimeSource.Monotonic.markNow() + val result = ctx.eval("while(true){}") + assertEquals(Unit, result) + assertTrue(mark.elapsedNow() < 2.seconds) + } + + @Test + fun jsContextEvalInfiniteLoopAbortedByTinyInstructionBudget() = runTest { + val ctx = newJsContext() + ctx.maxExecutionTime = 60.seconds + ctx.maxInstructions = 1000 + // High time budget; instruction cap should be what stops it. + val result = ctx.eval("while(true){}") + assertEquals(Unit, result) + } + + @Test + fun jsContextEvalCustomBudgetAppliesOnlyToThatCall() = runTest { + val ctx = newJsContext() + ctx.maxInstructions = 100 + // Abort this call + assertEquals(Unit, ctx.eval("while(true){}")) + // The next call, using the defaults again, runs a normal script fine. + val result = ctx.eval("1+1") + assertEquals(2.0, result as? Double ?: 0.0) + } + + @Test + fun jsContextEvalVariablesSurviveAnAbortedPriorCall() = runTest { + val ctx = newJsContext() + ctx.maxInstructions = 100 + ctx.eval("var x = 5") + ctx.eval("while(true){}") + assertEquals(5.0, ctx["x"] as? Double ?: 0.0) + } + + @Test + fun jsContextEvalCancelledWhenEnclosingCoroutineCancelledViaWithTimeout() = runTest { + val done = Channel() + var elapsed = Duration.ZERO + activeScope().launch { + assertFailsWith { + withTimeout(300.milliseconds) { + val ctx = newJsContext() + val mark = TimeSource.Monotonic.markNow() + try { + ctx.eval("while(true){}") + } finally { + elapsed = mark.elapsedNow() + } + } + } + + done.send(Unit) + } + + done.receive() + assertTrue(elapsed > 200.milliseconds, "expected genuine ~300ms run, was $elapsed") + assertTrue(elapsed < 1.seconds, "expected prompt cancellation, was $elapsed") + } + + @Test + fun jsContextEvalNotCancelledWhenWithTimeoutDoesNotExpire() = runTest { + val ctx = newJsContext() + val result = withTimeoutOrNull(5.seconds) { + ctx.eval("var s=0; for(var i=0;i<100;i++){s+=i} s") + } + + assertEquals(4950.0, result as? Double ?: 0.0) + } + + @Test + fun jsContextJsTryCatchCannotSwallowEnclosingCancellation() = runTest { + val done = Channel() + var elapsed = Duration.ZERO + activeScope().launch { + assertFailsWith { + withTimeout(300.milliseconds) { + val ctx = newJsContext() + val mark = TimeSource.Monotonic.markNow() + try { + ctx.eval("while(true){ try{ throw 1; }catch(e){} }") + } finally { + elapsed = mark.elapsedNow() + } + } + } + done.send(Unit) + } + + done.receive() + assertTrue(elapsed < 1.seconds, "JS try/catch appears to have swallowed cancellation; elapsed: $elapsed") + } + + @Test + fun jsContextUrlExtractionPattern() = runTest { + val ctx = newJsContext() + ctx.eval("var url = '/e/abc123?t=' + (1000+337) + '&s=xyz'") + assertEquals("/e/abc123?t=1337&s=xyz", ctx["url"]?.toString()) + } + + @Test + fun jsContextInitializerCanReadBackItsOwnEvalResult() = runTest { + var capturedDuringInit: Any? = null + newJsContext { + capturedDuringInit = eval("2 * 21") + } + + assertEquals(42.0, capturedDuringInit as? Double ?: 0.0) + } + + @Test + fun jsContextSequentialIndependentComputations() = runTest { + val ctx = newJsContext() + assertEquals(4.0, ctx.eval("2+2") as? Double ?: 0.0) + assertEquals("hi", ctx.eval("'h'+'i'") as? String) + assertFalse(ctx.eval("1 > 2") as? Boolean ?: true) + } +} From 59eaed0c82515d3adfd843cbc6f62f0760b539e0 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:08:53 +0000 Subject: [PATCH 034/105] Fix: Subsouce and cache issue (#3033) --- .../syncproviders/AccountManager.kt | 6 +- .../syncproviders/SubtitleRepo.kt | 8 +- .../syncproviders/providers/SubSource.kt | 223 ++++++++++-------- 3 files changed, 133 insertions(+), 104 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index 3bc5f2733..68fba9777 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -70,7 +70,8 @@ abstract class AccountManager { SubtitleRepo(openSubtitlesApi), SubtitleRepo(addic7ed), SubtitleRepo(subDlApi), - PlainAuthRepo(animeSkipApi) + PlainAuthRepo(animeSkipApi), + SubtitleRepo(subSourceApi) ) fun updateAccountIds() { @@ -120,7 +121,8 @@ abstract class AccountManager { val subtitleProviders = arrayOf( SubtitleRepo(openSubtitlesApi), SubtitleRepo(addic7ed), - SubtitleRepo(subDlApi) + SubtitleRepo(subDlApi), + SubtitleRepo(subSourceApi) ) val syncApis = arrayOf( SyncRepo(malApi), diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SubtitleRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SubtitleRepo.kt index 0b8c3e5ae..161001611 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SubtitleRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SubtitleRepo.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.syncproviders import androidx.annotation.WorkerThread import com.lagradost.cloudstream3.APIHolder.unixTime -import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch import com.lagradost.cloudstream3.subtitles.SubtitleResource @@ -14,7 +13,8 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) { data class SavedSearchResponse( val unixTime: Long, val response: List, - val query: SubtitleSearch + val query: SubtitleSearch, + val idPrefix: String, ) data class SavedResourceResponse( @@ -66,7 +66,7 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) { var found: List? = null for (item in searchCache) { // 120 min save - if (item.query == query && (unixTime - item.unixTime) < 60 * 120) { + if (item.idPrefix == idPrefix && item.query == query && (unixTime - item.unixTime) < 60 * 120) { found = item.response break } @@ -79,7 +79,7 @@ class SubtitleRepo(override val api: SubtitleAPI) : AuthRepo(api) { // only cache valid return values if (returnValue.isNotEmpty()) { - val add = SavedSearchResponse(unixTime, returnValue, query) + val add = SavedSearchResponse(unixTime, returnValue, query, idPrefix) searchCache.withLock { if (searchCache.size > CACHE_SIZE) { searchCache[searchCacheIndex] = add // rolling cache diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt index fb463ebee..317c3dc90 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SubSource.kt @@ -7,11 +7,10 @@ import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.subtitles.SubtitleResource import com.lagradost.cloudstream3.syncproviders.AuthData import com.lagradost.cloudstream3.syncproviders.SubtitleAPI -import com.lagradost.cloudstream3.utils.AppUtils.parseJson -import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.SubtitleHelper import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import java.util.concurrent.TimeUnit class SubSourceApi : SubtitleAPI() { override val name = "SubSource" @@ -20,77 +19,70 @@ class SubSourceApi : SubtitleAPI() { override val requiresLogin = false companion object { - const val APIURL = "https://api.subsource.net/api" - const val DOWNLOADENDPOINT = "https://api.subsource.net/api/downloadSub" + const val APIURL = "https://api.subsource.net/v1" } override suspend fun search( auth: AuthData?, query: AbstractSubtitleEntities.SubtitleSearch ): List? { - //Only supports Imdb Id search for now if (query.imdbId == null) return null val queryLang = SubtitleHelper.fromTagToEnglishLanguageName(query.lang) val type = if ((query.seasonNumber ?: 0) > 0) TvType.TvSeries else TvType.Movie - val searchRes = app.post( - url = "$APIURL/searchMovie", - data = mapOf( - "query" to query.imdbId!! - ) - ).parsedSafe() ?: return null + val searchResponse = app.post( + url = "$APIURL/movie/search", + json = mapOf( + "includeSeasons" to false, + "limit" to 15, + "query" to query.imdbId!!, + "signal" to "{}" + ), + cacheTime = 120, + cacheUnit = TimeUnit.MINUTES, + ).parsedSafe() ?: return null - val postData = if (type == TvType.TvSeries) { - mapOf( - "langs" to "[]", - "movieName" to searchRes.found.first().linkName, - "season" to "season-${query.seasonNumber}" - ) - } else { - mapOf( - "langs" to "[]", - "movieName" to searchRes.found.first().linkName, - ) + val firstResult = searchResponse.results.firstOrNull() ?: return null + + val apiResponse = app.get( + url = "$APIURL${firstResult.link.replace("series", "subtitles")}", + cacheTime = 120, + cacheUnit = TimeUnit.MINUTES, + ).parsedSafe() ?: return null + + val filteredSubtitles = apiResponse.subtitles.filter { sub -> + sub.releaseType != "trailer" && + sub.language.equals(queryLang, true) } - val getMovieRes = app.post( - url = "$APIURL/getMovie", - data = postData - ).parsedSafe().let { - // api doesn't has episode number or lang filtering - if (type == TvType.Movie) { - it?.subs?.filter { sub -> - sub.lang == queryLang - } - } else { - it?.subs?.filter { sub -> - sub.releaseName!!.contains( - String.format( - null, - "E%02d", - query.epNumber - ) - ) && sub.lang == queryLang - } + // api doesn't has episode number or lang filtering + val subtitles = if (type == TvType.Movie) { + filteredSubtitles + } else { + val shouldContain = String.format( + null, + "E%02d", + query.epNumber + ) + filteredSubtitles.filter { sub -> + sub.releaseInfo.contains( + shouldContain + ) } - } ?: return null + } - return getMovieRes.map { subtitle -> + return subtitles.map { subtitle -> AbstractSubtitleEntities.SubtitleEntity( idPrefix = this.idPrefix, - name = subtitle.releaseName!!, - lang = subtitle.lang!!, - data = SubData( - movie = subtitle.linkName!!, - lang = subtitle.lang, - id = subtitle.subId.toString(), - ).toJson(), + name = subtitle.releaseInfo, + lang = subtitle.language, + data = subtitle.link, type = type, source = this.name, epNumber = query.epNumber, seasonNumber = query.seasonNumber, - isHearingImpaired = subtitle.hi == 1, + isHearingImpaired = subtitle.hearingImpaired == 1, ) } } @@ -99,79 +91,114 @@ class SubSourceApi : SubtitleAPI() { auth: AuthData?, subtitle: AbstractSubtitleEntities.SubtitleEntity ) { - val parsedSub = parseJson(subtitle.data) - - val subRes = app.post( - url = "$APIURL/getSub", - data = mapOf( - "movie" to parsedSub.movie, - "lang" to subtitle.lang, - "id" to parsedSub.id - ) - ).parsedSafe() ?: return + val data = app.get("$APIURL/subtitle/${subtitle.data}") + .parsedSafe() + ?: return this.addZipUrl( - "$DOWNLOADENDPOINT/${subRes.sub.downloadToken}" + "$APIURL/subtitle/download/${data.subtitle.downloadToken}" ) { name, _ -> name } } + @Serializable - data class ApiSearch( - @JsonProperty("success") @SerialName("success") val success: Boolean, - @JsonProperty("found") @SerialName("found") val found: List, + data class SearchRoot( + @JsonProperty("success") @SerialName("success") var success: Boolean? = null, + @JsonProperty("results") @SerialName("results") var results: ArrayList = arrayListOf(), + @JsonProperty("users") @SerialName("users") var users: ArrayList = arrayListOf() ) @Serializable - data class Found( - @JsonProperty("id") @SerialName("id") val id: Long, - @JsonProperty("title") @SerialName("title") val title: String, - @JsonProperty("seasons") @SerialName("seasons") val seasons: Long, - @JsonProperty("type") @SerialName("type") val type: String, - @JsonProperty("releaseYear") @SerialName("releaseYear") val releaseYear: Long, - @JsonProperty("linkName") @SerialName("linkName") val linkName: String, + data class Users( + + @JsonProperty("id") @SerialName("id") var id: Int? = null, + @JsonProperty("displayname") @SerialName("displayname") var displayname: String? = null, + @JsonProperty("avatar") @SerialName("avatar") var avatar: String? = null, + @JsonProperty("badges") @SerialName("badges") var badges: ArrayList = arrayListOf() + ) @Serializable - data class ApiResponse( - @JsonProperty("success") @SerialName("success") val success: Boolean, - @JsonProperty("movie") @SerialName("movie") val movie: Movie, - @JsonProperty("subs") @SerialName("subs") val subs: List, + data class Results( + @JsonProperty("id") @SerialName("id") var id: Int? = null, + @JsonProperty("title") @SerialName("title") var title: String? = null, + @JsonProperty("type") @SerialName("type") var type: String? = null, + @JsonProperty("link") @SerialName("link") var link: String, + @JsonProperty("releaseYear") @SerialName("releaseYear") var releaseYear: Int? = null, + @JsonProperty("poster") @SerialName("poster") var poster: String? = null, + @JsonProperty("subtitleCount") @SerialName("subtitleCount") var subtitleCount: String? = null, + @JsonProperty("rating") @SerialName("rating") var rating: Double? = null, + @JsonProperty("cast") @SerialName("cast") var cast: ArrayList = arrayListOf(), + @JsonProperty("genres") @SerialName("genres") var genres: ArrayList = arrayListOf(), + @JsonProperty("score") @SerialName("score") var score: Double? = null ) @Serializable - data class Movie( - @JsonProperty("id") @SerialName("id") val id: Long? = null, - @JsonProperty("type") @SerialName("type") val type: String? = null, - @JsonProperty("year") @SerialName("year") val year: Long? = null, - @JsonProperty("fullName") @SerialName("fullName") val fullName: String? = null, + + data class ItemRoot( + + // @SerialName("media_type" ) var mediaType : String? = null, + @JsonProperty("subtitles") @SerialName("subtitles") var subtitles: ArrayList, + //@SerialName("movie" ) var movie : Movie? = Movie() + ) @Serializable - data class Sub( - @JsonProperty("hi") @SerialName("hi") val hi: Int? = null, - @JsonProperty("fullLink") @SerialName("fullLink") val fullLink: String? = null, - @JsonProperty("linkName") @SerialName("linkName") val linkName: String? = null, - @JsonProperty("lang") @SerialName("lang") val lang: String? = null, - @JsonProperty("releaseName") @SerialName("releaseName") val releaseName: String? = null, - @JsonProperty("subId") @SerialName("subId") val subId: Long? = null, + data class Subtitles( + + @JsonProperty("id") @SerialName("id") var id: Int? = null, + @JsonProperty("language") @SerialName("language") var language: String, + @JsonProperty("release_type") @SerialName("release_type") var releaseType: String? = null, + @JsonProperty("release_info") @SerialName("release_info") var releaseInfo: String, + @JsonProperty("upload_date") @SerialName("upload_date") var uploadDate: String? = null, + @JsonProperty("hearing_impaired") @SerialName("hearing_impaired") var hearingImpaired: Int? = null, + @JsonProperty("caption") @SerialName("caption") var caption: String? = null, + @JsonProperty("rating") @SerialName("rating") var rating: String? = null, + @JsonProperty("uploader_id") @SerialName("uploader_id") var uploaderId: Int? = null, + @JsonProperty("uploader_displayname") @SerialName("uploader_displayname") var uploaderDisplayname: String? = null, + @JsonProperty("uploader_badges") @SerialName("uploader_badges") var uploaderBadges: ArrayList = arrayListOf(), + @JsonProperty("link") @SerialName("link") var link: String, + @JsonProperty("production_type") @SerialName("production_type") var productionType: String? = null, + @JsonProperty("last_subtitle") @SerialName("last_subtitle") var lastSubtitle: Boolean? = null + ) @Serializable - data class SubData( - @JsonProperty("movie") @SerialName("movie") val movie: String, - @JsonProperty("lang") @SerialName("lang") val lang: String, - @JsonProperty("id") @SerialName("id") val id: String, + data class DownloadRoot( + @JsonProperty("subtitle") @SerialName("subtitle") var subtitle: Subtitle, + //@SerializedName("movie" ) var movie : Movie? = Movie(), + //@SerializedName("donationLinks" ) var donationLinks : DonationLinks? = DonationLinks(), + //@SerializedName("isDownloaded" ) var isDownloaded : Boolean? = null, + //@SerializedName("user_rated" ) var userRated : String? = null ) @Serializable - data class SubTitleLink( - @JsonProperty("sub") @SerialName("sub") val sub: SubToken, - ) + data class Subtitle( + + @JsonProperty("id") @SerialName("id") var id: Int? = null, + @JsonProperty("uploaded_at") @SerialName("uploaded_at") var uploadedAt: String? = null, + @JsonProperty("language") @SerialName("language") var language: String? = null, + @JsonProperty("rating") @SerialName("rating") var rating: String? = null, + //SerialName("rates" ) var rates : Rates? = Rates(), + @JsonProperty("uploaded_by") @SerialName("uploaded_by") var uploadedBy: Int? = null, + //@SerialName("contribs" ) var contribs : ArrayList = arrayListOf(), + @JsonProperty("release_info") @SerialName("release_info") var releaseInfo: ArrayList = arrayListOf(), + @JsonProperty("commentary") @SerialName("commentary") var commentary: String? = null, + @JsonProperty("files") @SerialName("files") var files: String? = null, + @JsonProperty("size") @SerialName("size") var size: String? = null, + @JsonProperty("downloads") @SerialName("downloads") var downloads: Int? = null, + @JsonProperty("comments") @SerialName("comments") var comments: Int? = null, + @JsonProperty("production_type") @SerialName("production_type") var productionType: String? = null, + @JsonProperty("release_type") @SerialName("release_type") var releaseType: String? = null, + @JsonProperty("episode") @SerialName("episode") var episode: String? = null, + @JsonProperty("hearing_impaired") @SerialName("hearing_impaired") var hearingImpaired: Int? = null, + @JsonProperty("foreign_parts") @SerialName("foreign_parts") var foreignParts: String? = null, + @JsonProperty("framerate") @SerialName("framerate") var framerate: String? = null, + @JsonProperty("preview") @SerialName("preview") var preview: String? = null, + @JsonProperty("user_uploaded") @SerialName("user_uploaded") var userUploaded: Boolean? = null, + @JsonProperty("download_token") @SerialName("download_token") var downloadToken: String - @Serializable - data class SubToken( - @JsonProperty("downloadToken") @SerialName("downloadToken") val downloadToken: String, ) } From e428d422ebdd8d4def84c555401fbd55b7e39a73 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:12:37 +0000 Subject: [PATCH 035/105] Fix: Remove uiReset for better UX for errors (#3023) --- .../com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index b64355e23..91e3ff970 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -509,7 +509,8 @@ class GeneratorPlayer : FullScreenPlayer() { showDownloadProgress(DownloadEvent(0, 0, 0, null)) - uiReset() + // uiReset() // Removed due to UX + currentSelectedLink = link // setEpisodes(viewModel.getAllMeta() ?: emptyList()) setPlayerDimen(null) From 66afc3b1eec4a387113312770b4765d175179ba9 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:50:05 +0000 Subject: [PATCH 036/105] Feat: Animation to actor adapter (#3035) --- .../cloudstream3/ui/result/ActorAdaptor.kt | 23 ++++++++++++++++++- app/src/main/res/layout/cast_item.xml | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt index 056588d0b..fe8c617f2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt @@ -5,6 +5,9 @@ import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.Animation +import android.view.animation.OvershootInterpolator +import android.view.animation.ScaleAnimation import androidx.core.view.isVisible import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.ActorRole @@ -46,6 +49,24 @@ class ActorAdaptor( } } + override fun onUpdateContent(holder: ViewHolderState, item: ActorData, position: Int) { + when (val binding = holder.view) { + is CastItemBinding -> { + val anim: Animation = ScaleAnimation( + 0.8f, 1f, + 0.8f, 1f, + Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f + ) + anim.fillAfter = true + anim.duration = 200 + anim.interpolator = OvershootInterpolator() + binding.voiceActorImageHolder2.startAnimation(anim) + } + } + super.onUpdateContent(holder, item, position) + } + override fun onBindContent(holder: ViewHolderState, item: ActorData, position: Int) { when (val binding = holder.view) { is CastItemBinding -> { @@ -137,4 +158,4 @@ class ActorAdaptor( } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/cast_item.xml b/app/src/main/res/layout/cast_item.xml index 4f7bdf74d..610b4e3e4 100644 --- a/app/src/main/res/layout/cast_item.xml +++ b/app/src/main/res/layout/cast_item.xml @@ -47,6 +47,7 @@ Date: Thu, 9 Jul 2026 15:51:16 +0000 Subject: [PATCH 037/105] Remove AM/PM when on 24h settings --- .../lagradost/cloudstream3/ui/settings/SettingsFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index e41109b59..6943e5cf4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -249,7 +249,7 @@ class SettingsFragment : BaseFragment( val appVersion = BuildConfig.VERSION_NAME val commitHash = activity?.currentCommitHash() ?: "" - val buildTimestamp = SimpleDateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, + val buildTimestamp = SimpleDateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault() ).apply { timeZone = TimeZone.getTimeZone("UTC") }.format(Date(BuildConfig.BUILD_DATE)).replace("UTC", "") @@ -262,4 +262,4 @@ class SettingsFragment : BaseFragment( true } } -} \ No newline at end of file +} From 11202d269de03ac38bc3fd09efc441d1f2011966 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:40:00 -0600 Subject: [PATCH 038/105] [skip ci] SyncUtil: fix type for tryParseJson (#3038) --- app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index b8e2c1b76..43e61ad88 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -72,7 +72,7 @@ object SyncUtil { // Gogoanime, Twistmoe and 9anime val url = "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/pages/$site/$slug.json" val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).text - val mapped = tryParseJson(response) + val mapped = tryParseJson(response) val overrideMal = mapped?.malId ?: mapped?.mal?.id ?: mapped?.anilist?.malId val overrideAnilist = mapped?.aniId ?: mapped?.anilist?.id From 0e3191e28b843e5b0f838e39fea056d69170fad1 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:41:14 -0600 Subject: [PATCH 039/105] [skip ci] Update commented deprecation message for getRhinoContext (#3037) --- .../kotlin/com/lagradost/cloudstream3/MainAPI.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 72d69dc45..99421a800 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -797,16 +797,9 @@ fun fixTitle(str: String): String { } } -/** - * Get rhino context in a safe way as it needs to be initialized on the main thread. - * - * Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects() - * - * Use like the following: rhino.evaluateString(scope, js, "JavaScript", 1, null) - **/ // Deprecate after next stable /* @Deprecated( - message = "Use JsContext or evalJs instead.", + message = "Use newJsContext or evalJs instead.", level = DeprecationLevel.WARNING, ) */ suspend fun getRhinoContext(): org.mozilla.javascript.Context { From 8bdc5999968265d74dae8ddff3656950be255edc Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:43:49 -0600 Subject: [PATCH 040/105] test: some cleanup to JsInterpreterTest (#3036) --- .../cloudstream3/utils/JsInterpreterTest.kt | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt index 182ea6c95..ded236f4e 100644 --- a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt @@ -28,6 +28,10 @@ class JsInterpreterTest { private fun num(code: String, variable: String? = null): Double = evalJsInternal(code, variable) as? Double ?: Double.NaN private fun str(code: String, variable: String? = null): String = jsValueToString(evalJsInternal(code, variable)) + private suspend fun JsContext.bool(code: String): Boolean = eval(code) as? Boolean ?: false + private suspend fun JsContext.num(code: String): Double = eval(code) as? Double ?: Double.NaN + private suspend fun JsContext.str(code: String): String = jsValueToString(eval(code)) + private fun assertApprox(expected: Double, actual: Double, tol: Double = 1e-9) { assertTrue(abs(actual - expected) <= tol, "Expected $expected ± $tol but was $actual") } @@ -1384,18 +1388,18 @@ class JsInterpreterTest { @Test fun evaluateMathSimpleAddition() { - assertEquals("5", jsValueToString(evalJsInternal("eval(2+3)"))) + assertEquals("5", str("eval(2+3)")) } @Test fun evaluateMathNestedParens() { - assertEquals("12", jsValueToString(evalJsInternal("eval((2+4)*2)"))) + assertEquals("12", str("eval((2+4)*2)")) } @Test fun evaluateMathProducesCharCode() { val code = "eval(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)" - assertEquals(65.0, (evalJsInternal(code) as? Double) ?: 0.0) + assertEquals(65.0, num(code)) } @Test @@ -1925,12 +1929,12 @@ class JsInterpreterTest { @Test fun emptyStringMinusNegatedEmptyStringIsZero() { - assertEquals(0.0, evalJsInternal("\"\" - - \"\"")) + assertEquals(0.0, num("\"\" - - \"\"")) } @Test fun emptyStringMinusNumber() { - assertEquals(-1.0, evalJsInternal("\"\" - 1")) + assertEquals(-1.0, num("\"\" - 1")) } @Test @@ -1946,7 +1950,7 @@ class JsInterpreterTest { @Test fun postfixIncrementOnNonLvalueFailsGracefully() { - assertEquals(2.0, evalJsInternal("true+1")) + assertEquals(2.0, num("true+1")) // `true++` has no valid assignment target (a SyntaxError in real JS); evalJs falls // back to Unit instead of returning a bogus number. assertEquals(Unit, evalJsInternal("true++")) @@ -2029,7 +2033,7 @@ class JsInterpreterTest { @Test fun combinedCoercionOfNaNEmptyStringAndArrayHoleIsZero() { - assertEquals(0.0, evalJsInternal("+!!NaN * \"\" - - [,]")) + assertEquals(0.0, num("+!!NaN * \"\" - - [,]")) } /** Returns a [CoroutineScope] backed by a plain [Job] with no dispatcher attached. */ @@ -2153,7 +2157,6 @@ class JsInterpreterTest { } } } - done.send(Unit) } @@ -2165,7 +2168,7 @@ class JsInterpreterTest { @Test fun newJsContextWithNoInitializerIsUsable() = runTest { val ctx = newJsContext() - assertEquals(3.0, ctx.eval("1+2") as? Double ?: 0.0) + assertEquals(3.0, ctx.num("1+2")) } @Test @@ -2225,8 +2228,7 @@ class JsInterpreterTest { fun jsContextFunctionDeclaredInOneEvalUsableInAnother() = runTest { val ctx = newJsContext() ctx.eval("function double(n) { return n * 2; }") - val result = ctx.eval("double(21)") - assertEquals(42.0, result as? Double ?: 0.0) + assertEquals(42.0, ctx.num("double(21)")) } @Test @@ -2234,13 +2236,13 @@ class JsInterpreterTest { val ctx = newJsContext() ctx.eval("var arr = [1,2,3]") ctx.eval("arr.push(4)") - assertEquals("1,2,3,4", ctx.eval("arr.join(',')") as? String) + assertEquals("1,2,3,4", ctx.str("arr.join(',')")) } @Test fun jsContextEvalReturnsLastStatementValue() = runTest { val ctx = newJsContext() - assertEquals(9.0, ctx.eval("var a = 3; a * a") as? Double ?: 0.0) + assertEquals(9.0, ctx.num("var a = 3; a * a")) } @Test @@ -2252,7 +2254,7 @@ class JsInterpreterTest { @Test fun jsContextEvalReturnsStringValue() = runTest { val ctx = newJsContext() - assertEquals("ab", ctx.eval("'a' + 'b'")) + assertEquals("ab", ctx.str("'a' + 'b'")) } @Test @@ -2267,7 +2269,7 @@ class JsInterpreterTest { fun separateJsContextsDoNotShareFunctions() = runTest { val ctx1 = newJsContext { eval("function f(){ return 1; }") } val ctx2 = newJsContext() - assertEquals(1.0, ctx1.eval("f()") as? Double ?: 0.0) + assertEquals(1.0, ctx1.num("f()")) // f was never declared in ctx2, so calling it should not crash and yields Unit. assertEquals(Unit, ctx2.eval("typeof f === 'function' ? f() : undefined")) } @@ -2275,8 +2277,7 @@ class JsInterpreterTest { @Test fun jsContextEvalDefaultBudgetHandlesNormalScript() = runTest { val ctx = newJsContext() - val result = ctx.eval("var s=0; for(var i=1;i<=1000;i++){s+=i} s") - assertEquals(500500.0, result as? Double ?: 0.0) + assertEquals(500500.0, ctx.num("var s=0; for(var i=1;i<=1000;i++){s+=i} s")) } @Test @@ -2312,8 +2313,7 @@ class JsInterpreterTest { // Abort this call assertEquals(Unit, ctx.eval("while(true){}")) // The next call, using the defaults again, runs a normal script fine. - val result = ctx.eval("1+1") - assertEquals(2.0, result as? Double ?: 0.0) + assertEquals(2.0, ctx.num("1+1")) } @Test @@ -2341,7 +2341,6 @@ class JsInterpreterTest { } } } - done.send(Unit) } @@ -2354,10 +2353,10 @@ class JsInterpreterTest { fun jsContextEvalNotCancelledWhenWithTimeoutDoesNotExpire() = runTest { val ctx = newJsContext() val result = withTimeoutOrNull(5.seconds) { - ctx.eval("var s=0; for(var i=0;i<100;i++){s+=i} s") + ctx.num("var s=0; for(var i=0;i<100;i++){s+=i} s") } - assertEquals(4950.0, result as? Double ?: 0.0) + assertEquals(4950.0, result ?: Double.NaN) } @Test @@ -2392,19 +2391,19 @@ class JsInterpreterTest { @Test fun jsContextInitializerCanReadBackItsOwnEvalResult() = runTest { - var capturedDuringInit: Any? = null + var capturedDuringInit: Double? = null newJsContext { - capturedDuringInit = eval("2 * 21") + capturedDuringInit = num("2 * 21") } - assertEquals(42.0, capturedDuringInit as? Double ?: 0.0) + assertEquals(42.0, capturedDuringInit ?: Double.NaN) } @Test fun jsContextSequentialIndependentComputations() = runTest { val ctx = newJsContext() - assertEquals(4.0, ctx.eval("2+2") as? Double ?: 0.0) - assertEquals("hi", ctx.eval("'h'+'i'") as? String) - assertFalse(ctx.eval("1 > 2") as? Boolean ?: true) + assertEquals(4.0, ctx.num("2+2")) + assertEquals("hi", ctx.str("'h'+'i'")) + assertFalse(ctx.bool("1 > 2")) } } From 3796406e636356f556c16e04e1da4e37d6a19038 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Fri, 10 Jul 2026 01:46:50 +0200 Subject: [PATCH 041/105] Bump to 4.8.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8935ab52a..f20c19832 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,7 +63,7 @@ compileSdk = "36" targetSdk = "36" versionCode = "68" -versionName = "4.7.0" +versionName = "4.8.0" [libraries] activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } From 189162fac9183f17fcb26bf3b6b6f09e8001397c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:12:06 -0600 Subject: [PATCH 042/105] AesHelper: make the new cryptoAESHandler suspend (#3039) --- .../cloudstream3/extractors/helper/AesHelper.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt index 22527bcfb..a09159245 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt @@ -9,6 +9,7 @@ import dev.whyoleg.cryptography.CryptographyProvider import dev.whyoleg.cryptography.DelicateCryptographyApi import dev.whyoleg.cryptography.algorithms.AES import dev.whyoleg.cryptography.algorithms.MD5 +import kotlinx.coroutines.runBlocking import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -21,7 +22,7 @@ object AesHelper { @OptIn(DelicateCryptographyApi::class) @Prerelease - fun cryptoAESHandler( + suspend fun cryptoAESHandler( data: String, pass: ByteArray, encrypt: Boolean = true, @@ -35,14 +36,14 @@ object AesHelper { saltLength = parse.s.length / 2, ) ?: return null - val aesKey = aesCbc.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, key) + val aesKey = aesCbc.keyDecoder().decodeFromByteArray(AES.Key.Format.RAW, key) val cipher = aesKey.cipher(padding = padding) return if (!encrypt) { - val plainBytes = cipher.decryptWithIvBlocking(iv, base64DecodeArray(parse.ct)) + val plainBytes = cipher.decryptWithIv(iv, base64DecodeArray(parse.ct)) plainBytes.decodeToString() } else { - base64Encode(cipher.encryptWithIvBlocking(iv, parse.ct.encodeToByteArray())) + base64Encode(cipher.encryptWithIv(iv, parse.ct.encodeToByteArray())) } } @@ -60,7 +61,7 @@ object AesHelper { // If it ends with NoPadding (e.g. "AES/CBC/NoPadding"), then it // doesn't have padding, otherwise we treat as if it does. val hasPadding = !padding.endsWith("NoPadding") - return cryptoAESHandler(data, pass, encrypt, hasPadding) + return runBlocking { cryptoAESHandler(data, pass, encrypt, hasPadding) } } // https://stackoverflow.com/a/41434590/8166854 From b681d443e42b0fd5daa413cc42b7f6ff38181ebb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:18:30 -0600 Subject: [PATCH 043/105] [skip ci] Make some classes serializable (#2874) --- .../ui/player/PlayerSubtitleHelper.kt | 33 +++++---- .../cloudstream3/ui/result/ResultFragment.kt | 53 ++++++++------- .../com/lagradost/cloudstream3/MainAPI.kt | 67 +++++++++++-------- .../cloudstream3/utils/ExtractorApi.kt | 29 ++++---- 4 files changed, 102 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt index ee6170aa5..f62bad58d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt @@ -8,11 +8,14 @@ import androidx.annotation.OptIn import androidx.media3.common.MimeTypes import androidx.media3.common.util.UnstableApi import androidx.media3.ui.SubtitleView +import com.fasterxml.jackson.annotation.JsonIgnore import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.setSubtitleViewStyle import com.lagradost.cloudstream3.utils.SubtitleHelper.fromLanguageToTagIETF import com.lagradost.cloudstream3.utils.UIHelper.toPx +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable enum class SubtitleStatus { IS_ACTIVE, @@ -32,17 +35,19 @@ enum class SubtitleOrigin { * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend id * @param headers if empty it will use the base onlineDataSource headers else only the specified headers * @param languageCode usually, tags such as "en", "es-mx", or "zh-hant-TW". But it could be something like "English 4" - * */ + */ +@Serializable data class SubtitleData( - val originalName: String, - val nameSuffix: String, - val url: String, - val origin: SubtitleOrigin, - val mimeType: String, - val headers: Map, - val languageCode: String?, + @SerialName("originalName") val originalName: String, + @SerialName("nameSuffix") val nameSuffix: String, + @SerialName("url") val url: String, + @SerialName("origin") val origin: SubtitleOrigin, + @SerialName("mimeType") val mimeType: String, + @SerialName("headers") val headers: Map, + @SerialName("languageCode") val languageCode: String?, ) { - /** Internal ID for exoplayer, unique for each link*/ + /** Internal ID for media3, unique for each link. */ + @JsonIgnore fun getId(): String { return if (origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) url else "$url|$name" @@ -54,22 +59,22 @@ data class SubtitleData( } /** Tries hard to figure out a valid IETF tag based on language code and name. Will return null if not found. */ + @JsonIgnore fun getIETF_tag(): String? { return fromLanguageToTagIETF(this.languageCode) ?: fromLanguageToTagIETF(this.originalName, halfMatch = true) } - val name = "$originalName $nameSuffix" + @SerialName("name") val name = "$originalName $nameSuffix" /** * Gets the URL, but tries to fix it if it is malformed. */ + @JsonIgnore fun getFixedUrl(): String { // Some extensions fail to include the protocol, this helps with that. val fixedSubUrl = if (this.url.startsWith("//")) { "https:${this.url}" - } else { - this.url - } + } else this.url return fixedSubUrl } } @@ -142,4 +147,4 @@ class PlayerSubtitleHelper { setSubStyle(it) } } -} \ No newline at end of file +} 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 cbf94fd97..71909c5d3 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 @@ -21,6 +21,8 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UiImage +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_LOAD_EP = 2 @@ -34,33 +36,32 @@ enum class VideoWatchState { Watched } +@Serializable data class ResultEpisode( - val headerName: String, - val name: String?, - val poster: String?, - val episode: Int, - val seasonIndex: Int?, // this is the "season" index used season names - val season: Int?, // this is the display - val data: String, - val apiName: String, - val id: Int, - val index: Int, - val position: Long, // time in MS - val duration: Long, // duration in MS - val score: Score?, - val description: String?, - val isFiller: Boolean?, - val tvType: TvType, - val parentId: Int, - /** - * Conveys if the episode itself is marked as watched - **/ - val videoWatchState: VideoWatchState, - /** Sum of all previous season episode counts + episode */ - val totalEpisodeIndex: Int? = null, - val airDate: Long? = null, - val runTime: Int? = null, - val seasonData: SeasonData? = null, + @SerialName("headerName") val headerName: String, + @SerialName("name") val name: String?, + @SerialName("poster") val poster: String?, + @SerialName("episode") val episode: Int, + @SerialName("seasonIndex") val seasonIndex: Int?, // this is the "season" index used season names + @SerialName("season") val season: Int?, // this is the display + @SerialName("data") val data: String, + @SerialName("apiName") val apiName: String, + @SerialName("id") val id: Int, + @SerialName("index") val index: Int, + @SerialName("position") val position: Long, // time in MS + @SerialName("duration") val duration: Long, // duration in MS + @SerialName("score") val score: Score?, + @SerialName("description") val description: String?, + @SerialName("isFiller") val isFiller: Boolean?, + @SerialName("tvType") val tvType: TvType, + @SerialName("parentId") val parentId: Int, + /** Conveys if the episode itself is marked as watched. */ + @SerialName("videoWatchState") val videoWatchState: VideoWatchState, + /** Sum of all previous season episode counts + episode. */ + @SerialName("totalEpisodeIndex") val totalEpisodeIndex: Int? = null, + @SerialName("airDate") val airDate: Long? = null, + @SerialName("runTime") val runTime: Int? = null, + @SerialName("seasonData") val seasonData: SeasonData? = null, ) fun ResultEpisode.getRealPosition(): Long { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 99421a800..53b773e59 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -39,6 +39,8 @@ import kotlinx.datetime.format.byUnicodePattern import kotlinx.datetime.format.char import kotlinx.datetime.format.parse import kotlinx.datetime.toInstant +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlin.io.encoding.Base64 import kotlin.jvm.JvmName @@ -325,7 +327,7 @@ object APIHolder { ).toJson().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) return app.post("https://graphql.anilist.co", requestBody = data) - .parsedSafe() + .parsedSafe() } } @@ -393,18 +395,19 @@ const val PROVIDER_STATUS_SLOW = 2 const val PROVIDER_STATUS_OK = 1 const val PROVIDER_STATUS_DOWN = 0 +@Serializable data class ProvidersInfoJson( - @JsonProperty("name") var name: String, - @JsonProperty("url") var url: String, - @JsonProperty("credentials") var credentials: String? = null, - @JsonProperty("status") var status: Int, + @JsonProperty("name") @SerialName("name") var name: String, + @JsonProperty("url") @SerialName("url") var url: String, + @JsonProperty("credentials") @SerialName("credentials") var credentials: String? = null, + @JsonProperty("status") @SerialName("status") var status: Int, ) +@Serializable data class SettingsJson( - @JsonProperty("enableAdult") var enableAdult: Boolean = false, + @JsonProperty("enableAdult") @SerialName("enableAdult") var enableAdult: Boolean = false, ) - data class MainPageData( val name: String, val data: String, @@ -868,10 +871,10 @@ enum class DubStatus(val id: Int) { * of this as a decimal class specifically for ratings. * */ @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +@Serializable class Score private constructor( /** Decimal between [0, 10^9] representing the min score and max score respectively */ - @JsonProperty("data") - private val data: Int, + @JsonProperty("data") @SerialName("data") private val data: Int, ) { override fun hashCode(): Int = this.data.hashCode() override fun equals(other: Any?): Boolean = other is Score && this.data == other.data @@ -1197,9 +1200,10 @@ suspend fun newSubtitleFile( * @see newAudioFile * */ @ConsistentCopyVisibility +@Serializable data class AudioFile internal constructor( - var url: String, - var headers: Map? = null + @JsonProperty("url") @SerialName("url") var url: String, + @JsonProperty("headers") @SerialName("headers") var headers: Map? = null, ) /** Creates an AudioFile with optional initializer for setting additional properties. @@ -2176,10 +2180,11 @@ data class NextAiring( * @param name To be shown next to the season like "Season $displaySeason $name" but if displaySeason is null then "$name" * @param displaySeason What to be displayed next to the season name, if null then the name is the only thing shown. * */ +@Serializable data class SeasonData( - val season: Int, - val name: String? = null, - val displaySeason: Int? = null, // will use season if null + @JsonProperty("season") @SerialName("season") val season: Int, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("displaySeason") @SerialName("displaySeason") val displaySeason: Int? = null, // will use season if null ) /** Abstract interface of EpisodeResponse */ @@ -2737,32 +2742,38 @@ data class Tracker( val cover: String? = null, ) +@Serializable data class AniSearch( - @JsonProperty("data") var data: Data? = Data() + @JsonProperty("data") @SerialName("data") var data: Data? = Data(), ) { + @Serializable data class Data( - @JsonProperty("Page") var page: Page? = Page() + @JsonProperty("Page") @SerialName("Page") var page: Page? = Page(), ) { + @Serializable data class Page( - @JsonProperty("media") var media: ArrayList = arrayListOf() + @JsonProperty("media") @SerialName("media") var media: ArrayList = arrayListOf(), ) { + @Serializable data class Media( - @JsonProperty("title") var title: Title? = null, - @JsonProperty("id") var id: Int? = null, - @JsonProperty("idMal") var idMal: Int? = null, - @JsonProperty("seasonYear") var seasonYear: Int? = null, - @JsonProperty("format") var format: String? = null, - @JsonProperty("coverImage") var coverImage: CoverImage? = null, - @JsonProperty("bannerImage") var bannerImage: String? = null, + @JsonProperty("title") @SerialName("title") var title: Title? = null, + @JsonProperty("id") @SerialName("id") var id: Int? = null, + @JsonProperty("idMal") @SerialName("idMal") var idMal: Int? = null, + @JsonProperty("seasonYear") @SerialName("seasonYear") var seasonYear: Int? = null, + @JsonProperty("format") @SerialName("format") var format: String? = null, + @JsonProperty("coverImage") @SerialName("coverImage") var coverImage: CoverImage? = null, + @JsonProperty("bannerImage") @SerialName("bannerImage") var bannerImage: String? = null, ) { + @Serializable data class CoverImage( - @JsonProperty("extraLarge") var extraLarge: String? = null, - @JsonProperty("large") var large: String? = null, + @JsonProperty("extraLarge") @SerialName("extraLarge") var extraLarge: String? = null, + @JsonProperty("large") @SerialName("large") var large: String? = null, ) + @Serializable data class Title( - @JsonProperty("romaji") var romaji: String? = null, - @JsonProperty("english") var english: String? = null, + @JsonProperty("romaji") @SerialName("romaji") var romaji: String? = null, + @JsonProperty("english") @SerialName("english") var english: String? = null, ) { fun isMatchingTitles(title: String?): Boolean { if (title == null) return false diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 8ef391050..554170df5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -328,6 +328,9 @@ import io.ktor.http.decodeURLPart import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import kotlin.coroutines.cancellation.CancellationException import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -418,6 +421,7 @@ enum class ExtractorLinkType { MAGNET; // See https://www.iana.org/assignments/media-types/media-types.xhtml + @JsonIgnore fun getMimeType(): String { return when (this) { VIDEO -> "video/mp4" @@ -685,26 +689,27 @@ open class DrmExtractorLink private constructor( * @property audioTracks List of separate audio tracks that can be used with this video * @see newExtractorLink * */ +@Serializable open class ExtractorLink @Deprecated("Use newExtractorLink", level = DeprecationLevel.WARNING) constructor( - open val source: String, - open val name: String, - override val url: String, - override var referer: String, - open var quality: Int, - override var headers: Map = mapOf(), + @SerialName("source") open val source: String, + @SerialName("name") open val name: String, + @SerialName("url") override val url: String, + @SerialName("referer") override var referer: String, + @SerialName("quality") open var quality: Int, + @SerialName("headers") override var headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ - open var extractorData: String? = null, - open var type: ExtractorLinkType, + @SerialName("extractorData") open var extractorData: String? = null, + @SerialName("type") open var type: ExtractorLinkType, /** List of separate audio tracks that can be merged with this video */ - open var audioTracks: List = emptyList(), + @SerialName("audioTracks") open var audioTracks: List = emptyList(), ) : IDownloadableMinimum { - val isM3u8: Boolean get() = type == ExtractorLinkType.M3U8 - val isDash: Boolean get() = type == ExtractorLinkType.DASH + @get:JsonIgnore val isM3u8: Boolean get() = type == ExtractorLinkType.M3U8 + @get:JsonIgnore val isDash: Boolean get() = type == ExtractorLinkType.DASH // Cached video size - private var videoSize: Long? = null + @Transient private var videoSize: Long? = null /** * Get video size in bytes with one head request. Only available for ExtractorLinkType.Video From c36652d265a970f261060a5707293361b278e667 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:40:25 -0600 Subject: [PATCH 044/105] [skip ci] DownloadObjects: migrate to kotlinx serialization (#3026) --- .../utils/downloader/DownloadManager.kt | 4 +- .../utils/downloader/DownloadObjects.kt | 181 ++++++++++-------- 2 files changed, 106 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadManager.kt index 7cb190667..adefedd20 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadManager.kt @@ -1640,11 +1640,11 @@ object VideoDownloadManager { } fun getDownloadResumePackage(context: Context, id: Int): DownloadResumePackage? { - return context.getKey(KEY_RESUME_PACKAGES, id.toString()) + return context.getKey(KEY_RESUME_PACKAGES, id.toString()) } fun getDownloadQueuePackage(context: Context, id: Int): DownloadQueueWrapper? { - return context.getKey(KEY_RESUME_IN_QUEUE, id.toString()) + return context.getKey(KEY_RESUME_IN_QUEUE, id.toString()) } fun getDownloadEpisodeMetadata( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadObjects.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadObjects.kt index 25a9fdf2a..946e7ecf9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadObjects.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/downloader/DownloadObjects.kt @@ -1,23 +1,32 @@ package com.lagradost.cloudstream3.utils.downloader import android.net.Uri +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.Score +import com.lagradost.cloudstream3.SkipSerializationTest import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.services.DownloadQueueService import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.serializers.UriSerializer +import com.lagradost.cloudstream3.utils.serializers.WriteOnlySerializer import com.lagradost.safefile.SafeFile +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KeepGeneratedSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import java.io.IOException import java.io.OutputStream import java.util.Objects object DownloadObjects { /** An item can either be something to resume or something new to start */ + @Serializable data class DownloadQueueWrapper( - @JsonProperty("resumePackage") val resumePackage: DownloadResumePackage?, - @JsonProperty("downloadItem") val downloadItem: DownloadQueueItem?, + @JsonProperty("resumePackage") @SerialName("resumePackage") val resumePackage: DownloadResumePackage?, + @JsonProperty("downloadItem") @SerialName("downloadItem") val downloadItem: DownloadQueueItem?, ) { init { assert(resumePackage != null || downloadItem != null) { @@ -26,56 +35,66 @@ object DownloadObjects { } /** Loop through the current download instances to see if it is currently downloading. Also includes link loading. */ + @JsonIgnore fun isCurrentlyDownloading(): Boolean { return DownloadQueueService.downloadInstances.value.any { it.downloadQueueWrapper.id == this.id } } - @JsonProperty("id") + @JsonProperty("id") @SerialName("id") val id = resumePackage?.item?.ep?.id ?: downloadItem!!.episode.id - @JsonProperty("parentId") + @JsonProperty("parentId") @SerialName("parentId") val parentId = resumePackage?.item?.ep?.parentId ?: downloadItem!!.episode.parentId } /** General data about the episode and show to start a download from. */ + @Serializable data class DownloadQueueItem( - @JsonProperty("episode") val episode: ResultEpisode, - @JsonProperty("isMovie") val isMovie: Boolean, - @JsonProperty("resultName") val resultName: String, - @JsonProperty("resultType") val resultType: TvType, - @JsonProperty("resultPoster") val resultPoster: String?, - @JsonProperty("apiName") val apiName: String, - @JsonProperty("resultId") val resultId: Int, - @JsonProperty("resultUrl") val resultUrl: String, - @JsonProperty("links") val links: List? = null, - @JsonProperty("subs") val subs: List? = null, + @JsonProperty("episode") @SerialName("episode") val episode: ResultEpisode, + @JsonProperty("isMovie") @SerialName("isMovie") val isMovie: Boolean, + @JsonProperty("resultName") @SerialName("resultName") val resultName: String, + @JsonProperty("resultType") @SerialName("resultType") val resultType: TvType, + @JsonProperty("resultPoster") @SerialName("resultPoster") val resultPoster: String?, + @JsonProperty("apiName") @SerialName("apiName") val apiName: String, + @JsonProperty("resultId") @SerialName("resultId") val resultId: Int, + @JsonProperty("resultUrl") @SerialName("resultUrl") val resultUrl: String, + @JsonProperty("links") @SerialName("links") val links: List? = null, + @JsonProperty("subs") @SerialName("subs") val subs: List? = null, ) { fun toWrapper(): DownloadQueueWrapper { return DownloadQueueWrapper(null, this) } } + interface DownloadCached { + val id: Int + } - abstract class DownloadCached( - @JsonProperty("id") open val id: Int, - ) - + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = DownloadEpisodeCached.Serializer::class) data class DownloadEpisodeCached( - @JsonProperty("name") val name: String?, - @JsonProperty("poster") val poster: String?, - @JsonProperty("episode") val episode: Int, - @JsonProperty("season") val season: Int?, - @JsonProperty("parentId") val parentId: Int, - @JsonProperty("score") var score: Score? = null, - @JsonProperty("description") val description: String?, - @JsonProperty("cacheTime") val cacheTime: Long, - override val id: Int, - ) : DownloadCached(id) { + @JsonProperty("name") @SerialName("name") val name: String?, + @JsonProperty("poster") @SerialName("poster") val poster: String?, + @JsonProperty("episode") @SerialName("episode") val episode: Int, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("parentId") @SerialName("parentId") val parentId: Int, + @JsonProperty("score") @SerialName("score") var score: Score? = null, + @JsonProperty("description") @SerialName("description") val description: String?, + @JsonProperty("cacheTime") @SerialName("cacheTime") val cacheTime: Long, + @JsonProperty("id") @SerialName("id") override val id: Int, + ) : DownloadCached { + object Serializer : WriteOnlySerializer( + DownloadEpisodeCached.generatedSerializer(), + setOf("rating"), + ) + @JsonProperty("rating", access = JsonProperty.Access.WRITE_ONLY) + @SerialName("rating") @Deprecated( "`rating` is the old scoring system, use score instead", replaceWith = ReplaceWith("score"), - level = DeprecationLevel.ERROR + level = DeprecationLevel.ERROR, ) var rating: Int? = null set(value) { @@ -87,74 +106,81 @@ object DownloadObjects { } /** What to display to the user for a downloaded show/movie. Includes info such as name, poster and url */ + @Serializable data class DownloadHeaderCached( - @JsonProperty("apiName") val apiName: String, - @JsonProperty("url") val url: String, - @JsonProperty("type") val type: TvType, - @JsonProperty("name") val name: String, - @JsonProperty("poster") val poster: String?, - @JsonProperty("cacheTime") val cacheTime: Long, - override val id: Int, - ) : DownloadCached(id) + @JsonProperty("apiName") @SerialName("apiName") val apiName: String, + @JsonProperty("url") @SerialName("url") val url: String, + @JsonProperty("type") @SerialName("type") val type: TvType, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("poster") @SerialName("poster") val poster: String?, + @JsonProperty("cacheTime") @SerialName("cacheTime") val cacheTime: Long, + @JsonProperty("id") @SerialName("id") override val id: Int, + ) : DownloadCached + @Serializable data class DownloadResumePackage( - @JsonProperty("item") val item: DownloadItem, + @JsonProperty("item") @SerialName("item") val item: DownloadItem, /** Tills which link should get resumed */ - @JsonProperty("linkIndex") val linkIndex: Int?, + @JsonProperty("linkIndex") @SerialName("linkIndex") val linkIndex: Int?, ) { fun toWrapper(): DownloadQueueWrapper { return DownloadQueueWrapper(this, null) } } + @Serializable data class DownloadItem( - @JsonProperty("source") val source: String?, - @JsonProperty("folder") val folder: String?, - @JsonProperty("ep") val ep: DownloadEpisodeMetadata, - @JsonProperty("links") val links: List, + @JsonProperty("source") @SerialName("source") val source: String?, + @JsonProperty("folder") @SerialName("folder") val folder: String?, + @JsonProperty("ep") @SerialName("ep") val ep: DownloadEpisodeMetadata, + @JsonProperty("links") @SerialName("links") val links: List, ) /** Metadata for a specific episode and how to display it. */ + @Serializable data class DownloadEpisodeMetadata( - @JsonProperty("id") val id: Int, - @JsonProperty("parentId") val parentId: Int, - @JsonProperty("mainName") val mainName: String, - @JsonProperty("sourceApiName") val sourceApiName: String?, - @JsonProperty("poster") val poster: String?, - @JsonProperty("name") val name: String?, - @JsonProperty("season") val season: Int?, - @JsonProperty("episode") val episode: Int?, - @JsonProperty("type") val type: TvType?, + @JsonProperty("id") @SerialName("id") val id: Int, + @JsonProperty("parentId") @SerialName("parentId") val parentId: Int, + @JsonProperty("mainName") @SerialName("mainName") val mainName: String, + @JsonProperty("sourceApiName") @SerialName("sourceApiName") val sourceApiName: String?, + @JsonProperty("poster") @SerialName("poster") val poster: String?, + @JsonProperty("name") @SerialName("name") val name: String?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("type") @SerialName("type") val type: TvType?, ) - + @Serializable data class DownloadedFileInfo( - @JsonProperty("totalBytes") val totalBytes: Long, - @JsonProperty("relativePath") val relativePath: String, - @JsonProperty("displayName") val displayName: String, - @JsonProperty("extraInfo") val extraInfo: String? = null, - @JsonProperty("basePath") val basePath: String? = null, // null is for legacy downloads. See getBasePath() + @JsonProperty("totalBytes") @SerialName("totalBytes") val totalBytes: Long, + @JsonProperty("relativePath") @SerialName("relativePath") val relativePath: String, + @JsonProperty("displayName") @SerialName("displayName") val displayName: String, + @JsonProperty("extraInfo") @SerialName("extraInfo") val extraInfo: String? = null, + @JsonProperty("basePath") @SerialName("basePath") val basePath: String? = null, // null is for legacy downloads. See getBasePath() // Hash of the link associated with this DownloadFile, used so not override old data in the DownloadedFileInfo - @JsonProperty("linkHash") val linkHash : Int? = null + @JsonProperty("linkHash") @SerialName("linkHash") val linkHash: Int? = null, ) + @Serializable + @SkipSerializationTest // Uri has issues with Jackson data class DownloadedFileInfoResult( - @JsonProperty("fileLength") val fileLength: Long, - @JsonProperty("totalBytes") val totalBytes: Long, - @JsonProperty("path") val path: Uri, + @JsonProperty("fileLength") @SerialName("fileLength") val fileLength: Long, + @JsonProperty("totalBytes") @SerialName("totalBytes") val totalBytes: Long, + @JsonProperty("path") @SerialName("path") + @Serializable(with = UriSerializer::class) + val path: Uri, ) - + @Serializable data class ResumeWatching( - @JsonProperty("parentId") val parentId: Int, - @JsonProperty("episodeId") val episodeId: Int?, - @JsonProperty("episode") val episode: Int?, - @JsonProperty("season") val season: Int?, - @JsonProperty("updateTime") val updateTime: Long, - @JsonProperty("isFromDownload") val isFromDownload: Boolean, + @JsonProperty("parentId") @SerialName("parentId") val parentId: Int, + @JsonProperty("episodeId") @SerialName("episodeId") val episodeId: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("updateTime") @SerialName("updateTime") val updateTime: Long, + @JsonProperty("isFromDownload") @SerialName("isFromDownload") val isFromDownload: Boolean, ) - data class DownloadStatus( /** if you should retry with the same args and hope for a better result */ val retrySame: Boolean, @@ -164,20 +190,19 @@ object DownloadObjects { val success: Boolean, ) - data class CreateNotificationMetadata( val type: VideoDownloadManager.DownloadType, val bytesDownloaded: Long, val bytesTotal: Long, val hlsProgress: Long? = null, val hlsTotal: Long? = null, - val bytesPerSecond: Long + val bytesPerSecond: Long, ) data class StreamData( private val fileLength: Long, val file: SafeFile, - //val fileStream: OutputStream, + // val fileStream: OutputStream, ) { @Throws(IOException::class) fun open(): OutputStream { @@ -198,9 +223,11 @@ object DownloadObjects { val exists: Boolean get() = file.exists() == true } - - /** bytes have the size end-start where the byte range is [start,end) - * note that ByteArray is a pointer and therefore cant be stored without cloning it */ + /** + * Bytes have the size end-start where the byte range is [start,end) + * note that ByteArray is a pointer and therefore can't be stored + * without cloning it. + */ data class LazyStreamDownloadResponse( val bytes: ByteArray, val startByte: Long, @@ -221,4 +248,4 @@ object DownloadObjects { return Objects.hash(startByte, endByte) } } -} \ No newline at end of file +} From e75b5e64a7ea49498d87208542bb38ce468db152 Mon Sep 17 00:00:00 2001 From: Kraptor123 <89366989+Kraptor123@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:52:23 +0300 Subject: [PATCH 045/105] shortcode: Add py.md as alternative to cutt.ly (#3001) --- .../cloudstream3/plugins/RepositoryManager.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index 5868146c7..7fec67637 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -141,11 +141,19 @@ object RepositoryManager { } } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { safeAsync { - val response = app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false) - val url = response.headers["Location"] ?: return@safeAsync null - if (url.startsWith("https://cutt.ly/404")) return@safeAsync null - if (url.removeSuffix("/") == "https://cutt.ly") return@safeAsync null - return@safeAsync url + if (fixedUrl.startsWith("!")) { + val response = app.get("https://py.md/${fixedUrl.removePrefix("!")}", allowRedirects = false) + val url = response.headers["Location"] ?: return@safeAsync null + if (url.startsWith("https://py.md/404")) return@safeAsync null + if (url.removeSuffix("/") == "https://py.md") return@safeAsync null + return@safeAsync url + } else { + val response = app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false) + val url = response.headers["Location"] ?: return@safeAsync null + if (url.startsWith("https://cutt.ly/404")) return@safeAsync null + if (url.removeSuffix("/") == "https://cutt.ly") return@safeAsync null + return@safeAsync url + } } } else null } From 5b0c5afebad244dded6b47467339b4d0befefeca Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:53:36 -0600 Subject: [PATCH 046/105] EmturbovidExtractor: replace selectXpath (#2977) This will be necessary when we finally fully migrate to ksoup, which currently doesn't have selectXpath, but for our usages that is not necessary. This way is also more consistent with how we do it everywhere else. --- .../extractors/EmturbovidExtractor.kt | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt index f4a5cdb2b..ca821b21c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt @@ -8,35 +8,33 @@ import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.newExtractorLink open class EmturbovidExtractor : ExtractorApi() { - override var name = "Emturbovid" - override var mainUrl = "https://emturbovid.com" + override val name = "Emturbovid" + override val mainUrl = "https://emturbovid.com" override val requiresReferer = false override suspend fun getUrl(url: String, referer: String?): List? { - val response = app.get( - url, referer = referer ?: "$mainUrl/" - ) - val playerScript = - response.document.selectXpath("//script[contains(text(),'var urlPlay')]") - .html() + val response = app.get(url, referer = referer ?: "$mainUrl/") + val playerScript = response.document + .select("script") + .first { it.data().contains("var urlPlay") } + .html() val sources = mutableListOf() if (playerScript.isNotBlank()) { - val m3u8Url = - playerScript.substringAfter("var urlPlay = '").substringBefore("'") - + val m3u8Url = playerScript.substringAfter("var urlPlay = '").substringBefore("'") sources.add( newExtractorLink( source = name, name = name, url = m3u8Url, - type = ExtractorLinkType.M3U8 + type = ExtractorLinkType.M3U8, ) { this.referer = "$mainUrl/" this.quality = Qualities.Unknown.value } ) } + return sources } -} \ No newline at end of file +} From 8c3dbdc5b68be080715e8b3c6676cfadd03f51df Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:54:15 -0600 Subject: [PATCH 047/105] Remove fuzzywuzzy from library (#2999) It doesn't use api(), which means extensions don't inherently have access, and they must already have the dependency explicit to use it. It being in app prevents runtime errors if they use it, but it being an implimentation in the library should be completely unused and unnecessary. --- library/build.gradle.kts | 3 --- 1 file changed, 3 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 9b769d1e5..979e09844 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -69,9 +69,6 @@ kotlin { implementation(libs.rhino) // Run JavaScript implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit implementation(libs.bundles.cryptography) // Cryptography - - // Deprecated; will be removed once extensions have time to migrate from using it - implementation("me.xdrop:fuzzywuzzy:1.4.0") } commonTest.dependencies { From 3e17b3a5a01b973cd135db259c025c998b7c2680 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:55:35 -0600 Subject: [PATCH 048/105] Make some private cryptography methods suspend (#3040) --- .../lagradost/cloudstream3/extractors/ByseSX.kt | 6 +++--- .../cloudstream3/extractors/Rabbitstream.kt | 16 ++++++++-------- .../cloudstream3/extractors/helper/GogoHelper.kt | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt index 248b94a73..5248a8861 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt @@ -86,15 +86,15 @@ open class ByseSX : ExtractorApi() { } @OptIn(DelicateCryptographyApi::class) - private fun decryptPlayback(playback: Playback): String? { + private suspend fun decryptPlayback(playback: Playback): String? { val keyBytes = buildAesKey(playback) val ivBytes = b64UrlDecode(playback.iv) val cipherBytes = b64UrlDecode(playback.payload) - val aesKey = aesGcm.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes) + val aesKey = aesGcm.keyDecoder().decodeFromByteArray(AES.Key.Format.RAW, keyBytes) // 128-bit GCM tag (default) val cipher = aesKey.cipher() - val plainBytes = cipher.decryptWithIvBlocking(ivBytes, cipherBytes) + val plainBytes = cipher.decryptWithIv(ivBytes, cipherBytes) var jsonStr = plainBytes.decodeToString() if (jsonStr.startsWith("\uFEFF")) jsonStr = jsonStr.substring(1) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt index fe9624430..fdd559258 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -133,12 +133,12 @@ open class Rabbitstream : ExtractorApi() { return extractedKey to sources } - private inline fun decryptMapped(input: String, key: String): T? { + private inline suspend fun decryptMapped(input: String, key: String): T? { val decrypt = decrypt(input, key) return AppUtils.tryParseJson(decrypt) } - private fun decrypt(input: String, key: String): String { + private suspend fun decrypt(input: String, key: String): String { return decryptSourceUrl( generateKey( salt = base64DecodeArray(input).copyOfRange(8, 16), @@ -147,7 +147,7 @@ open class Rabbitstream : ExtractorApi() { ) } - private fun generateKey(salt: ByteArray, secret: ByteArray): ByteArray { + private suspend fun generateKey(salt: ByteArray, secret: ByteArray): ByteArray { var key = md5(secret + salt) var currentKey = key while (currentKey.size < 48) { @@ -157,18 +157,18 @@ open class Rabbitstream : ExtractorApi() { return currentKey } - private fun md5(input: ByteArray): ByteArray = - md5Hasher.hashBlocking(input) + private suspend fun md5(input: ByteArray): ByteArray = + md5Hasher.hash(input) @OptIn(DelicateCryptographyApi::class) - private fun decryptSourceUrl(decryptionKey: ByteArray, sourceUrl: String): String { + private suspend fun decryptSourceUrl(decryptionKey: ByteArray, sourceUrl: String): String { val cipherData = base64DecodeArray(sourceUrl) val encrypted = cipherData.copyOfRange(16, cipherData.size) val keyBytes = decryptionKey.copyOfRange(0, 32) val ivBytes = decryptionKey.copyOfRange(32, decryptionKey.size) - val aesKey = aesCbc.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes) - val decryptedData = aesKey.cipher(padding = true).decryptWithIvBlocking(ivBytes, encrypted) + val aesKey = aesCbc.keyDecoder().decodeFromByteArray(AES.Key.Format.RAW, keyBytes) + val decryptedData = aesKey.cipher(padding = true).decryptWithIv(ivBytes, encrypted) return decryptedData.decodeToString() } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt index 477c965ce..86d402002 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt @@ -39,7 +39,7 @@ object GogoHelper { // https://github.com/saikou-app/saikou/blob/45d0a99b8a72665a29a1eadfb38c506b842a29d7/app/src/main/java/ani/saikou/parsers/anime/extractors/GogoCDN.kt#L97 // No Licence on the function @OptIn(DelicateCryptographyApi::class) - private fun cryptoHandler( + private suspend fun cryptoHandler( string: String, iv: String, secretKeyString: String, @@ -47,14 +47,14 @@ object GogoHelper { ): String { val ivBytes = iv.encodeToByteArray() val keyBytes = secretKeyString.encodeToByteArray() - val aesKey = aesCbc.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes) + val aesKey = aesCbc.keyDecoder().decodeFromByteArray(AES.Key.Format.RAW, keyBytes) val cipher = aesKey.cipher(padding = true) return if (!encrypt) { - val plainBytes = cipher.decryptWithIvBlocking(ivBytes, base64DecodeArray(string)) + val plainBytes = cipher.decryptWithIv(ivBytes, base64DecodeArray(string)) plainBytes.decodeToString() } else { - base64Encode(cipher.encryptWithIvBlocking(ivBytes, string.encodeToByteArray())) + base64Encode(cipher.encryptWithIv(ivBytes, string.encodeToByteArray())) } } From 1463cf40cd9c5ad94e089d4107a29895e3fa71dc Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:57:27 +0000 Subject: [PATCH 049/105] remove prerelease annotations (#3046) --- .../com/lagradost/cloudstream3/network/RequestsHelper.kt | 2 -- .../commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 5 ----- .../kotlin/com/lagradost/cloudstream3/MainActivity.kt | 1 - .../com/lagradost/cloudstream3/extractors/DoodExtractor.kt | 1 - .../com/lagradost/cloudstream3/extractors/Firestream.kt | 1 - .../kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt | 1 - .../lagradost/cloudstream3/extractors/StreamWishExtractor.kt | 1 - .../com/lagradost/cloudstream3/extractors/Streamcash.kt | 1 - .../kotlin/com/lagradost/cloudstream3/extractors/Vids.kt | 1 - .../lagradost/cloudstream3/extractors/helper/AesHelper.kt | 1 - .../cloudstream3/extractors/helper/JWPlayerHelper.kt | 1 - .../kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt | 1 - .../kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt | 4 ---- .../kotlin/com/lagradost/cloudstream3/utils/Levenshtein.kt | 1 - .../kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt | 2 -- .../cloudstream3/utils/serializers/NonEmptySerializer.kt | 1 - .../cloudstream3/utils/serializers/WriteOnlySerializer.kt | 1 - 17 files changed, 26 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt index 6234297d0..203a503e9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -22,7 +22,6 @@ fun Requests.initClient(context: Context) { } /** Only use ignoreSSL if you know what you are doing*/ -@Prerelease fun Requests.initClient(context: Context, ignoreSSL: Boolean = false) { this.baseClient = buildDefaultClient(context, ignoreSSL) } @@ -34,7 +33,6 @@ fun buildDefaultClient(context: Context): OkHttpClient { } /** Only use ignoreSSL if you know what you are doing*/ -@Prerelease fun buildDefaultClient(context: Context, ignoreSSL: Boolean = false): OkHttpClient { safe { Security.insertProviderAt(Conscrypt.newProvider(), 1) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 53b773e59..fc860c06b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -97,7 +97,6 @@ class ErrorLoadingException(message: String? = null) : Exception(message) //val baseHeader = mapOf("User-Agent" to USER_AGENT) -@Prerelease val json = Json { encodeDefaults = true explicitNulls = false @@ -1094,7 +1093,6 @@ enum class TvType(value: Int?) { Audio(16), Podcast(17), - @Prerelease Video(18), } @@ -2554,12 +2552,10 @@ fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") { }.onFailure { logError(it) }.getOrNull() } -@Prerelease fun Episode.addDate(date: LocalDate?) { this.date = date?.atStartOfDayIn(TimeZone.currentSystemDefault())?.toEpochMilliseconds() } -@Prerelease fun Episode.addDate(date: Instant?) { this.date = date?.toEpochMilliseconds() } @@ -2706,7 +2702,6 @@ fun fetchUrls(text: String?): List { return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList() } -@Prerelease fun isUpcoming(dateString: String?): Boolean { return runCatching { val fmt = DateTimeComponents.Format { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainActivity.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainActivity.kt index 127b075da..dc8317fa0 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainActivity.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainActivity.kt @@ -33,7 +33,6 @@ var app = Requests(responseParser = jsonResponseParser).apply { /** Same as the default app networking helper, but this instance ignores SSL certificates. * This should NEVER be used for sensitive networking operations such as logins. Only use this when required. */ -@Prerelease @UnsafeSSL var insecureApp = Requests(responseParser = jsonResponseParser).apply { defaultHeaders = mapOf("user-agent" to USER_AGENT) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 099406f16..9117d1d78 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -88,7 +88,6 @@ class MyVidPlay : DoodLaExtractor() { override var mainUrl = "https://myvidplay.com" } -@Prerelease class Playmogo : DoodLaExtractor() { override var mainUrl = "https://playmogo.com" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt index c8ef5d0ac..c2fbaeee2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt @@ -9,7 +9,6 @@ import com.lagradost.cloudstream3.utils.newExtractorLink import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@Prerelease class Firestream : ExtractorApi() { override val name: String = "Firestream" override val mainUrl: String = "https://firestream.to" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt index eb6d474a5..50042bd19 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt @@ -10,7 +10,6 @@ import com.lagradost.cloudstream3.utils.newExtractorLink import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@Prerelease open class Flyfile : ExtractorApi() { override val name: String = "FlyFile" override val mainUrl: String = "https://flyfile.app" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamWishExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamWishExtractor.kt index 58aa25c8c..03c94db9d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamWishExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamWishExtractor.kt @@ -28,7 +28,6 @@ class Ewish : StreamWishExtractor() { override val mainUrl = "https://embedwish.com" } -@Prerelease class Hgcloudto : StreamWishExtractor() { override val name = "Hgcloud" override val mainUrl = "https://Hgcloud.to" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt index 2775c3062..91ccfb2ad 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamcash.kt @@ -7,7 +7,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.newExtractorLink -@Prerelease open class Streamcash: ExtractorApi() { override val name: String = "Streamcash" override val mainUrl: String = "https://streamcash.to" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt index 1312570d3..9eb01e321 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vids.kt @@ -7,7 +7,6 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.newExtractorLink -@Prerelease open class Vids : ExtractorApi() { override val name: String = "Vids" override val mainUrl: String = "https://vids.st" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt index a09159245..1d2b6bdf3 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt @@ -21,7 +21,6 @@ object AesHelper { private val md5Hasher = provider.get(MD5).hasher() @OptIn(DelicateCryptographyApi::class) - @Prerelease suspend fun cryptoAESHandler( data: String, pass: ByteArray, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt index c9ba4a624..b026fa237 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt @@ -13,7 +13,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.collections.orEmpty -@Prerelease object JwPlayerHelper { private val sourceRegex = Regex(""""?sources"?:\s*(\[.*?\])""") private val tracksRegex = Regex(""""?tracks"?:\s*(\[.*?\])""") diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index 444709f88..37cda4124 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -68,7 +68,6 @@ object Coroutines { * If you want to iterate over the list then you need to do: * list.withLock { code here } */ - @Prerelease fun atomicListOf(vararg items: T): AtomicMutableList { return AtomicMutableList(items.toMutableList()) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt index 39d950ed1..a8a6344aa 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/JsInterpreter.kt @@ -86,7 +86,6 @@ private const val JS_DEFAULT_MAX_INSTRUCTIONS: Long = 50_000_000L * Convert any JS runtime value to its JavaScript string representation. * Mirrors what JS `String(value)` would produce. */ -@Prerelease fun jsValueToString(v: Any?): String = toJsString(v) /** @@ -106,7 +105,6 @@ fun jsValueToString(v: Any?): String = toJsString(v) * @param scope the [CoroutineScope] this context's cancellation is tied to. Supplied * automatically by [newJsContext] from the caller's own coroutine context. */ -@Prerelease class JsContext internal constructor( var maxExecutionTime: Duration = JS_DEFAULT_MAX_EXECUTION_TIME, var maxInstructions: Long = JS_DEFAULT_MAX_INSTRUCTIONS, @@ -160,7 +158,6 @@ class JsContext internal constructor( * eval("x + 1") * } */ -@Prerelease suspend fun newJsContext( initializer: suspend JsContext.() -> Unit = {}, ): JsContext { @@ -188,7 +185,6 @@ suspend fun newJsContext( * Returns [Unit] on evaluation failure, timeout, or when the result is JS undefined. * JS null is represented as Kotlin null. Use [jsValueToString] to convert to a JS string. */ -@Prerelease @Throws(CancellationException::class) suspend fun evalJs( js: String, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Levenshtein.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Levenshtein.kt index 2f3957630..82fb3af23 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Levenshtein.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Levenshtein.kt @@ -28,7 +28,6 @@ import com.lagradost.cloudstream3.Prerelease import kotlin.math.round // Taken from https://github.com/terrakok/FuzzyKot/blob/f794d43/fuzzykot/src/commonMain/kotlin/com/github/terrakok/fuzzykot/Levenshtein.kt -@Prerelease object Levenshtein { fun ratio(s1: String, s2: String, processor: (String) -> String = { it }): Int { val p1 = processor(s1) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt index 64815f794..8b2fa752e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt @@ -5,12 +5,10 @@ import io.ktor.http.decodeURLQueryComponent import io.ktor.http.encodeURLParameter object StringUtils { - @Prerelease fun String.decodeUrl(): String { return this.decodeURLQueryComponent() } - @Prerelease fun String.encodeUrl(): String { return this.encodeURLParameter() } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt index 82de9f7f7..62b306e1f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt @@ -28,7 +28,6 @@ import kotlinx.serialization.json.JsonTransformingSerializer * object Serializer : NonEmptySerializer(MyData.generatedSerializer()) * } */ -@Prerelease abstract class NonEmptySerializer(tSerializer: KSerializer) : JsonTransformingSerializer(tSerializer) { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt index c7f412eaa..1d06b7fca 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt @@ -25,7 +25,6 @@ import kotlinx.serialization.json.JsonTransformingSerializer * ) * } */ -@Prerelease abstract class WriteOnlySerializer( tSerializer: KSerializer, private val keysToIgnore: Set, From 0af9b7f569fa0bc177ee04d1c3a38848ee4f4151 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:59:59 -0600 Subject: [PATCH 050/105] DataStoreHelper: migrate to kotlinx serialization (#3027) --- .../cloudstream3/utils/DataStoreHelper.kt | 326 ++++++++++-------- 1 file changed, 185 insertions(+), 141 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 19caead21..ec896facb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.CloudStreamApp.Companion.context @@ -31,6 +32,12 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia import com.lagradost.cloudstream3.utils.downloader.DownloadObjects +import com.lagradost.cloudstream3.utils.serializers.WriteOnlySerializer +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KeepGeneratedSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import java.util.Calendar import java.util.Date import java.util.GregorianCalendar @@ -43,17 +50,18 @@ const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" const val RESULT_FAVORITES_STATE_DATA = "result_favorites_state_data" -const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes +const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // Changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" const val RESULT_EPISODE = "result_episode" const val RESULT_SEASON = "result_season" const val RESULT_DUB = "result_dub" const val KEY_RESULT_SORT = "result_sort" -const val USER_PINNED_PROVIDERS = "user_pinned_providers" //key for pinned user set +const val USER_PINNED_PROVIDERS = "user_pinned_providers" // Key for pinned user set class UserPreferenceDelegate( - private val key: String, private val default: T //, private val klass: KClass + private val key: String, + private val default: T, ) { private val klass: KClass = default::class private val realKey get() = "${DataStoreHelper.currentAccount}/$key" @@ -63,7 +71,7 @@ class UserPreferenceDelegate( operator fun setValue( self: Any?, property: KProperty<*>, - t: T? + t: T?, ) { if (t == null) { removeKey(realKey) @@ -82,7 +90,7 @@ object DataStoreHelper { R.drawable.profile_bg_pink, R.drawable.profile_bg_purple, R.drawable.profile_bg_red, - R.drawable.profile_bg_teal + R.drawable.profile_bg_teal, ) private var searchPreferenceProvidersStrings: List by UserPreferenceDelegate( @@ -112,16 +120,17 @@ object DataStoreHelper { private var searchPreferenceTagsStrings: List by UserPreferenceDelegate( "search_pref_tags", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) + var searchPreferenceTags: List get() = deserializeTv(searchPreferenceTagsStrings) set(value) { searchPreferenceTagsStrings = serializeTv(value) } - private var homePreferenceStrings: List by UserPreferenceDelegate( "home_pref_homepage", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) + var homePreference: List get() = deserializeTv(homePreferenceStrings) set(value) { @@ -132,38 +141,38 @@ object DataStoreHelper { "home_bookmarked_last_list", IntArray(0) ) + var playBackSpeed: Float by UserPreferenceDelegate("playback_speed", 1.0f) var resizeMode: Int by UserPreferenceDelegate("resize_mode", 0) var librarySortingMode: Int by UserPreferenceDelegate( "library_sorting_mode", ListSorting.AlphabeticalA.ordinal ) + private var _resultsSortingMode: Int by UserPreferenceDelegate( "results_sorting_mode", EpisodeSortType.NUMBER_ASC.ordinal ) + var resultsSortingMode: EpisodeSortType get() = EpisodeSortType.entries.getOrNull(_resultsSortingMode) ?: EpisodeSortType.NUMBER_ASC set(value) { _resultsSortingMode = value.ordinal } + @Serializable data class Account( - @JsonProperty("keyIndex") - val keyIndex: Int, - @JsonProperty("name") - val name: String, - @JsonProperty("customImage") - val customImage: String? = null, - @JsonProperty("defaultImageIndex") - val defaultImageIndex: Int, - @JsonProperty("lockPin") - val lockPin: String? = null, + @JsonProperty("keyIndex") @SerialName("keyIndex") val keyIndex: Int, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("customImage") @SerialName("customImage") val customImage: String? = null, + @JsonProperty("defaultImageIndex") @SerialName("defaultImageIndex") val defaultImageIndex: Int, + @JsonProperty("lockPin") @SerialName("lockPin") val lockPin: String? = null, ) { - val image - get() = customImage?.let { UiImage.Image(it) } ?: profileImages.getOrNull( - defaultImageIndex - )?.let { UiImage.Drawable(it) } ?: UiImage.Drawable(profileImages.first()) + @get:JsonIgnore + val image get() = customImage?.let { UiImage.Image(it) } ?: + profileImages.getOrNull(defaultImageIndex)?.let { + UiImage.Drawable(it) + } ?: UiImage.Drawable(profileImages.first()) } const val TAG = "data_store_helper" @@ -188,7 +197,6 @@ object DataStoreHelper { fun setAccount(account: Account) { val homepage = currentHomePage - selectedKeyIndex = account.keyIndex AccountManager.updateAccountIds() showToast(context?.getString(R.string.logged_account, account.name) ?: account.name) @@ -206,7 +214,7 @@ object DataStoreHelper { currentAccounts.getOrNull(currentAccounts.indexOfFirst { it.keyIndex == 0 }) ?: Account( keyIndex = 0, name = context.getString(R.string.default_account), - defaultImageIndex = 0 + defaultImageIndex = 0, ) } } @@ -232,18 +240,21 @@ object DataStoreHelper { } } + @Serializable data class PosDur( - @JsonProperty("position") val position: Long, - @JsonProperty("duration") val duration: Long + @JsonProperty("position") @SerialName("position") val position: Long, + @JsonProperty("duration") @SerialName("duration") val duration: Long, ) fun PosDur.fixVisual(): PosDur { if (duration <= 0) return PosDur(0, duration) val percentage = position * 100 / duration - if (percentage <= 1) return PosDur(0, duration) - if (percentage <= 5) return PosDur(5 * duration / 100, duration) - if (percentage >= 95) return PosDur(duration, duration) - return this + return when { + percentage <= 1 -> PosDur(0, duration) + percentage <= 5 -> PosDur(5 * duration / 100, duration) + percentage >= 95 -> PosDur(duration, duration) + else -> this + } } fun Int.toYear(): Date = @@ -251,28 +262,38 @@ object DataStoreHelper { /** * Used to display notifications on new episodes and posters in library. - **/ + */ + @Serializable abstract class LibrarySearchResponse( - @JsonProperty("id") override var id: Int?, - @JsonProperty("latestUpdatedTime") open val latestUpdatedTime: Long, - @JsonProperty("name") override val name: String, - @JsonProperty("url") override val url: String, - @JsonProperty("apiName") override val apiName: String, - @JsonProperty("type") override var type: TvType?, - @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("year") open val year: Int?, - @JsonProperty("syncData") open val syncData: Map?, - @JsonProperty("quality") override var quality: SearchQuality?, - @JsonProperty("posterHeaders") override var posterHeaders: Map?, - @JsonProperty("plot") open val plot: String? = null, - @JsonProperty("score") override var score: Score? = null, - @JsonProperty("tags") open val tags: List? = null, + /** + * These fields are marked @Transient because this class is only ever serialized through + * through its subclasses, which redeclare each property with their own @SerialName + * annotations. Without @Transient here, kotlinx.serialization would try to + * generate a serializer for the abstract base class itself (or double-serialize + * these fields), which fails/conflicts since these are meant to be overridden, + * not serialized directly from the parent. + */ + @Transient override var id: Int? = null, + @Transient open val latestUpdatedTime: Long = 0L, + @Transient override val name: String = "", + @Transient override val url: String = "", + @Transient override val apiName: String = "", + @Transient override var type: TvType? = null, + @Transient override var posterUrl: String? = null, + @Transient open val year: Int? = null, + @Transient open val syncData: Map? = null, + @Transient override var quality: SearchQuality? = null, + @Transient override var posterHeaders: Map? = null, + @Transient open val plot: String? = null, + @Transient override var score: Score? = null, + @Transient open val tags: List? = null, ) : SearchResponse { @JsonProperty("rating", access = JsonProperty.Access.WRITE_ONLY) + @SerialName("rating") @Deprecated( "`rating` is the old scoring system, use score instead", replaceWith = ReplaceWith("score"), - level = DeprecationLevel.ERROR + level = DeprecationLevel.ERROR, ) var rating: Int? = null set(value) { @@ -283,23 +304,26 @@ object DataStoreHelper { } } + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = SubscribedData.Serializer::class) data class SubscribedData( - @JsonProperty("subscribedTime") val subscribedTime: Long, - @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, - override var id: Int?, - override val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override val year: Int?, - override val syncData: Map? = null, - override var quality: SearchQuality? = null, - override var posterHeaders: Map? = null, - override val plot: String? = null, - override var score: Score? = null, - override val tags: List? = null, + @JsonProperty("subscribedTime") @SerialName("subscribedTime") val subscribedTime: Long, + @JsonProperty("lastSeenEpisodeCount") @SerialName("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("latestUpdatedTime") @SerialName("latestUpdatedTime") override val latestUpdatedTime: Long, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType?, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("year") @SerialName("year") override val year: Int?, + @JsonProperty("syncData") @SerialName("syncData") override val syncData: Map? = null, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("plot") @SerialName("plot") override val plot: String? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, + @JsonProperty("tags") @SerialName("tags") override val tags: List? = null, ) : LibrarySearchResponse( id, latestUpdatedTime, @@ -314,8 +338,13 @@ object DataStoreHelper { posterHeaders, plot, score, - tags + tags, ) { + object Serializer : WriteOnlySerializer( + SubscribedData.generatedSerializer(), + setOf("rating"), + ) + fun toLibraryItem(): SyncAPI.LibraryItem? { return SyncAPI.LibraryItem( name, @@ -334,27 +363,30 @@ object DataStoreHelper { this.id, plot = this.plot, score = this.score, - tags = this.tags + tags = this.tags, ) } } + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = BookmarkedData.Serializer::class) data class BookmarkedData( - @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, - override var id: Int?, - override val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override val year: Int?, - override val syncData: Map? = null, - override var quality: SearchQuality? = null, - override var posterHeaders: Map? = null, - override val plot: String? = null, - override var score: Score? = null, - override val tags: List? = null, + @JsonProperty("bookmarkedTime") @SerialName("bookmarkedTime") val bookmarkedTime: Long, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("latestUpdatedTime") @SerialName("latestUpdatedTime") override val latestUpdatedTime: Long, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType?, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("year") @SerialName("year") override val year: Int?, + @JsonProperty("syncData") @SerialName("syncData") override val syncData: Map? = null, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("plot") @SerialName("plot") override val plot: String? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, + @JsonProperty("tags") @SerialName("tags") override val tags: List? = null, ) : LibrarySearchResponse( id, latestUpdatedTime, @@ -367,8 +399,13 @@ object DataStoreHelper { syncData, quality, posterHeaders, - plot + plot, ) { + object Serializer : WriteOnlySerializer( + BookmarkedData.generatedSerializer(), + setOf("rating"), + ) + fun toLibraryItem(id: String): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( name, @@ -387,27 +424,30 @@ object DataStoreHelper { this.id, plot = this.plot, score = this.score, - tags = this.tags + tags = this.tags, ) } } + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = FavoritesData.Serializer::class) data class FavoritesData( - @JsonProperty("favoritesTime") val favoritesTime: Long, - override var id: Int?, - override val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override val year: Int?, - override val syncData: Map? = null, - override var quality: SearchQuality? = null, - override var posterHeaders: Map? = null, - override val plot: String? = null, - override var score: Score? = null, - override val tags: List? = null, + @JsonProperty("favoritesTime") @SerialName("favoritesTime") val favoritesTime: Long, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("latestUpdatedTime") @SerialName("latestUpdatedTime") override val latestUpdatedTime: Long, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType?, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("year") @SerialName("year") override val year: Int?, + @JsonProperty("syncData") @SerialName("syncData") override val syncData: Map? = null, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("plot") @SerialName("plot") override val plot: String? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, + @JsonProperty("tags") @SerialName("tags") override val tags: List? = null, ) : LibrarySearchResponse( id, latestUpdatedTime, @@ -420,8 +460,13 @@ object DataStoreHelper { syncData, quality, posterHeaders, - plot + plot, ) { + object Serializer : WriteOnlySerializer( + FavoritesData.generatedSerializer(), + setOf("rating"), + ) + fun toLibraryItem(): SyncAPI.LibraryItem? { return SyncAPI.LibraryItem( name, @@ -440,31 +485,32 @@ object DataStoreHelper { this.id, plot = this.plot, score = this.score, - tags = this.tags + tags = this.tags, ) } } + @Serializable data class ResumeWatchingResult( - @JsonProperty("name") override val name: String, - @JsonProperty("url") override val url: String, - @JsonProperty("apiName") override val apiName: String, - @JsonProperty("type") override var type: TvType? = null, - @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("watchPos") val watchPos: PosDur?, - @JsonProperty("id") override var id: Int?, - @JsonProperty("parentId") val parentId: Int?, - @JsonProperty("episode") val episode: Int?, - @JsonProperty("season") val season: Int?, - @JsonProperty("isFromDownload") val isFromDownload: Boolean, - @JsonProperty("quality") override var quality: SearchQuality? = null, - @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, - @JsonProperty("score") override var score: Score? = null, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType? = null, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("watchPos") @SerialName("watchPos") val watchPos: PosDur?, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("parentId") @SerialName("parentId") val parentId: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("isFromDownload") @SerialName("isFromDownload") val isFromDownload: Boolean, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, ) : SearchResponse /** * A datastore wide account for future implementations of a multiple account system - **/ + */ fun getAllWatchStateIds(): List? { val folder = "$currentAccount/$RESULT_WATCH_STATE" @@ -500,7 +546,7 @@ object DataStoreHelper { } fun migrateResumeWatching() { - // if (getKey(RESULT_RESUME_WATCHING_HAS_MIGRATED, false) != true) { + // if (getKey(RESULT_RESUME_WATCHING_HAS_MIGRATED, false) != true) { setKey(RESULT_RESUME_WATCHING_HAS_MIGRATED, true) getAllResumeStateIdsOld()?.forEach { id -> getLastWatchedOld(id)?.let { @@ -510,12 +556,12 @@ object DataStoreHelper { it.episode, it.season, it.isFromDownload, - it.updateTime + it.updateTime, ) removeLastWatchedOld(it.parentId) } } - //} + // } } fun setLastWatched( @@ -536,7 +582,7 @@ object DataStoreHelper { episode, season, updateTime ?: System.currentTimeMillis(), - isFromDownload + isFromDownload, ) ) } @@ -553,7 +599,7 @@ object DataStoreHelper { fun getLastWatched(id: Int?): DownloadObjects.ResumeWatching? { if (id == null) return null - return getKey( + return getKey( "$currentAccount/$RESULT_RESUME_WATCHING", id.toString(), ) @@ -561,7 +607,7 @@ object DataStoreHelper { private fun getLastWatchedOld(id: Int?): DownloadObjects.ResumeWatching? { if (id == null) return null - return getKey( + return getKey( "$currentAccount/$RESULT_RESUME_WATCHING_OLD", id.toString(), ) @@ -575,18 +621,18 @@ object DataStoreHelper { fun getBookmarkedData(id: Int?): BookmarkedData? { if (id == null) return null - return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) + return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) } fun getAllBookmarkedData(): List { return getKeys("$currentAccount/$RESULT_WATCH_STATE_DATA")?.mapNotNull { - getKey(it) + getKey(it) } ?: emptyList() } fun getAllSubscriptions(): List { return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull { - getKey(it) + getKey(it) } ?: emptyList() } @@ -598,12 +644,12 @@ object DataStoreHelper { /** * Set new seen episodes and update time - **/ + */ fun updateSubscribedData(id: Int?, data: SubscribedData?, episodeResponse: EpisodeResponse?) { if (id == null || data == null || episodeResponse == null) return val newData = data.copy( latestUpdatedTime = unixTimeMS, - lastSeenEpisodeCount = episodeResponse.getLatestEpisodes() + lastSeenEpisodeCount = episodeResponse.getLatestEpisodes(), ) setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), newData) } @@ -616,12 +662,12 @@ object DataStoreHelper { fun getSubscribedData(id: Int?): SubscribedData? { if (id == null) return null - return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) + return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) } fun getAllFavorites(): List { return getKeys("$currentAccount/$RESULT_FAVORITES_STATE_DATA")?.mapNotNull { - getKey(it) + getKey(it) } ?: emptyList() } @@ -639,7 +685,7 @@ object DataStoreHelper { fun getFavoritesData(id: Int?): FavoritesData? { if (id == null) return null - return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) } fun setViewPos(id: Int?, pos: Long, dur: Long) { @@ -648,10 +694,10 @@ object DataStoreHelper { setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur)) } - /** Sets the position, duration, and resume data of an episode/movie, - * - * if nextEpisode is not specified it will not be able to set the next episode as resumable if progress > NEXT_WATCH_EPISODE_PERCENTAGE - * */ + /** + * Sets the position, duration, and resume data of an episode/movie, + * If nextEpisode is not specified it will not be able to set the next episode as resumable if progress > NEXT_WATCH_EPISODE_PERCENTAGE + */ fun setViewPosAndResume(id: Int?, position: Long, duration: Long, currentEpisode: Any?, nextEpisode: Any?) { setViewPos(id, position, duration) if (id != null) { @@ -687,7 +733,7 @@ object DataStoreHelper { resumeMeta.id, resumeMeta.episode, resumeMeta.season, - isFromDownload = false + isFromDownload = false, ) } @@ -697,7 +743,7 @@ object DataStoreHelper { resumeMeta.id, resumeMeta.episode, resumeMeta.season, - isFromDownload = true + isFromDownload = true, ) } } @@ -706,17 +752,16 @@ object DataStoreHelper { fun getViewPos(id: Int?): PosDur? { if (id == null) return null - return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) + return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) } fun getVideoWatchState(id: Int?): VideoWatchState? { if (id == null) return null - return getKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), null) + return getKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), null) } fun setVideoWatchState(id: Int?, watchState: VideoWatchState) { if (id == null) return - // None == No key if (watchState == VideoWatchState.None) { removeKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString()) @@ -727,7 +772,7 @@ object DataStoreHelper { fun getDub(id: Int): DubStatus? { return DubStatus.entries - .getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1) + .getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1) } fun setDub(id: Int, status: DubStatus) { @@ -748,13 +793,13 @@ object DataStoreHelper { getKey( "$currentAccount/$RESULT_WATCH_STATE", id.toString(), - null + null, ) ) } fun getResultSeason(id: Int): Int? { - return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null) + return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null) } fun setResultSeason(id: Int, value: Int?) { @@ -762,7 +807,7 @@ object DataStoreHelper { } fun getResultEpisode(id: Int): Int? { - return getKey("$currentAccount/$RESULT_EPISODE", id.toString(), null) + return getKey("$currentAccount/$RESULT_EPISODE", id.toString(), null) } fun setResultEpisode(id: Int, value: Int?) { @@ -775,12 +820,11 @@ object DataStoreHelper { fun getSync(id: Int, idPrefixes: List): List { return idPrefixes.map { idPrefix -> - getKey("${idPrefix}_sync", id.toString()) + getKey("${idPrefix}_sync", id.toString()) } } var pinnedProviders: Array - get() = getKey(USER_PINNED_PROVIDERS) ?: emptyArray() + get() = getKey>(USER_PINNED_PROVIDERS) ?: emptyArray() set(value) = setKey(USER_PINNED_PROVIDERS, value) - } From f03b18ff9553ecdc31412b91e389f737d0c9e8d2 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:19:33 -0600 Subject: [PATCH 051/105] [skip ci] Enable some deprecations (#3048) --- .../kotlin/com/lagradost/cloudstream3/MainAPI.kt | 10 ++++------ .../cloudstream3/extractors/helper/AesHelper.kt | 5 ++--- .../com/lagradost/cloudstream3/utils/Coroutines.kt | 5 ++--- .../com/lagradost/cloudstream3/utils/StringUtils.kt | 10 ++++------ 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index fc860c06b..c29ef150f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -799,11 +799,10 @@ fun fixTitle(str: String): String { } } -// Deprecate after next stable -/* @Deprecated( +@Deprecated( message = "Use newJsContext or evalJs instead.", level = DeprecationLevel.WARNING, -) */ +) suspend fun getRhinoContext(): org.mozilla.javascript.Context { return Coroutines.mainWork { val rhino = org.mozilla.javascript.Context.enter() @@ -2560,11 +2559,10 @@ fun Episode.addDate(date: Instant?) { this.date = date?.toEpochMilliseconds() } -// Deprecate after next stable -/* @Deprecated( +@Deprecated( message = "Use addDate with LocalDate, Instant, or String instead.", level = DeprecationLevel.WARNING, -) */ +) fun Episode.addDate(date: java.util.Date?) { this.date = date?.time } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt index 1d2b6bdf3..4ae615846 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt @@ -46,11 +46,10 @@ object AesHelper { } } - // Deprecate after next stable - /* @Deprecated( + @Deprecated( message = "Set padding = false for no padding", level = DeprecationLevel.WARNING, - ) */ + ) fun cryptoAESHandler( data: String, pass: ByteArray, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index 37cda4124..4536eb88b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -72,11 +72,10 @@ object Coroutines { return AtomicMutableList(items.toMutableList()) } - // Deprecate after next stable - /*@Deprecated( + @Deprecated( message = "Use atomicListOf() instead.", replaceWith = ReplaceWith("atomicListOf(*items)"), level = DeprecationLevel.WARNING, - )*/ + ) fun threadSafeListOf(vararg items: T): MutableList = atomicListOf(*items) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt index 8b2fa752e..fc1055dc6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/StringUtils.kt @@ -13,20 +13,18 @@ object StringUtils { return this.encodeURLParameter() } - // Deprecate after next stable - - /* @Deprecated( + @Deprecated( message = "Use Ktor 'Url' naming convention instead.", replaceWith = ReplaceWith("this.encodeUrl()"), level = DeprecationLevel.WARNING, - ) */ + ) fun String.encodeUri(): String = encodeUrl() - /* @Deprecated( + @Deprecated( message = "Use Ktor 'Url' naming convention instead.", replaceWith = ReplaceWith("this.decodeUrl()"), level = DeprecationLevel.WARNING, - ) */ + ) fun String.decodeUri(): String = decodeUrl() } From 5cd1cb3433ab215392cc3346855790c902d4f1da Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:26:09 -0600 Subject: [PATCH 052/105] SimklApi: migrate to kotlinx serialization (#2998) --- .../syncproviders/providers/SimklApi.kt | 645 ++++++++++-------- 1 file changed, 350 insertions(+), 295 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 075c08bb8..a57ec6716 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -2,11 +2,11 @@ package com.lagradost.cloudstream3.syncproviders.providers import androidx.annotation.StringRes import androidx.core.net.toUri +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.BuildConfig -import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey @@ -32,7 +32,12 @@ import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.DataStoreHelper.toYear +import com.lagradost.cloudstream3.utils.serializers.NonEmptySerializer import com.lagradost.cloudstream3.utils.txt +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KeepGeneratedSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import java.math.BigInteger import java.security.SecureRandom import java.text.SimpleDateFormat @@ -45,22 +50,18 @@ import kotlin.time.DurationUnit import kotlin.time.toDuration class SimklApi : SyncAPI() { - override var name = "Simkl" + override val name = "Simkl" override val idPrefix = "simkl" - val key = "simkl-key" override val redirectUrlIdentifier = "simkl" override val hasOAuth2 = true override val hasPin = true override var requireLibraryRefresh = true - override var mainUrl = "https://api.simkl.com" + override val mainUrl = "https://api.simkl.com" override val icon = R.drawable.simkl_logo override val createAccountUrl = "$mainUrl/signup" override val syncIdName = SyncIdName.Simkl - /** Automatically adds simkl auth headers */ - // private val interceptor = HeaderInterceptor() - /** * This is required to override the reported last activity as simkl activites * may not always update based on testing. @@ -69,63 +70,99 @@ class SimklApi : SyncAPI() { private object SimklCache { private const val SIMKL_CACHE_KEY = "SIMKL_API_CACHE" - enum class CacheTimes(val value: String) { OneMonth("30d"), ThirtyMinutes("30m") } - private class SimklCacheWrapper( - @JsonProperty("obj") val obj: T?, - @JsonProperty("validUntil") val validUntil: Long, - @JsonProperty("cacheTime") val cacheTime: Long = APIHolder.unixTime, - ) { - /** Returns true if cache is newer than cacheDays */ - fun isFresh(): Boolean { - return validUntil > APIHolder.unixTime - } + @Serializable + private data class MediaObjectCacheEntry( + @JsonProperty("obj") @SerialName("obj") val obj: MediaObject?, + @JsonProperty("validUntil") @SerialName("validUntil") val validUntil: Long, + @JsonProperty("cacheTime") @SerialName("cacheTime") val cacheTime: Long = APIHolder.unixTime, + ) - fun remainingTime(): Duration { - val unixTime = APIHolder.unixTime - return if (validUntil > unixTime) { - (validUntil - unixTime).toDuration(DurationUnit.SECONDS) - } else { - Duration.ZERO - } + @Serializable + private data class EpisodesCacheEntry( + @JsonProperty("obj") @SerialName("obj") val obj: Array?, + @JsonProperty("validUntil") @SerialName("validUntil") val validUntil: Long, + @JsonProperty("cacheTime") @SerialName("cacheTime") val cacheTime: Long = APIHolder.unixTime, + ) + + /** + * Minimal class used only to peek at an entry's expiry, without caring which of the + * concrete entry types above actually produced it. + */ + @Serializable + private data class CacheFreshness( + @JsonProperty("validUntil") @SerialName("validUntil") val validUntil: Long, + ) + + private fun Long.isFresh(): Boolean = this > APIHolder.unixTime + + private fun Long.remaining(): Duration { + val unixTime = APIHolder.unixTime + return if (this > unixTime) { + (this - unixTime).toDuration(DurationUnit.SECONDS) + } else { + Duration.ZERO } } fun cleanOldCache() { getKeys(SIMKL_CACHE_KEY)?.forEach { - val isOld = CloudStreamApp.getKey>(it)?.isFresh() == false - if (isOld) { - removeKey(it) - } + val isOld = getKey(it)?.validUntil?.isFresh() == false + if (isOld) removeKey(it) } } - fun setKey(path: String, value: T, cacheTime: Duration) { + fun setMediaObject(path: String, value: MediaObject, cacheTime: Duration) { debugPrint { "Set cache: $SIMKL_CACHE_KEY/$path for ${cacheTime.inWholeDays} days or ${cacheTime.inWholeSeconds} seconds." } setKey( SIMKL_CACHE_KEY, path, - // Storing as plain sting is required to make generics work. - SimklCacheWrapper(value, APIHolder.unixTime + cacheTime.inWholeSeconds).toJson() + MediaObjectCacheEntry(value, APIHolder.unixTime + cacheTime.inWholeSeconds).toJson(), ) } - /** - * Gets cached object, if object is not fresh returns null and removes it from cache - */ - inline fun getKey(path: String): T? { + /** Gets the cached [MediaObject], if it's not fresh returns null and removes it from cache */ + fun getMediaObject(path: String): MediaObject? { val cache = getKey(SIMKL_CACHE_KEY, path)?.let { - tryParseJson>(it) + tryParseJson(it) } - return if (cache?.isFresh() == true) { + return if (cache?.validUntil?.isFresh() == true) { debugPrint { "Cache hit at: $SIMKL_CACHE_KEY/$path. " + - "Remains fresh for ${cache.remainingTime().inWholeDays} days or ${cache.remainingTime().inWholeSeconds} seconds." + "Remains fresh for ${cache.validUntil.remaining().inWholeDays} days or ${cache.validUntil.remaining().inWholeSeconds} seconds." + } + cache.obj + } else { + debugPrint { "Cache miss at: $SIMKL_CACHE_KEY/$path" } + removeKey(SIMKL_CACHE_KEY, path) + null + } + } + + fun setEpisodes(path: String, value: Array, cacheTime: Duration) { + debugPrint { "Set cache: $SIMKL_CACHE_KEY/$path for ${cacheTime.inWholeDays} days or ${cacheTime.inWholeSeconds} seconds." } + setKey( + SIMKL_CACHE_KEY, + path, + EpisodesCacheEntry(value, APIHolder.unixTime + cacheTime.inWholeSeconds).toJson(), + ) + } + + /** Gets the cached episode list, if it's not fresh returns null and removes it from cache */ + fun getEpisodes(path: String): Array? { + val cache = getKey(SIMKL_CACHE_KEY, path)?.let { + tryParseJson(it) + } + + return if (cache?.validUntil?.isFresh() == true) { + debugPrint { + "Cache hit at: $SIMKL_CACHE_KEY/$path. " + + "Remains fresh for ${cache.validUntil.remaining().inWholeDays} days or ${cache.validUntil.remaining().inWholeSeconds} seconds." } cache.obj } else { @@ -169,7 +206,7 @@ class SimklApi : SyncAPI() { ) ) ) - } catch (e: Exception) { + } catch (_: Exception) { null } } @@ -185,7 +222,7 @@ class SimklApi : SyncAPI() { enum class SimklListStatusType( var value: Int, @StringRes val stringRes: Int, - val originalName: String? + val originalName: String?, ) { Watching(0, R.string.type_watching, "watching"), Completed(1, R.string.type_completed, "completed"), @@ -204,83 +241,92 @@ class SimklApi : SyncAPI() { } } - // ------------------- @JsonInclude(JsonInclude.Include.NON_EMPTY) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = TokenRequest.Serializer::class) data class TokenRequest( - @JsonProperty("code") val code: String, - @JsonProperty("client_id") val clientId: String = CLIENT_ID, - @JsonProperty("client_secret") val clientSecret: String = CLIENT_SECRET, - @JsonProperty("redirect_uri") val redirectUri: String = "$APP_STRING://simkl", - @JsonProperty("grant_type") val grantType: String = "authorization_code" - ) + @JsonProperty("code") @SerialName("code") val code: String, + @JsonProperty("client_id") @SerialName("client_id") val clientId: String = CLIENT_ID, + @JsonProperty("client_secret") @SerialName("client_secret") val clientSecret: String = CLIENT_SECRET, + @JsonProperty("redirect_uri") @SerialName("redirect_uri") val redirectUri: String = "$APP_STRING://simkl", + @JsonProperty("grant_type") @SerialName("grant_type") val grantType: String = "authorization_code", + ) { + object Serializer : NonEmptySerializer(TokenRequest.generatedSerializer()) + } + @Serializable data class TokenResponse( /** No expiration date */ - @JsonProperty("access_token") val accessToken: String, - @JsonProperty("token_type") val tokenType: String, - @JsonProperty("scope") val scope: String + @JsonProperty("access_token") @SerialName("access_token") val accessToken: String, + @JsonProperty("token_type") @SerialName("token_type") val tokenType: String, + @JsonProperty("scope") @SerialName("scope") val scope: String, ) - // ------------------- /** https://simkl.docs.apiary.io/#reference/users/settings/receive-settings */ + @Serializable data class SettingsResponse( - @JsonProperty("user") - val user: User, - @JsonProperty("account") - val account: Account, + @JsonProperty("user") @SerialName("user") val user: User, + @JsonProperty("account") @SerialName("account") val account: Account, ) { + @Serializable data class User( - @JsonProperty("name") - val name: String, - /** Url */ - @JsonProperty("avatar") - val avatar: String + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("avatar") @SerialName("avatar") val avatar: String, // Url ) + @Serializable data class Account( - @JsonProperty("id") - val id: Int, + @JsonProperty("id") @SerialName("id") val id: Int, ) } + @Serializable data class PinAuthResponse( - @JsonProperty("result") val result: String, - @JsonProperty("device_code") val deviceCode: String, - @JsonProperty("user_code") val userCode: String, - @JsonProperty("verification_url") val verificationUrl: String, - @JsonProperty("expires_in") val expiresIn: Int, - @JsonProperty("interval") val interval: Int, + @JsonProperty("result") @SerialName("result") val result: String, + @JsonProperty("device_code") @SerialName("device_code") val deviceCode: String, + @JsonProperty("user_code") @SerialName("user_code") val userCode: String, + @JsonProperty("verification_url") @SerialName("verification_url") val verificationUrl: String, + @JsonProperty("expires_in") @SerialName("expires_in") val expiresIn: Int, + @JsonProperty("interval") @SerialName("interval") val interval: Int, ) + @Serializable data class PinExchangeResponse( - @JsonProperty("result") val result: String, - @JsonProperty("message") val message: String? = null, - @JsonProperty("access_token") val accessToken: String? = null, + @JsonProperty("result") @SerialName("result") val result: String, + @JsonProperty("message") @SerialName("message") val message: String? = null, + @JsonProperty("access_token") @SerialName("access_token") val accessToken: String? = null, ) - // ------------------- + @Serializable data class ActivitiesResponse( - @JsonProperty("all") val all: String?, - @JsonProperty("tv_shows") val tvShows: UpdatedAt, - @JsonProperty("anime") val anime: UpdatedAt, - @JsonProperty("movies") val movies: UpdatedAt, + @JsonProperty("all") @SerialName("all") val all: String?, + @JsonProperty("tv_shows") @SerialName("tv_shows") val tvShows: UpdatedAt, + @JsonProperty("anime") @SerialName("anime") val anime: UpdatedAt, + @JsonProperty("movies") @SerialName("movies") val movies: UpdatedAt, ) { + @Serializable data class UpdatedAt( - @JsonProperty("all") val all: String?, - @JsonProperty("removed_from_list") val removedFromList: String?, - @JsonProperty("rated_at") val ratedAt: String?, + @JsonProperty("all") @SerialName("all") val all: String?, + @JsonProperty("removed_from_list") @SerialName("removed_from_list") val removedFromList: String?, + @JsonProperty("rated_at") @SerialName("rated_at") val ratedAt: String?, ) } /** https://simkl.docs.apiary.io/#reference/tv/episodes/get-tv-show-episodes */ @JsonInclude(JsonInclude.Include.NON_EMPTY) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = EpisodeMetadata.Serializer::class) data class EpisodeMetadata( - @JsonProperty("title") val title: String?, - @JsonProperty("description") val description: String?, - @JsonProperty("season") val season: Int?, - @JsonProperty("episode") val episode: Int, - @JsonProperty("img") val img: String? + @JsonProperty("title") @SerialName("title") val title: String?, + @JsonProperty("description") @SerialName("description") val description: String?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int, + @JsonProperty("img") @SerialName("img") val img: String?, ) { + object Serializer : NonEmptySerializer(EpisodeMetadata.generatedSerializer()) + companion object { fun convertToEpisodes(list: List?): List? { return list?.map { @@ -300,40 +346,58 @@ class SimklApi : SyncAPI() { /** * https://simkl.docs.apiary.io/#introduction/about-simkl-api/standard-media-objects - * Useful for finding shows from metadata + * Useful for finding shows from metadata. */ @JsonInclude(JsonInclude.Include.NON_EMPTY) - open class MediaObject( - @JsonProperty("title") val title: String?, - @JsonProperty("year") val year: Int?, - @JsonProperty("ids") val ids: Ids?, - @JsonProperty("total_episodes") val totalEpisodes: Int? = null, - @JsonProperty("status") val status: String? = null, - @JsonProperty("poster") val poster: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("seasons") val seasons: List? = null, - @JsonProperty("episodes") val episodes: List? = null + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = MediaObject.Serializer::class) + data class MediaObject( + @JsonProperty("title") @SerialName("title") val title: String?, + @JsonProperty("year") @SerialName("year") val year: Int?, + @JsonProperty("ids") @SerialName("ids") val ids: Ids?, + @JsonProperty("total_episodes") @SerialName("total_episodes") val totalEpisodes: Int? = null, + @JsonProperty("status") @SerialName("status") val status: String? = null, + @JsonProperty("poster") @SerialName("poster") val poster: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("seasons") @SerialName("seasons") val seasons: List? = null, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, ) { + object Serializer : NonEmptySerializer(MediaObject.generatedSerializer()) + fun hasEnded(): Boolean { return status == "released" || status == "ended" } @JsonInclude(JsonInclude.Include.NON_EMPTY) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = Season.Serializer::class) data class Season( - @JsonProperty("number") val number: Int, - @JsonProperty("episodes") val episodes: List + @JsonProperty("number") @SerialName("number") val number: Int, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List, ) { - data class Episode(@JsonProperty("number") val number: Int) + object Serializer : NonEmptySerializer(Season.generatedSerializer()) + + @Serializable + data class Episode( + @JsonProperty("number") @SerialName("number") val number: Int, + ) } @JsonInclude(JsonInclude.Include.NON_EMPTY) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = Ids.Serializer::class) data class Ids( - @JsonProperty("simkl") val simkl: Int?, - @JsonProperty("imdb") val imdb: String? = null, - @JsonProperty("tmdb") val tmdb: String? = null, - @JsonProperty("mal") val mal: String? = null, - @JsonProperty("anilist") val anilist: String? = null, + @JsonProperty("simkl") @SerialName("simkl") val simkl: Int?, + @JsonProperty("imdb") @SerialName("imdb") val imdb: String? = null, + @JsonProperty("tmdb") @SerialName("tmdb") val tmdb: String? = null, + @JsonProperty("mal") @SerialName("mal") val mal: String? = null, + @JsonProperty("anilist") @SerialName("anilist") val anilist: String? = null, ) { + object Serializer : NonEmptySerializer(Ids.generatedSerializer()) + companion object { fun fromMap(map: Map): Ids { return Ids( @@ -341,20 +405,21 @@ class SimklApi : SyncAPI() { imdb = map[SimklSyncServices.Imdb], tmdb = map[SimklSyncServices.Tmdb], mal = map[SimklSyncServices.Mal], - anilist = map[SimklSyncServices.AniList] + anilist = map[SimklSyncServices.AniList], ) } } } fun toSyncSearchResult(): SyncAPI.SyncSearchResult? { + val currentIds = this.ids return SyncAPI.SyncSearchResult( this.title ?: return null, "Simkl", - this.ids?.simkl?.toString() ?: return null, - getUrlFromId(this.ids.simkl), + currentIds?.simkl?.toString() ?: return null, + getUrlFromId(currentIds.simkl), this.poster?.let { getPosterUrl(it) }, - if (this.type == "movie") TvType.Movie else TvType.TvSeries + if (this.type == "movie") TvType.Movie else TvType.TvSeries, ) } } @@ -369,7 +434,7 @@ class SimklApi : SyncAPI() { private var addEpisodes: Pair?, List?>? = null, private var removeEpisodes: Pair?, List?>? = null, // Required for knowing if the status should be overwritten - private var onList: Boolean = false + private var onList: Boolean = false, ) { fun token(token: AuthToken) = apply { this.headers = getHeaders(token) } fun apiUrl(url: String) = apply { this.url = url } @@ -383,11 +448,7 @@ class SimklApi : SyncAPI() { fun status(newStatus: Int?, oldStatus: Int?) = apply { onList = oldStatus != null // Only set status if its new - if (newStatus != oldStatus) { - this.status = newStatus - } else { - this.status = null - } + this.status = if (newStatus != oldStatus) newStatus else null } fun episodes( @@ -396,7 +457,6 @@ class SimklApi : SyncAPI() { oldEpisodes: Int?, ) = apply { if (allEpisodes == null || newEpisodes == null) return@apply - fun getEpisodes(rawEpisodes: List) = if (rawEpisodes.any { it.season != null }) { EpisodeMetadata.convertToSeasons(rawEpisodes) to null @@ -407,12 +467,12 @@ class SimklApi : SyncAPI() { // Do not add episodes if there is no change if (newEpisodes > (oldEpisodes ?: 0)) { this.addEpisodes = getEpisodes(allEpisodes.take(newEpisodes)) - // Set to watching if episodes are added and there is no current status if (!onList) { status = SimklListStatusType.Watching.value } } + if ((oldEpisodes ?: 0) > newEpisodes) { this.removeEpisodes = getEpisodes(allEpisodes.drop(newEpisodes)) } @@ -424,19 +484,17 @@ class SimklApi : SyncAPI() { return if (this.status == SimklListStatusType.None.value) { app.post( "$url/sync/history/remove", - json = StatusRequest( + json = HistoryRequest( shows = listOf(HistoryMediaObject(ids = ids)), - movies = emptyList() + movies = emptyList(), ), - headers = headers + headers = headers, ).isSuccessful } else { val statusResponse = this.status?.let { setStatus -> - val newStatus = - SimklListStatusType.entries - .firstOrNull { it.value == setStatus }?.originalName - ?: SimklListStatusType.Watching.originalName!! - + val newStatus = SimklListStatusType.entries.firstOrNull { + it.value == setStatus + }?.originalName ?: SimklListStatusType.Watching.originalName!! app.post( "${this.url}/sync/add-to-list", json = StatusRequest( @@ -446,41 +504,40 @@ class SimklApi : SyncAPI() { null, ids, newStatus, - ) - ), movies = emptyList() + ), + ), + movies = emptyList(), ), - headers = headers + headers = headers, ).isSuccessful } ?: true val episodeRemovalResponse = removeEpisodes?.let { (seasons, episodes) -> app.post( "${this.url}/sync/history/remove", - json = StatusRequest( + json = HistoryRequest( shows = listOf( HistoryMediaObject( ids = ids, seasons = seasons, - episodes = episodes - ) + episodes = episodes, + ), ), - movies = emptyList() + movies = emptyList(), ), - headers = headers + headers = headers, ).isSuccessful } ?: true // You cannot rate if you are planning to watch it. - val shouldRate = - score != null && status != SimklListStatusType.Planning.value + val shouldRate = score != null && status != SimklListStatusType.Planning.value val realScore = if (shouldRate) score else null - val historyResponse = // Only post if there are episodes or score to upload if (addEpisodes != null || shouldRate) { app.post( "${this.url}/sync/history", - json = StatusRequest( + json = HistoryRequest( shows = listOf( HistoryMediaObject( null, @@ -490,34 +547,33 @@ class SimklApi : SyncAPI() { addEpisodes?.second, realScore, realScore?.let { time }, - ) - ), movies = emptyList() + ), + ), + movies = emptyList(), ), - headers = headers + headers = headers, ).isSuccessful - } else { - true - } - + } else true statusResponse && episodeRemovalResponse && historyResponse } } } } - fun getHeaders(token: AuthToken): Map = - mapOf("Authorization" to "Bearer ${token.accessToken}", "simkl-api-key" to CLIENT_ID) + fun getHeaders(token: AuthToken): Map = mapOf( + "Authorization" to "Bearer ${token.accessToken}", + "simkl-api-key" to CLIENT_ID, + ) suspend fun getEpisodes( simklId: Int?, type: String?, episodes: Int?, - hasEnded: Boolean? + hasEnded: Boolean?, ): Array? { if (simklId == null) return null - val cacheKey = "Episodes/$simklId" - val cache = SimklCache.getKey>(cacheKey) + val cache = SimklCache.getEpisodes(cacheKey) // Return cached result if its higher or equal the amount of episodes. if (cache != null && cache.size >= (episodes ?: 0)) { @@ -534,6 +590,7 @@ class SimklApi : SyncAPI() { }.toTypedArray() } } + val url = when (type) { "anime" -> "https://api.simkl.com/anime/episodes/$simklId" "tv" -> "https://api.simkl.com/tv/episodes/$simklId" @@ -548,57 +605,86 @@ class SimklApi : SyncAPI() { if (hasEnded == true) SimklCache.CacheTimes.OneMonth.value else SimklCache.CacheTimes.ThirtyMinutes.value // 1 Month cache - SimklCache.setKey(cacheKey, it, Duration.parse(cacheTime)) + SimklCache.setEpisodes(cacheKey, it, Duration.parse(cacheTime)) } } @JsonInclude(JsonInclude.Include.NON_EMPTY) - class HistoryMediaObject( - @JsonProperty("title") title: String? = null, - @JsonProperty("year") year: Int? = null, - @JsonProperty("ids") ids: Ids? = null, - @JsonProperty("seasons") seasons: List? = null, - @JsonProperty("episodes") episodes: List? = null, - @JsonProperty("rating") val rating: Int? = null, - @JsonProperty("rated_at") val ratedAt: String? = null, - ) : MediaObject(title, year, ids, seasons = seasons, episodes = episodes) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = HistoryMediaObject.Serializer::class) + data class HistoryMediaObject( + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: MediaObject.Ids? = null, + @JsonProperty("seasons") @SerialName("seasons") val seasons: List? = null, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Int? = null, + @JsonProperty("rated_at") @SerialName("rated_at") val ratedAt: String? = null, + ) { + object Serializer : NonEmptySerializer(HistoryMediaObject.generatedSerializer()) + } @JsonInclude(JsonInclude.Include.NON_EMPTY) - class RatingMediaObject( - @JsonProperty("title") title: String?, - @JsonProperty("year") year: Int?, - @JsonProperty("ids") ids: Ids?, - @JsonProperty("rating") val rating: Int, - @JsonProperty("rated_at") val ratedAt: String? = getDateTime(APIHolder.unixTime) - ) : MediaObject(title, year, ids) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = RatingMediaObject.Serializer::class) + data class RatingMediaObject( + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: MediaObject.Ids? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Int, + @JsonProperty("rated_at") @SerialName("rated_at") val ratedAt: String? = getDateTime(APIHolder.unixTime), + ) { + object Serializer : NonEmptySerializer(RatingMediaObject.generatedSerializer()) + } @JsonInclude(JsonInclude.Include.NON_EMPTY) - class StatusMediaObject( - @JsonProperty("title") title: String?, - @JsonProperty("year") year: Int?, - @JsonProperty("ids") ids: Ids?, - @JsonProperty("to") val to: String, - @JsonProperty("watched_at") val watchedAt: String? = getDateTime(APIHolder.unixTime) - ) : MediaObject(title, year, ids) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = StatusMediaObject.Serializer::class) + data class StatusMediaObject( + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: MediaObject.Ids? = null, + @JsonProperty("to") @SerialName("to") val to: String, + @JsonProperty("watched_at") @SerialName("watched_at") val watchedAt: String? = getDateTime(APIHolder.unixTime), + ) { + object Serializer : NonEmptySerializer(StatusMediaObject.generatedSerializer()) + } @JsonInclude(JsonInclude.Include.NON_EMPTY) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = StatusRequest.Serializer::class) data class StatusRequest( - @JsonProperty("movies") val movies: List, - @JsonProperty("shows") val shows: List - ) + @JsonProperty("movies") @SerialName("movies") val movies: List, + @JsonProperty("shows") @SerialName("shows") val shows: List, + ) { + object Serializer : NonEmptySerializer(StatusRequest.generatedSerializer()) + } + + /** Same shape as [StatusRequest], for the endpoints that post [HistoryMediaObject]s instead. */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = HistoryRequest.Serializer::class) + data class HistoryRequest( + @JsonProperty("movies") @SerialName("movies") val movies: List, + @JsonProperty("shows") @SerialName("shows") val shows: List, + ) { + object Serializer : NonEmptySerializer(HistoryRequest.generatedSerializer()) + } /** https://simkl.docs.apiary.io/#reference/sync/get-all-items/get-all-items-in-the-user's-watchlist */ + @Serializable data class AllItemsResponse( - @JsonProperty("shows") - val shows: List = emptyList(), - @JsonProperty("anime") - val anime: List = emptyList(), - @JsonProperty("movies") - val movies: List = emptyList(), + @JsonProperty("shows") @SerialName("shows") val shows: List = emptyList(), + @JsonProperty("anime") @SerialName("anime") val anime: List = emptyList(), + @JsonProperty("movies") @SerialName("movies") val movies: List = emptyList(), ) { companion object { fun merge(first: AllItemsResponse?, second: AllItemsResponse?): AllItemsResponse { - // Replace the first item with the same id, or add the new item fun MutableList.replaceOrAddItem(newItem: T, predicate: (T) -> Boolean) { for (i in this.indices) { @@ -607,10 +693,10 @@ class SimklApi : SyncAPI() { return } } + this.add(newItem) } - // fun merge( first: List?, second: List? @@ -644,15 +730,17 @@ class SimklApi : SyncAPI() { fun toLibraryItem(): SyncAPI.LibraryItem } + @Serializable data class MovieMetadata( - @JsonProperty("last_watched_at") override val lastWatchedAt: String?, - @JsonProperty("status") override val status: String, - @JsonProperty("user_rating") override val userRating: Int?, - @JsonProperty("last_watched") override val lastWatched: String?, - @JsonProperty("watched_episodes_count") override val watchedEpisodesCount: Int?, - @JsonProperty("total_episodes_count") override val totalEpisodesCount: Int?, - val movie: ShowMetadata.Show + @JsonProperty("last_watched_at") @SerialName("last_watched_at") override val lastWatchedAt: String?, + @JsonProperty("status") @SerialName("status") override val status: String, + @JsonProperty("user_rating") @SerialName("user_rating") override val userRating: Int?, + @JsonProperty("last_watched") @SerialName("last_watched") override val lastWatched: String?, + @JsonProperty("watched_episodes_count") @SerialName("watched_episodes_count") override val watchedEpisodesCount: Int?, + @JsonProperty("total_episodes_count") @SerialName("total_episodes_count") override val totalEpisodesCount: Int?, + @JsonProperty("movie") @SerialName("movie") val movie: ShowMetadata.Show, ) : Metadata { + @JsonIgnore override fun getIds(): ShowMetadata.Show.Ids { return this.movie.ids } @@ -672,20 +760,22 @@ class SimklApi : SyncAPI() { null, null, this.movie.year?.toYear(), - movie.ids.simkl + movie.ids.simkl, ) } } + @Serializable data class ShowMetadata( - @JsonProperty("last_watched_at") override val lastWatchedAt: String?, - @JsonProperty("status") override val status: String, - @JsonProperty("user_rating") override val userRating: Int?, - @JsonProperty("last_watched") override val lastWatched: String?, - @JsonProperty("watched_episodes_count") override val watchedEpisodesCount: Int?, - @JsonProperty("total_episodes_count") override val totalEpisodesCount: Int?, - @JsonProperty("show") val show: Show + @JsonProperty("last_watched_at") @SerialName("last_watched_at") override val lastWatchedAt: String?, + @JsonProperty("status") @SerialName("status") override val status: String, + @JsonProperty("user_rating") @SerialName("user_rating") override val userRating: Int?, + @JsonProperty("last_watched") @SerialName("last_watched") override val lastWatched: String?, + @JsonProperty("watched_episodes_count") @SerialName("watched_episodes_count") override val watchedEpisodesCount: Int?, + @JsonProperty("total_episodes_count") @SerialName("total_episodes_count") override val totalEpisodesCount: Int?, + @JsonProperty("show") @SerialName("show") val show: Show, ) : Metadata { + @JsonIgnore override fun getIds(): Show.Ids { return this.show.ids } @@ -705,28 +795,30 @@ class SimklApi : SyncAPI() { null, null, this.show.year?.toYear(), - show.ids.simkl + show.ids.simkl, ) } + @Serializable data class Show( - @JsonProperty("title") val title: String, - @JsonProperty("poster") val poster: String?, - @JsonProperty("year") val year: Int?, - @JsonProperty("ids") val ids: Ids, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("poster") @SerialName("poster") val poster: String?, + @JsonProperty("year") @SerialName("year") val year: Int?, + @JsonProperty("ids") @SerialName("ids") val ids: Ids, ) { + @Serializable data class Ids( - @JsonProperty("simkl") val simkl: Int, - @JsonProperty("slug") val slug: String?, - @JsonProperty("imdb") val imdb: String?, - @JsonProperty("zap2it") val zap2it: String?, - @JsonProperty("tmdb") val tmdb: String?, - @JsonProperty("offen") val offen: String?, - @JsonProperty("tvdb") val tvdb: String?, - @JsonProperty("mal") val mal: String?, - @JsonProperty("anidb") val anidb: String?, - @JsonProperty("anilist") val anilist: String?, - @JsonProperty("traktslug") val traktslug: String? + @JsonProperty("simkl") @SerialName("simkl") val simkl: Int, + @JsonProperty("slug") @SerialName("slug") val slug: String?, + @JsonProperty("imdb") @SerialName("imdb") val imdb: String?, + @JsonProperty("zap2it") @SerialName("zap2it") val zap2it: String?, + @JsonProperty("tmdb") @SerialName("tmdb") val tmdb: String?, + @JsonProperty("offen") @SerialName("offen") val offen: String?, + @JsonProperty("tvdb") @SerialName("tvdb") val tvdb: String?, + @JsonProperty("mal") @SerialName("mal") val mal: String?, + @JsonProperty("anidb") @SerialName("anidb") val anidb: String?, + @JsonProperty("anilist") @SerialName("anilist") val anilist: String?, + @JsonProperty("traktslug") @SerialName("traktslug") val traktslug: String?, ) { fun matchesId(database: SimklSyncServices, id: String): Boolean { return when (database) { @@ -743,27 +835,10 @@ class SimklApi : SyncAPI() { } } - /** - * Appends api keys to the requests - **/ - /*private inner class HeaderInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - debugPrint { "${this@SimklApi.name} made request to ${chain.request().url}" } - return chain.proceed( - chain.request() - .newBuilder() - .addHeader("Authorization", "Bearer $token") - .addHeader("simkl-api-key", CLIENT_ID) - .build() - ) - } - }*/ - private suspend fun getUser(token: AuthToken): SettingsResponse = app.post("$mainUrl/users/settings", headers = getHeaders(token)) .parsed() - /** * Useful to get episodes on demand to prevent unnecessary requests. */ @@ -771,7 +846,7 @@ class SimklApi : SyncAPI() { private val simklId: Int?, private val type: String?, private val totalEpisodeCount: Int?, - private val hasEnded: Boolean? + private val hasEnded: Boolean?, ) { suspend fun getEpisodes(): Array? { return getEpisodes(simklId, type, totalEpisodeCount, hasEnded) @@ -786,10 +861,12 @@ class SimklApi : SyncAPI() { val episodeConstructor: SimklEpisodeConstructor, override var isFavorite: Boolean? = null, override var maxEpisodes: Int? = null, - /** Save seen episodes separately to know the change from old to new. - * Required to remove seen episodes if count decreases */ + /** + * Save seen episodes separately to know the change from old to new. + * Required to remove seen episodes if count decreases. + */ val oldEpisodes: Int, - val oldStatus: String? + val oldStatus: String?, ) : SyncAPI.AbstractSyncStatus() override suspend fun status(auth: AuthData?, id: String): SyncAPI.AbstractSyncStatus? { @@ -799,22 +876,23 @@ class SimklApi : SyncAPI() { // Key which assumes all ids are the same each time :/ // This could be some sort of reference system to make multiple IDs // point to the same key. - val idKey = - realIds.toList().map { "${it.first.originalName}=${it.second}" }.sorted().joinToString() + val idKey = realIds.toList().map { + "${it.first.originalName}=${it.second}" + }.sorted().joinToString() - val cachedObject = SimklCache.getKey(idKey) + val cachedObject = SimklCache.getMediaObject(idKey) val searchResult: MediaObject = cachedObject ?: (searchByIds(realIds)?.firstOrNull()?.also { result -> val cacheTime = if (result.hasEnded()) SimklCache.CacheTimes.OneMonth.value else SimklCache.CacheTimes.ThirtyMinutes.value - SimklCache.setKey(idKey, result, Duration.parse(cacheTime)) + SimklCache.setMediaObject(idKey, result, Duration.parse(cacheTime)) }) ?: return null val episodeConstructor = SimklEpisodeConstructor( searchResult.ids?.simkl, searchResult.type, searchResult.totalEpisodes, - searchResult.hasEnded() + searchResult.hasEnded(), ) val foundItem = getSyncListSmart(auth)?.let { list -> @@ -829,19 +907,16 @@ class SimklApi : SyncAPI() { return SimklSyncStatus( status = foundItem.status?.let { SyncWatchType.fromInternalId( - SimklListStatusType.fromString( - it - )?.value + SimklListStatusType.fromString(it)?.value ) - } - ?: return null, + } ?: return null, score = Score.from10(foundItem.userRating), watchedEpisodes = foundItem.watchedEpisodesCount, maxEpisodes = searchResult.totalEpisodes, episodeConstructor = episodeConstructor, oldEpisodes = foundItem.watchedEpisodesCount ?: 0, oldScore = foundItem.userRating, - oldStatus = foundItem.status + oldStatus = foundItem.status, ) } else { return SimklSyncStatus( @@ -852,7 +927,7 @@ class SimklApi : SyncAPI() { episodeConstructor = episodeConstructor, oldEpisodes = 0, oldStatus = null, - oldScore = null + oldScore = null, ) } } @@ -860,12 +935,11 @@ class SimklApi : SyncAPI() { override suspend fun updateStatus( auth: AuthData?, id: String, - newStatus: AbstractSyncStatus + newStatus: AbstractSyncStatus, ): Boolean { - val parsedId = readIdFromString(id) lastScoreTime = APIHolder.unixTime + val parsedId = readIdFromString(id) val simklStatus = newStatus as? SimklSyncStatus - val builder = SimklScoreBuilder.Builder() .apiUrl(this.mainUrl) .score(newStatus.score?.toInt(10), simklStatus?.oldScore) @@ -879,7 +953,6 @@ class SimklApi : SyncAPI() { .token(auth?.token ?: return false) .ids(MediaObject.Ids.fromMap(parsedId)) - // Get episodes only when required val episodes = simklStatus?.episodeConstructor?.getEpisodes() @@ -892,22 +965,19 @@ class SimklApi : SyncAPI() { } builder.episodes(episodes?.toList(), watchedEpisodes, simklStatus?.oldEpisodes) - requireLibraryRefresh = true return builder.execute() } - /** See https://simkl.docs.apiary.io/#reference/search/id-lookup/get-items-by-id */ private suspend fun searchByIds(serviceMap: Map): Array? { if (serviceMap.isEmpty()) return emptyArray() - return app.get( "$mainUrl/search/id", params = mapOf("client_id" to CLIENT_ID) + serviceMap.map { (service, id) -> service.originalName to id } - ).parsedSafe() + ).parsedSafe>() } override suspend fun search(auth: AuthData?, query: String): List? { @@ -918,12 +988,10 @@ class SimklApi : SyncAPI() { override fun loginRequest(): AuthLoginPage? { val lastLoginState = BigInteger(130, SecureRandom()).toString(32) - val url = - "https://simkl.com/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$APP_STRING://${redirectUrlIdentifier}&state=$lastLoginState" - + val url = "https://simkl.com/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$APP_STRING://$redirectUrlIdentifier&state=$lastLoginState" return AuthLoginPage( url = url, - payload = lastLoginState + payload = lastLoginState, ) } @@ -938,12 +1006,12 @@ class SimklApi : SyncAPI() { return app.get( "$mainUrl/sync/all-items/", params = params, - headers = getHeaders(auth.token) - ).parsedSafe() + headers = getHeaders(auth.token), + ).parsedSafe() } private suspend fun getActivities(token: AuthToken): ActivitiesResponse? { - return app.post("$mainUrl/sync/activities", headers = getHeaders(token)).parsedSafe() + return app.post("$mainUrl/sync/activities", headers = getHeaders(token)).parsedSafe() } private fun getSyncListCached(auth: AuthData): AllItemsResponse? { @@ -957,18 +1025,13 @@ class SimklApi : SyncAPI() { val lastRemoval = listOf( activities?.tvShows?.removedFromList, activities?.anime?.removedFromList, - activities?.movies?.removedFromList - ).maxOf { - getUnixTime(it) ?: -1 - } - val lastRealUpdate = - listOf( - activities?.tvShows?.all, - activities?.anime?.all, - activities?.movies?.all, - ).maxOf { - getUnixTime(it) ?: -1 - } + activities?.movies?.removedFromList, + ).maxOf { getUnixTime(it) ?: -1 } + val lastRealUpdate = listOf( + activities?.tvShows?.all, + activities?.anime?.all, + activities?.movies?.all, + ).maxOf { getUnixTime(it) ?: -1 } debugPrint { "Cache times: lastCacheUpdate=$lastCacheUpdate, lastRemoval=$lastRemoval, lastRealUpdate=$lastRealUpdate" } val list = if (lastCacheUpdate == null || lastCacheUpdate < lastRemoval) { @@ -980,38 +1043,33 @@ class SimklApi : SyncAPI() { setKey(SIMKL_CACHED_LIST_TIME, userId, lastCacheUpdate) AllItemsResponse.merge( getSyncListCached(auth), - getSyncListSince(auth, lastCacheUpdate) + getSyncListSince(auth, lastCacheUpdate), ) } else { debugPrint { "Cached list update in ${this.name}." } getSyncListCached(auth) } + debugPrint { "List sizes: movies=${list?.movies?.size}, shows=${list?.shows?.size}, anime=${list?.anime?.size}" } - setKey(SIMKL_CACHED_LIST, userId, list) - return list } override suspend fun library(auth: AuthData?): SyncAPI.LibraryMetadata? { val list = getSyncListSmart(auth ?: return null) ?: return null - - val baseMap = - SimklListStatusType.entries - .filter { it.value >= 0 && it.value != SimklListStatusType.ReWatching.value } - .associate { - it.stringRes to emptyList() - } + val baseMap = SimklListStatusType.entries + .filter { it.value >= 0 && it.value != SimklListStatusType.ReWatching.value } + .associate { + it.stringRes to emptyList() + } val syncMap = listOf(list.anime, list.movies, list.shows) .flatten() - .groupBy { - it.status - } + .groupBy { it.status } .mapNotNull { (status, list) -> - val stringRes = - status?.let { SimklListStatusType.fromString(it)?.stringRes } - ?: return@mapNotNull null + val stringRes = status?.let { + SimklListStatusType.fromString(it)?.stringRes + } ?: return@mapNotNull null val libraryList = list.map { it.toLibraryItem() } stringRes to libraryList }.toMap() @@ -1037,15 +1095,14 @@ class SimklApi : SyncAPI() { override suspend fun pinRequest(): AuthPinData? { val pinAuthResp = app.get( - "$mainUrl/oauth/pin?client_id=$CLIENT_ID&redirect_uri=$APP_STRING://${redirectUrlIdentifier}" + "$mainUrl/oauth/pin?client_id=$CLIENT_ID&redirect_uri=$APP_STRING://$redirectUrlIdentifier" ).parsedSafe() ?: return null - return AuthPinData( deviceCode = pinAuthResp.deviceCode, userCode = pinAuthResp.userCode, verificationUrl = pinAuthResp.verificationUrl, expiresIn = pinAuthResp.expiresIn, - interval = pinAuthResp.interval + interval = pinAuthResp.interval, ) } @@ -1053,7 +1110,6 @@ class SimklApi : SyncAPI() { val pinAuthResp = app.get( "$mainUrl/oauth/pin/${payload.userCode}?client_id=$CLIENT_ID" ).parsedSafe() ?: return null - return AuthToken( accessToken = pinAuthResp.accessToken ?: return null, ) @@ -1069,7 +1125,6 @@ class SimklApi : SyncAPI() { val tokenResponse = app.post( "$mainUrl/oauth/token", json = TokenRequest(code) ).parsedSafe() ?: return null - return AuthToken( accessToken = tokenResponse.accessToken, ) @@ -1080,7 +1135,7 @@ class SimklApi : SyncAPI() { return AuthUser( id = user.account.id, name = user.user.name, - profilePicture = user.user.avatar + profilePicture = user.user.avatar, ) } } From 3496e5f8d2ebae4c1b5bdf264782f58375c1eb06 Mon Sep 17 00:00:00 2001 From: BlipBlob <82711292+BlipBlob@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:59:31 +0000 Subject: [PATCH 053/105] [skip ci] Use abiValidation to prevent breaking changes in extension library (#2835) --- .github/workflows/pull_request.yml | 5 + library/README.md | 15 + library/api/jvm/library.api | 7271 ++++++++++++++++++++++++++++ library/build.gradle.kts | 12 + 4 files changed, 7303 insertions(+) create mode 100644 library/README.md create mode 100644 library/api/jvm/library.api diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8f5c62866..433816e0f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -26,6 +26,11 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-read-only: false + - name: Ensure binary compatibility + # This is to ensure that your code is backwards compatible. + # If this fails you need to add a @Prerelease annotation to new code. + run: ./gradlew library:checkKotlinAbi + - name: Run Gradle run: ./gradlew assemblePrereleaseDebug lint check diff --git a/library/README.md b/library/README.md new file mode 100644 index 000000000..18f21055d --- /dev/null +++ b/library/README.md @@ -0,0 +1,15 @@ +## CloudStream extension library + +This is the official API surface for all CloudStream plugins. + +To ensure that all plugins work on both the stable release and pre-release we must have +binary compatibility on all changes. All new changes must be marked with `@Prerelease` to +prevent accidental usage among extension developers. + +We use Kotlin binary compatibility validation using: + +``./gradlew checkKotlinAbi`` + +If you for some reason must update the binary compatibility then manually edit `api/jvm/library.api` or use: + +``./gradlew updateKotlinAbi`` diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api new file mode 100644 index 000000000..025ebc391 --- /dev/null +++ b/library/api/jvm/library.api @@ -0,0 +1,7271 @@ +public final class com/lagradost/api/BuildConfig { + public static final field INSTANCE Lcom/lagradost/api/BuildConfig; + public final fun getMDL_API_KEY ()Ljava/lang/String; + public final fun getTRAKT_CLIENT_ID ()Ljava/lang/String; +} + +public final class com/lagradost/api/ContextHelper_jvmKt { + public static final fun getContext ()Ljava/lang/Object; + public static final fun setContext (Ljava/lang/ref/WeakReference;)V +} + +public final class com/lagradost/api/Log { + public static final field INSTANCE Lcom/lagradost/api/Log; + public final fun d (Ljava/lang/String;Ljava/lang/String;)V + public final fun e (Ljava/lang/String;Ljava/lang/String;)V + public final fun i (Ljava/lang/String;Ljava/lang/String;)V + public final fun w (Ljava/lang/String;Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/APIHolder { + public static final field INSTANCE Lcom/lagradost/cloudstream3/APIHolder; + public final fun addPluginMapping (Lcom/lagradost/cloudstream3/MainAPI;)V + public final fun capitalize (Ljava/lang/String;)Ljava/lang/String; + public final fun getAllProviders ()Lcom/lagradost/cloudstream3/utils/AtomicMutableList; + public final fun getApiFromNameNull (Ljava/lang/String;)Lcom/lagradost/cloudstream3/MainAPI; + public final fun getApiFromUrlNull (Ljava/lang/String;)Lcom/lagradost/cloudstream3/MainAPI; + public final fun getApiMap ()Ljava/util/Map; + public final fun getApis ()Lcom/lagradost/cloudstream3/utils/AtomicList; + public final fun getCaptchaToken (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getCaptchaToken$default (Lcom/lagradost/cloudstream3/APIHolder;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getTracker (Ljava/util/List;Ljava/util/Set;Ljava/lang/Integer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getTracker (Ljava/util/List;Ljava/util/Set;Ljava/lang/Integer;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getUnixTime ()J + public final fun getUnixTimeMS ()J + public final fun initAll ()V + public final fun removePluginMapping (Lcom/lagradost/cloudstream3/MainAPI;)V + public final fun setApiMap (Ljava/util/Map;)V + public final fun setApis (Lcom/lagradost/cloudstream3/utils/AtomicList;)V +} + +public final class com/lagradost/cloudstream3/Actor { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/Actor; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/Actor;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/Actor; + public fun equals (Ljava/lang/Object;)Z + public final fun getImage ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/ActorData { + public fun (Lcom/lagradost/cloudstream3/Actor;Lcom/lagradost/cloudstream3/ActorRole;Ljava/lang/String;Lcom/lagradost/cloudstream3/Actor;)V + public synthetic fun (Lcom/lagradost/cloudstream3/Actor;Lcom/lagradost/cloudstream3/ActorRole;Ljava/lang/String;Lcom/lagradost/cloudstream3/Actor;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/Actor; + public final fun component2 ()Lcom/lagradost/cloudstream3/ActorRole; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/Actor; + public final fun copy (Lcom/lagradost/cloudstream3/Actor;Lcom/lagradost/cloudstream3/ActorRole;Ljava/lang/String;Lcom/lagradost/cloudstream3/Actor;)Lcom/lagradost/cloudstream3/ActorData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/ActorData;Lcom/lagradost/cloudstream3/Actor;Lcom/lagradost/cloudstream3/ActorRole;Ljava/lang/String;Lcom/lagradost/cloudstream3/Actor;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/ActorData; + public fun equals (Ljava/lang/Object;)Z + public final fun getActor ()Lcom/lagradost/cloudstream3/Actor; + public final fun getRole ()Lcom/lagradost/cloudstream3/ActorRole; + public final fun getRoleString ()Ljava/lang/String; + public final fun getVoiceActor ()Lcom/lagradost/cloudstream3/Actor; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/ActorRole : java/lang/Enum { + public static final field Background Lcom/lagradost/cloudstream3/ActorRole; + public static final field Main Lcom/lagradost/cloudstream3/ActorRole; + public static final field Supporting Lcom/lagradost/cloudstream3/ActorRole; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/ActorRole; + public static fun values ()[Lcom/lagradost/cloudstream3/ActorRole; +} + +public final class com/lagradost/cloudstream3/AniSearch { + public static final field Companion Lcom/lagradost/cloudstream3/AniSearch$Companion; + public fun ()V + public fun (Lcom/lagradost/cloudstream3/AniSearch$Data;)V + public synthetic fun (Lcom/lagradost/cloudstream3/AniSearch$Data;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/AniSearch$Data; + public final fun copy (Lcom/lagradost/cloudstream3/AniSearch$Data;)Lcom/lagradost/cloudstream3/AniSearch; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AniSearch;Lcom/lagradost/cloudstream3/AniSearch$Data;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AniSearch; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Lcom/lagradost/cloudstream3/AniSearch$Data; + public fun hashCode ()I + public final fun setData (Lcom/lagradost/cloudstream3/AniSearch$Data;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AniSearch$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AniSearch$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AniSearch; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AniSearch;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data { + public static final field Companion Lcom/lagradost/cloudstream3/AniSearch$Data$Companion; + public fun ()V + public fun (Lcom/lagradost/cloudstream3/AniSearch$Data$Page;)V + public synthetic fun (Lcom/lagradost/cloudstream3/AniSearch$Data$Page;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/AniSearch$Data$Page; + public final fun copy (Lcom/lagradost/cloudstream3/AniSearch$Data$Page;)Lcom/lagradost/cloudstream3/AniSearch$Data; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AniSearch$Data;Lcom/lagradost/cloudstream3/AniSearch$Data$Page;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AniSearch$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getPage ()Lcom/lagradost/cloudstream3/AniSearch$Data$Page; + public fun hashCode ()I + public final fun setPage (Lcom/lagradost/cloudstream3/AniSearch$Data$Page;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AniSearch$Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AniSearch$Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AniSearch$Data; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AniSearch$Data;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page { + public static final field Companion Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Companion; + public fun ()V + public fun (Ljava/util/ArrayList;)V + public synthetic fun (Ljava/util/ArrayList;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/ArrayList; + public final fun copy (Ljava/util/ArrayList;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AniSearch$Data$Page;Ljava/util/ArrayList;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page; + public fun equals (Ljava/lang/Object;)Z + public final fun getMedia ()Ljava/util/ArrayList; + public fun hashCode ()I + public final fun setMedia (Ljava/util/ArrayList;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AniSearch$Data$Page$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AniSearch$Data$Page$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AniSearch$Data$Page;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Media { + public static final field Companion Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Companion; + public fun ()V + public fun (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;Ljava/lang/String;)V + public synthetic fun (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage; + public final fun component7 ()Ljava/lang/String; + public final fun copy (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;Ljava/lang/String;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media; + public fun equals (Ljava/lang/Object;)Z + public final fun getBannerImage ()Ljava/lang/String; + public final fun getCoverImage ()Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage; + public final fun getFormat ()Ljava/lang/String; + public final fun getId ()Ljava/lang/Integer; + public final fun getIdMal ()Ljava/lang/Integer; + public final fun getSeasonYear ()Ljava/lang/Integer; + public final fun getTitle ()Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title; + public fun hashCode ()I + public final fun setBannerImage (Ljava/lang/String;)V + public final fun setCoverImage (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;)V + public final fun setFormat (Ljava/lang/String;)V + public final fun setId (Ljava/lang/Integer;)V + public final fun setIdMal (Ljava/lang/Integer;)V + public final fun setSeasonYear (Ljava/lang/Integer;)V + public final fun setTitle (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage { + public static final field Companion Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage; + public fun equals (Ljava/lang/Object;)Z + public final fun getExtraLarge ()Ljava/lang/String; + public final fun getLarge ()Ljava/lang/String; + public fun hashCode ()I + public final fun setExtraLarge (Ljava/lang/String;)V + public final fun setLarge (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$CoverImage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title { + public static final field Companion Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title; + public fun equals (Ljava/lang/Object;)Z + public final fun getEnglish ()Ljava/lang/String; + public final fun getRomaji ()Ljava/lang/String; + public fun hashCode ()I + public final fun isMatchingTitles (Ljava/lang/String;)Z + public final fun setEnglish (Ljava/lang/String;)V + public final fun setRomaji (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AniSearch$Data$Page$Media$Title$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AnimeLoadResponse : com/lagradost/cloudstream3/EpisodeResponse, com/lagradost/cloudstream3/LoadResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lcom/lagradost/cloudstream3/ShowStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/Score;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lcom/lagradost/cloudstream3/ShowStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/Score;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Lcom/lagradost/cloudstream3/ShowStatus; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/util/List; + public final fun component13 ()Ljava/util/List; + public final fun component14 ()Lcom/lagradost/cloudstream3/Score; + public final fun component15 ()Ljava/lang/Integer; + public final fun component16 ()Ljava/util/List; + public final fun component17 ()Ljava/util/List; + public final fun component18 ()Ljava/util/List; + public final fun component19 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/util/Map; + public final fun component21 ()Ljava/util/Map; + public final fun component22 ()Lcom/lagradost/cloudstream3/NextAiring; + public final fun component23 ()Ljava/util/List; + public final fun component24 ()Ljava/lang/String; + public final fun component25 ()Ljava/lang/String; + public final fun component26 ()Ljava/lang/String; + public final fun component27 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/Integer; + public final fun component9 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lcom/lagradost/cloudstream3/ShowStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/Score;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/AnimeLoadResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AnimeLoadResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lcom/lagradost/cloudstream3/ShowStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/Score;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AnimeLoadResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getActors ()Ljava/util/List; + public fun getApiName ()Ljava/lang/String; + public fun getBackgroundPosterUrl ()Ljava/lang/String; + public fun getComingSoon ()Z + public fun getContentRating ()Ljava/lang/String; + public fun getDuration ()Ljava/lang/Integer; + public final fun getEngName ()Ljava/lang/String; + public final fun getEpisodes ()Ljava/util/Map; + public final fun getJapName ()Ljava/lang/String; + public fun getLatestEpisodes ()Ljava/util/Map; + public fun getLogoUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getNextAiring ()Lcom/lagradost/cloudstream3/NextAiring; + public fun getPlot ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getRating ()Ljava/lang/Integer; + public fun getRecommendations ()Ljava/util/List; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getSeasonNames ()Ljava/util/List; + public fun getShowStatus ()Lcom/lagradost/cloudstream3/ShowStatus; + public fun getSyncData ()Ljava/util/Map; + public final fun getSynonyms ()Ljava/util/List; + public fun getTags ()Ljava/util/List; + public fun getTotalEpisodeIndex (II)I + public fun getTrailers ()Ljava/util/List; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUniqueUrl ()Ljava/lang/String; + public fun getUrl ()Ljava/lang/String; + public fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun setActors (Ljava/util/List;)V + public fun setApiName (Ljava/lang/String;)V + public fun setBackgroundPosterUrl (Ljava/lang/String;)V + public fun setComingSoon (Z)V + public fun setContentRating (Ljava/lang/String;)V + public fun setDuration (Ljava/lang/Integer;)V + public final fun setEngName (Ljava/lang/String;)V + public final fun setEpisodes (Ljava/util/Map;)V + public final fun setJapName (Ljava/lang/String;)V + public fun setLogoUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setNextAiring (Lcom/lagradost/cloudstream3/NextAiring;)V + public fun setPlot (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setRating (Ljava/lang/Integer;)V + public fun setRecommendations (Ljava/util/List;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setSeasonNames (Ljava/util/List;)V + public fun setShowStatus (Lcom/lagradost/cloudstream3/ShowStatus;)V + public fun setSyncData (Ljava/util/Map;)V + public final fun setSynonyms (Ljava/util/List;)V + public fun setTags (Ljava/util/List;)V + public fun setTrailers (Ljava/util/List;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun setUniqueUrl (Ljava/lang/String;)V + public fun setUrl (Ljava/lang/String;)V + public fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/AnimeSearchResponse : com/lagradost/cloudstream3/SearchResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Set;Ljava/lang/String;Ljava/util/Map;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Set;Ljava/lang/String;Ljava/util/Map;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/lang/Integer; + public final fun component11 ()Lcom/lagradost/cloudstream3/SearchQuality; + public final fun component12 ()Ljava/util/Map; + public final fun component13 ()Lcom/lagradost/cloudstream3/Score; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/util/Set; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Set;Ljava/lang/String;Ljava/util/Map;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)Lcom/lagradost/cloudstream3/AnimeSearchResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Set;Ljava/lang/String;Ljava/util/Map;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AnimeSearchResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getApiName ()Ljava/lang/String; + public final fun getDubStatus ()Ljava/util/Set; + public final fun getEpisodes ()Ljava/util/Map; + public fun getId ()Ljava/lang/Integer; + public fun getName ()Ljava/lang/String; + public final fun getOtherName ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getQuality ()Lcom/lagradost/cloudstream3/SearchQuality; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUrl ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public final fun setDubStatus (Ljava/util/Set;)V + public final fun setEpisodes (Ljava/util/Map;)V + public fun setId (Ljava/lang/Integer;)V + public final fun setOtherName (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setQuality (Lcom/lagradost/cloudstream3/SearchQuality;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public final fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/AudioFile { + public static final field Companion Lcom/lagradost/cloudstream3/AudioFile$Companion; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public fun equals (Ljava/lang/Object;)Z + public final fun getHeaders ()Ljava/util/Map; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public final fun setHeaders (Ljava/util/Map;)V + public final fun setUrl (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/AudioFile$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/AudioFile$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/AudioFile; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/AudioFile;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AudioFile$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/AutoDownloadMode : java/lang/Enum { + public static final field All Lcom/lagradost/cloudstream3/AutoDownloadMode; + public static final field Companion Lcom/lagradost/cloudstream3/AutoDownloadMode$Companion; + public static final field Disable Lcom/lagradost/cloudstream3/AutoDownloadMode; + public static final field FilterByLang Lcom/lagradost/cloudstream3/AutoDownloadMode; + public static final field NsfwOnly Lcom/lagradost/cloudstream3/AutoDownloadMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/AutoDownloadMode; + public static fun values ()[Lcom/lagradost/cloudstream3/AutoDownloadMode; +} + +public final class com/lagradost/cloudstream3/AutoDownloadMode$Companion { + public final fun getEnum (I)Lcom/lagradost/cloudstream3/AutoDownloadMode; +} + +public final class com/lagradost/cloudstream3/DubStatus : java/lang/Enum { + public static final field Dubbed Lcom/lagradost/cloudstream3/DubStatus; + public static final field None Lcom/lagradost/cloudstream3/DubStatus; + public static final field Subbed Lcom/lagradost/cloudstream3/DubStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getId ()I + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/DubStatus; + public static fun values ()[Lcom/lagradost/cloudstream3/DubStatus; +} + +public final class com/lagradost/cloudstream3/Episode { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/lagradost/cloudstream3/Score; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/Long; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/Episode; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/Episode;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/Episode; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public final fun getDate ()Ljava/lang/Long; + public final fun getDescription ()Ljava/lang/String; + public final fun getEpisode ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public final fun getPosterUrl ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/Integer; + public final fun getRunTime ()Ljava/lang/Integer; + public final fun getScore ()Lcom/lagradost/cloudstream3/Score; + public final fun getSeason ()Ljava/lang/Integer; + public fun hashCode ()I + public final fun setData (Ljava/lang/String;)V + public final fun setDate (Ljava/lang/Long;)V + public final fun setDescription (Ljava/lang/String;)V + public final fun setEpisode (Ljava/lang/Integer;)V + public final fun setName (Ljava/lang/String;)V + public final fun setPosterUrl (Ljava/lang/String;)V + public final fun setRating (Ljava/lang/Integer;)V + public final fun setRunTime (Ljava/lang/Integer;)V + public final fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public final fun setSeason (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/lagradost/cloudstream3/EpisodeResponse { + public abstract fun getLatestEpisodes ()Ljava/util/Map; + public abstract fun getNextAiring ()Lcom/lagradost/cloudstream3/NextAiring; + public abstract fun getSeasonNames ()Ljava/util/List; + public abstract fun getShowStatus ()Lcom/lagradost/cloudstream3/ShowStatus; + public abstract fun getTotalEpisodeIndex (II)I + public abstract fun setNextAiring (Lcom/lagradost/cloudstream3/NextAiring;)V + public abstract fun setSeasonNames (Ljava/util/List;)V + public abstract fun setShowStatus (Lcom/lagradost/cloudstream3/ShowStatus;)V +} + +public final class com/lagradost/cloudstream3/ErrorLoadingException : java/lang/Exception { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class com/lagradost/cloudstream3/HomePageList { + public fun (Ljava/lang/String;Ljava/util/List;Z)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Z + public final fun copy (Ljava/lang/String;Ljava/util/List;Z)Lcom/lagradost/cloudstream3/HomePageList; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/HomePageList;Ljava/lang/String;Ljava/util/List;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/HomePageList; + public fun equals (Ljava/lang/Object;)Z + public final fun getList ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public final fun isHorizontalImages ()Z + public final fun setList (Ljava/util/List;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/HomePageResponse { + public fun (Ljava/util/List;Z)V + public synthetic fun (Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Z + public final fun copy (Ljava/util/List;Z)Lcom/lagradost/cloudstream3/HomePageResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/HomePageResponse;Ljava/util/List;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/HomePageResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getHasNext ()Z + public final fun getItems ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/lagradost/cloudstream3/IDownloadableMinimum { + public abstract fun getHeaders ()Ljava/util/Map; + public abstract fun getReferer ()Ljava/lang/String; + public abstract fun getUrl ()Ljava/lang/String; +} + +public abstract interface annotation class com/lagradost/cloudstream3/InternalAPI : java/lang/annotation/Annotation { +} + +public final class com/lagradost/cloudstream3/LiveSearchResponse : com/lagradost/cloudstream3/SearchResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Lcom/lagradost/cloudstream3/Score; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Lcom/lagradost/cloudstream3/SearchQuality; + public final fun component8 ()Ljava/util/Map; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;)Lcom/lagradost/cloudstream3/LiveSearchResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/LiveSearchResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/LiveSearchResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getApiName ()Ljava/lang/String; + public fun getId ()Ljava/lang/Integer; + public final fun getLang ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getQuality ()Lcom/lagradost/cloudstream3/SearchQuality; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun setId (Ljava/lang/Integer;)V + public final fun setLang (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setQuality (Lcom/lagradost/cloudstream3/SearchQuality;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/LiveStreamLoadResponse : com/lagradost/cloudstream3/LoadResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Ljava/lang/Integer; + public final fun component12 ()Ljava/util/List; + public final fun component13 ()Ljava/util/List; + public final fun component14 ()Ljava/util/List; + public final fun component15 ()Z + public final fun component16 ()Ljava/util/Map; + public final fun component17 ()Ljava/util/Map; + public final fun component18 ()Ljava/lang/String; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component9 ()Lcom/lagradost/cloudstream3/Score; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/LiveStreamLoadResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/LiveStreamLoadResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/LiveStreamLoadResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getActors ()Ljava/util/List; + public fun getApiName ()Ljava/lang/String; + public fun getBackgroundPosterUrl ()Ljava/lang/String; + public fun getComingSoon ()Z + public fun getContentRating ()Ljava/lang/String; + public final fun getDataUrl ()Ljava/lang/String; + public fun getDuration ()Ljava/lang/Integer; + public fun getLogoUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getPlot ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getRating ()Ljava/lang/Integer; + public fun getRecommendations ()Ljava/util/List; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getSyncData ()Ljava/util/Map; + public fun getTags ()Ljava/util/List; + public fun getTrailers ()Ljava/util/List; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUniqueUrl ()Ljava/lang/String; + public fun getUrl ()Ljava/lang/String; + public fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun setActors (Ljava/util/List;)V + public fun setApiName (Ljava/lang/String;)V + public fun setBackgroundPosterUrl (Ljava/lang/String;)V + public fun setComingSoon (Z)V + public fun setContentRating (Ljava/lang/String;)V + public final fun setDataUrl (Ljava/lang/String;)V + public fun setDuration (Ljava/lang/Integer;)V + public fun setLogoUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setPlot (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setRating (Ljava/lang/Integer;)V + public fun setRecommendations (Ljava/util/List;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setSyncData (Ljava/util/Map;)V + public fun setTags (Ljava/util/List;)V + public fun setTrailers (Ljava/util/List;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun setUniqueUrl (Ljava/lang/String;)V + public fun setUrl (Ljava/lang/String;)V + public fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/lagradost/cloudstream3/LoadResponse { + public static final field Companion Lcom/lagradost/cloudstream3/LoadResponse$Companion; + public abstract fun getActors ()Ljava/util/List; + public abstract fun getApiName ()Ljava/lang/String; + public abstract fun getBackgroundPosterUrl ()Ljava/lang/String; + public abstract fun getComingSoon ()Z + public abstract fun getContentRating ()Ljava/lang/String; + public abstract fun getDuration ()Ljava/lang/Integer; + public abstract fun getLogoUrl ()Ljava/lang/String; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getPlot ()Ljava/lang/String; + public abstract fun getPosterHeaders ()Ljava/util/Map; + public abstract fun getPosterUrl ()Ljava/lang/String; + public fun getRating ()Ljava/lang/Integer; + public abstract fun getRecommendations ()Ljava/util/List; + public abstract fun getScore ()Lcom/lagradost/cloudstream3/Score; + public abstract fun getSyncData ()Ljava/util/Map; + public abstract fun getTags ()Ljava/util/List; + public abstract fun getTrailers ()Ljava/util/List; + public abstract fun getType ()Lcom/lagradost/cloudstream3/TvType; + public abstract fun getUniqueUrl ()Ljava/lang/String; + public abstract fun getUrl ()Ljava/lang/String; + public abstract fun getYear ()Ljava/lang/Integer; + public abstract fun setActors (Ljava/util/List;)V + public abstract fun setApiName (Ljava/lang/String;)V + public abstract fun setBackgroundPosterUrl (Ljava/lang/String;)V + public abstract fun setComingSoon (Z)V + public abstract fun setContentRating (Ljava/lang/String;)V + public abstract fun setDuration (Ljava/lang/Integer;)V + public abstract fun setLogoUrl (Ljava/lang/String;)V + public abstract fun setName (Ljava/lang/String;)V + public abstract fun setPlot (Ljava/lang/String;)V + public abstract fun setPosterHeaders (Ljava/util/Map;)V + public abstract fun setPosterUrl (Ljava/lang/String;)V + public fun setRating (Ljava/lang/Integer;)V + public abstract fun setRecommendations (Ljava/util/List;)V + public abstract fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public abstract fun setSyncData (Ljava/util/Map;)V + public abstract fun setTags (Ljava/util/List;)V + public abstract fun setTrailers (Ljava/util/List;)V + public abstract fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public abstract fun setUniqueUrl (Ljava/lang/String;)V + public abstract fun setUrl (Ljava/lang/String;)V + public abstract fun setYear (Ljava/lang/Integer;)V +} + +public final class com/lagradost/cloudstream3/LoadResponse$Companion { + public final fun addActorNames (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/util/List;)V + public final fun addActors (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/util/List;)V + public final fun addActorsOnly (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/util/List;)V + public final fun addActorsRole (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/util/List;)V + public final fun addAniListId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/Integer;)V + public final fun addDuration (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun addIdToString (Ljava/lang/String;Lcom/lagradost/cloudstream3/SimklSyncServices;Ljava/lang/String;)Ljava/lang/String; + public final fun addImdbId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun addImdbUrl (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun addKitsuId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/Integer;)V + public final fun addKitsuId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun addMalId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/Integer;)V + public final fun addRating (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/Integer;)V + public final fun addRating (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun addScore (Lcom/lagradost/cloudstream3/LoadResponse;Lcom/lagradost/cloudstream3/Score;)V + public final fun addScore (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;I)V + public static synthetic fun addScore$default (Lcom/lagradost/cloudstream3/LoadResponse$Companion;Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;IILjava/lang/Object;)V + public final fun addSimklId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/Integer;)V + public final fun addTMDbId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun addTrailer (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;Ljava/lang/String;ZLjava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun addTrailer (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun addTrailer (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/util/List;Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun addTrailer$default (Lcom/lagradost/cloudstream3/LoadResponse$Companion;Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;Ljava/lang/String;ZLjava/util/Map;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun addTrailer$default (Lcom/lagradost/cloudstream3/LoadResponse$Companion;Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun addTrailer$default (Lcom/lagradost/cloudstream3/LoadResponse$Companion;Lcom/lagradost/cloudstream3/LoadResponse;Ljava/util/List;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun addTraktId (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;)V + public final fun getAniListId (Lcom/lagradost/cloudstream3/LoadResponse;)Ljava/lang/String; + public final fun getAniListIdPrefix ()Ljava/lang/String; + public final fun getImdbId (Lcom/lagradost/cloudstream3/LoadResponse;)Ljava/lang/String; + public final fun getKitsuId (Lcom/lagradost/cloudstream3/LoadResponse;)Ljava/lang/String; + public final fun getKitsuIdPrefix ()Ljava/lang/String; + public final fun getMalId (Lcom/lagradost/cloudstream3/LoadResponse;)Ljava/lang/String; + public final fun getMalIdPrefix ()Ljava/lang/String; + public final fun getSimklIdPrefix ()Ljava/lang/String; + public final fun getTMDbId (Lcom/lagradost/cloudstream3/LoadResponse;)Ljava/lang/String; + public final fun isMovie (Lcom/lagradost/cloudstream3/LoadResponse;)Z + public final fun isTrailersEnabled ()Z + public final fun readIdFromString (Ljava/lang/String;)Ljava/util/Map; + public final fun setAniListIdPrefix (Ljava/lang/String;)V + public final fun setKitsuIdPrefix (Ljava/lang/String;)V + public final fun setMalIdPrefix (Ljava/lang/String;)V + public final fun setSimklIdPrefix (Ljava/lang/String;)V + public final fun setTrailersEnabled (Z)V +} + +public final class com/lagradost/cloudstream3/LoadResponse$DefaultImpls { + public static fun getRating (Lcom/lagradost/cloudstream3/LoadResponse;)Ljava/lang/Integer; + public static fun setRating (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/Integer;)V +} + +public abstract class com/lagradost/cloudstream3/MainAPI { + public static final field Companion Lcom/lagradost/cloudstream3/MainAPI$Companion; + public fun ()V + public fun extractorVerifierJob (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getCanBeOverridden ()Z + public fun getGetMainPageTimeoutMs ()Ljava/lang/Long; + public fun getHasChromecastSupport ()Z + public fun getHasDownloadSupport ()Z + public fun getHasMainPage ()Z + public fun getHasQuickSearch ()Z + public fun getInstantLinkLoading ()Z + public fun getLang ()Ljava/lang/String; + public final fun getLastHomepageRequest ()J + public fun getLoadLinksTimeoutMs ()Ljava/lang/Long; + public fun getLoadTimeoutMs ()Ljava/lang/Long; + public fun getLoadUrl (Lcom/lagradost/cloudstream3/syncproviders/SyncIdName;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getMainPage ()Ljava/util/List; + public fun getMainPage (ILcom/lagradost/cloudstream3/MainPageRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getProviderType ()Lcom/lagradost/cloudstream3/ProviderType; + public fun getQuickSearchTimeoutMs ()Ljava/lang/Long; + public fun getSearchTimeoutMs ()Ljava/lang/Long; + public fun getSequentialMainPage ()Z + public fun getSequentialMainPageDelay ()J + public fun getSequentialMainPageScrollDelay ()J + public final fun getSourcePlugin ()Ljava/lang/String; + public fun getStoredCredentials ()Ljava/lang/String; + public fun getSupportedSyncNames ()Ljava/util/Set; + public fun getSupportedTypes ()Ljava/util/Set; + public fun getUsesWebView ()Z + public fun getVideoInterceptor (Lcom/lagradost/cloudstream3/utils/ExtractorLink;)Lokhttp3/Interceptor; + public fun getVpnStatus ()Lcom/lagradost/cloudstream3/VPNStatus; + public final fun init ()V + public fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun loadLinks (Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun overrideWithNewData (Lcom/lagradost/cloudstream3/ProvidersInfoJson;)V + public fun quickSearch (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun search (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setCanBeOverridden (Z)V + public fun setLang (Ljava/lang/String;)V + public final fun setLastHomepageRequest (J)V + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setSequentialMainPage (Z)V + public fun setSequentialMainPageDelay (J)V + public fun setSequentialMainPageScrollDelay (J)V + public final fun setSourcePlugin (Ljava/lang/String;)V + public fun setStoredCredentials (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/MainAPI$Companion { + public final fun getOverrideData ()Ljava/util/HashMap; + public final fun getSettingsForProvider ()Lcom/lagradost/cloudstream3/SettingsJson; + public final fun setOverrideData (Ljava/util/HashMap;)V + public final fun setSettingsForProvider (Lcom/lagradost/cloudstream3/SettingsJson;)V +} + +public final class com/lagradost/cloudstream3/MainAPIKt { + public static final field AllLanguagesName Ljava/lang/String; + public static final field PROVIDER_STATUS_BETA_ONLY I + public static final field PROVIDER_STATUS_DOWN I + public static final field PROVIDER_STATUS_KEY Ljava/lang/String; + public static final field PROVIDER_STATUS_OK I + public static final field PROVIDER_STATUS_SLOW I + public static final field USER_AGENT Ljava/lang/String; + public static final fun addDate (Lcom/lagradost/cloudstream3/Episode;Ljava/lang/String;Ljava/lang/String;)V + public static final fun addDate (Lcom/lagradost/cloudstream3/Episode;Ljava/util/Date;)V + public static final fun addDate (Lcom/lagradost/cloudstream3/Episode;Lkotlin/time/Instant;)V + public static final fun addDate (Lcom/lagradost/cloudstream3/Episode;Lkotlinx/datetime/LocalDate;)V + public static synthetic fun addDate$default (Lcom/lagradost/cloudstream3/Episode;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V + public static final fun addDub (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Ljava/lang/Integer;)V + public static final fun addDubStatus (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Lcom/lagradost/cloudstream3/DubStatus;Ljava/lang/Integer;)V + public static final fun addDubStatus (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Ljava/lang/String;Ljava/lang/Integer;)V + public static final fun addDubStatus (Lcom/lagradost/cloudstream3/AnimeSearchResponse;ZLjava/lang/Integer;)V + public static final fun addDubStatus (Lcom/lagradost/cloudstream3/AnimeSearchResponse;ZZLjava/lang/Integer;Ljava/lang/Integer;)V + public static synthetic fun addDubStatus$default (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Lcom/lagradost/cloudstream3/DubStatus;Ljava/lang/Integer;ILjava/lang/Object;)V + public static synthetic fun addDubStatus$default (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)V + public static synthetic fun addDubStatus$default (Lcom/lagradost/cloudstream3/AnimeSearchResponse;ZLjava/lang/Integer;ILjava/lang/Object;)V + public static synthetic fun addDubStatus$default (Lcom/lagradost/cloudstream3/AnimeSearchResponse;ZZLjava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)V + public static final fun addEpisodes (Lcom/lagradost/cloudstream3/AnimeLoadResponse;Lcom/lagradost/cloudstream3/DubStatus;Ljava/util/List;)V + public static final fun addPoster (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;Ljava/util/Map;)V + public static final fun addPoster (Lcom/lagradost/cloudstream3/SearchResponse;Ljava/lang/String;Ljava/util/Map;)V + public static synthetic fun addPoster$default (Lcom/lagradost/cloudstream3/LoadResponse;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V + public static synthetic fun addPoster$default (Lcom/lagradost/cloudstream3/SearchResponse;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V + public static final fun addQuality (Lcom/lagradost/cloudstream3/SearchResponse;Ljava/lang/String;)V + public static final fun addSeasonNamesSeasonData (Lcom/lagradost/cloudstream3/EpisodeResponse;Ljava/util/List;)V + public static final fun addSeasonNamesString (Lcom/lagradost/cloudstream3/EpisodeResponse;Ljava/util/List;)V + public static final fun addSub (Lcom/lagradost/cloudstream3/AnimeSearchResponse;Ljava/lang/Integer;)V + public static final fun base64Decode (Ljava/lang/String;)Ljava/lang/String; + public static final fun base64DecodeArray (Ljava/lang/String;)[B + public static final fun base64Encode ([B)Ljava/lang/String; + public static final fun capitalizeString (Ljava/lang/String;)Ljava/lang/String; + public static final fun capitalizeStringNullable (Ljava/lang/String;)Ljava/lang/String; + public static final fun fetchUrls (Ljava/lang/String;)Ljava/util/List; + public static final fun fixTitle (Ljava/lang/String;)Ljava/lang/String; + public static final fun fixUrl (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;)Ljava/lang/String; + public static final fun fixUrlNull (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;)Ljava/lang/String; + public static final fun getDurationFromString (Ljava/lang/String;)Ljava/lang/Integer; + public static final fun getFolderPrefix (Lcom/lagradost/cloudstream3/TvType;)Ljava/lang/String; + public static final fun getJson ()Lkotlinx/serialization/json/Json; + public static final fun getMapper ()Lcom/fasterxml/jackson/databind/json/JsonMapper; + public static final fun getQualityFromString (Ljava/lang/String;)Lcom/lagradost/cloudstream3/SearchQuality; + public static final fun getRhinoContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun imdbUrlToId (Ljava/lang/String;)Ljava/lang/String; + public static final fun imdbUrlToIdNullable (Ljava/lang/String;)Ljava/lang/String; + public static final fun isAnimeBased (Lcom/lagradost/cloudstream3/LoadResponse;)Z + public static final fun isAnimeOp (Lcom/lagradost/cloudstream3/TvType;)Z + public static final fun isAudioType (Lcom/lagradost/cloudstream3/TvType;)Z + public static final fun isEpisodeBased (Lcom/lagradost/cloudstream3/LoadResponse;)Z + public static final fun isEpisodeBased (Lcom/lagradost/cloudstream3/TvType;)Z + public static final fun isLiveStream (Lcom/lagradost/cloudstream3/TvType;)Z + public static final fun isMovieType (Lcom/lagradost/cloudstream3/TvType;)Z + public static final fun isUpcoming (Ljava/lang/String;)Z + public static final fun mainPage (Ljava/lang/String;Ljava/lang/String;Z)Lcom/lagradost/cloudstream3/MainPageData; + public static synthetic fun mainPage$default (Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/MainPageData; + public static final fun mainPageOf ([Lcom/lagradost/cloudstream3/MainPageData;)Ljava/util/List; + public static final fun mainPageOf ([Lkotlin/Pair;)Ljava/util/List; + public static final fun newAnimeLoadResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newAnimeLoadResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newAnimeSearchResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/AnimeSearchResponse; + public static synthetic fun newAnimeSearchResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/AnimeSearchResponse; + public static final fun newAudioFile (Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newAudioFile$default (Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newEpisode (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/Episode; + public static final fun newEpisode (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Z)Lcom/lagradost/cloudstream3/Episode; + public static synthetic fun newEpisode$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/Episode; + public static synthetic fun newEpisode$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/Episode; + public static final fun newHomePageResponse (Lcom/lagradost/cloudstream3/HomePageList;Ljava/lang/Boolean;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static final fun newHomePageResponse (Lcom/lagradost/cloudstream3/MainPageRequest;Ljava/util/List;Ljava/lang/Boolean;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static final fun newHomePageResponse (Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static final fun newHomePageResponse (Ljava/util/List;Ljava/lang/Boolean;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static synthetic fun newHomePageResponse$default (Lcom/lagradost/cloudstream3/HomePageList;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static synthetic fun newHomePageResponse$default (Lcom/lagradost/cloudstream3/MainPageRequest;Ljava/util/List;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static synthetic fun newHomePageResponse$default (Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static synthetic fun newHomePageResponse$default (Ljava/util/List;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/HomePageResponse; + public static final fun newLiveSearchResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/LiveSearchResponse; + public static synthetic fun newLiveSearchResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/LiveSearchResponse; + public static final fun newLiveStreamLoadResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newLiveStreamLoadResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newMovieLoadResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun newMovieLoadResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newMovieLoadResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun newMovieLoadResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newMovieSearchResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/MovieSearchResponse; + public static synthetic fun newMovieSearchResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/MovieSearchResponse; + public static final fun newSearchResponseList (Ljava/util/List;Ljava/lang/Boolean;)Lcom/lagradost/cloudstream3/SearchResponseList; + public static synthetic fun newSearchResponseList$default (Ljava/util/List;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/SearchResponseList; + public static final fun newSubtitleFile (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newSubtitleFile$default (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newTorrentLoadResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newTorrentLoadResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newTorrentSearchResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/TorrentSearchResponse; + public static synthetic fun newTorrentSearchResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TorrentSearchResponse; + public static final fun newTvSeriesLoadResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/util/List;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newTvSeriesLoadResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/util/List;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newTvSeriesSearchResponse (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/TvSeriesSearchResponse; + public static synthetic fun newTvSeriesSearchResponse$default (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TvSeriesSearchResponse; + public static final fun sortUrls (Ljava/util/Set;)Ljava/util/List; + public static final fun toNewSearchResponseList (Ljava/util/List;Ljava/lang/Boolean;)Lcom/lagradost/cloudstream3/SearchResponseList; + public static synthetic fun toNewSearchResponseList$default (Ljava/util/List;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/SearchResponseList; + public static final fun toRatingInt (Ljava/lang/String;)Ljava/lang/Integer; + public static final fun updateUrl (Lcom/lagradost/cloudstream3/MainAPI;Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/MainActivityKt { + public static final fun getApp ()Lcom/lagradost/nicehttp/Requests; + public static final fun getInsecureApp ()Lcom/lagradost/nicehttp/Requests; + public static final fun setApp (Lcom/lagradost/nicehttp/Requests;)V + public static final fun setInsecureApp (Lcom/lagradost/nicehttp/Requests;)V +} + +public final class com/lagradost/cloudstream3/MainPageData { + public fun (Ljava/lang/String;Ljava/lang/String;Z)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun copy (Ljava/lang/String;Ljava/lang/String;Z)Lcom/lagradost/cloudstream3/MainPageData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/MainPageData;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/MainPageData; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public final fun getHorizontalImages ()Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/MainPageRequest { + public fun (Ljava/lang/String;Ljava/lang/String;Z)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun copy (Ljava/lang/String;Ljava/lang/String;Z)Lcom/lagradost/cloudstream3/MainPageRequest; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/MainPageRequest;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/MainPageRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public final fun getHorizontalImages ()Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/MovieLoadResponse : com/lagradost/cloudstream3/LoadResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Ljava/lang/Integer; + public final fun component12 ()Ljava/util/List; + public final fun component13 ()Ljava/util/List; + public final fun component14 ()Ljava/util/List; + public final fun component15 ()Z + public final fun component16 ()Ljava/util/Map; + public final fun component17 ()Ljava/util/Map; + public final fun component18 ()Ljava/lang/String; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Lcom/lagradost/cloudstream3/Score; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/MovieLoadResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/MovieLoadResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/MovieLoadResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getActors ()Ljava/util/List; + public fun getApiName ()Ljava/lang/String; + public fun getBackgroundPosterUrl ()Ljava/lang/String; + public fun getComingSoon ()Z + public fun getContentRating ()Ljava/lang/String; + public final fun getDataUrl ()Ljava/lang/String; + public fun getDuration ()Ljava/lang/Integer; + public fun getLogoUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getPlot ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getRating ()Ljava/lang/Integer; + public fun getRecommendations ()Ljava/util/List; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getSyncData ()Ljava/util/Map; + public fun getTags ()Ljava/util/List; + public fun getTrailers ()Ljava/util/List; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUniqueUrl ()Ljava/lang/String; + public fun getUrl ()Ljava/lang/String; + public fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun setActors (Ljava/util/List;)V + public fun setApiName (Ljava/lang/String;)V + public fun setBackgroundPosterUrl (Ljava/lang/String;)V + public fun setComingSoon (Z)V + public fun setContentRating (Ljava/lang/String;)V + public final fun setDataUrl (Ljava/lang/String;)V + public fun setDuration (Ljava/lang/Integer;)V + public fun setLogoUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setPlot (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setRating (Ljava/lang/Integer;)V + public fun setRecommendations (Ljava/util/List;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setSyncData (Ljava/util/Map;)V + public fun setTags (Ljava/util/List;)V + public fun setTrailers (Ljava/util/List;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun setUniqueUrl (Ljava/lang/String;)V + public fun setUrl (Ljava/lang/String;)V + public fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/MovieSearchResponse : com/lagradost/cloudstream3/SearchResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Lcom/lagradost/cloudstream3/Score; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Lcom/lagradost/cloudstream3/SearchQuality; + public final fun component9 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)Lcom/lagradost/cloudstream3/MovieSearchResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/MovieSearchResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/MovieSearchResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getApiName ()Ljava/lang/String; + public fun getId ()Ljava/lang/Integer; + public fun getName ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getQuality ()Lcom/lagradost/cloudstream3/SearchQuality; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUrl ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun setId (Ljava/lang/Integer;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setQuality (Lcom/lagradost/cloudstream3/SearchQuality;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public final fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/NextAiring { + public fun (IJLjava/lang/Integer;)V + public synthetic fun (IJLjava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()J + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (IJLjava/lang/Integer;)Lcom/lagradost/cloudstream3/NextAiring; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/NextAiring;IJLjava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/NextAiring; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisode ()I + public final fun getSeason ()Ljava/lang/Integer; + public final fun getUnixTime ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/ParCollectionsKt { + public static final fun amap (Ljava/util/List;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun amap (Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun amapIndexed (Ljava/util/List;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun apmap (Ljava/util/List;Lkotlin/jvm/functions/Function2;)Ljava/util/List; + public static final fun apmap (Ljava/util/Map;Lkotlin/jvm/functions/Function2;)Ljava/util/List; + public static final fun apmapIndexed (Ljava/util/List;Lkotlin/jvm/functions/Function3;)Ljava/util/List; + public static final fun argamap ([Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun runAllAsync ([Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface annotation class com/lagradost/cloudstream3/Prerelease : java/lang/annotation/Annotation { +} + +public final class com/lagradost/cloudstream3/ProviderType : java/lang/Enum { + public static final field DirectProvider Lcom/lagradost/cloudstream3/ProviderType; + public static final field MetaProvider Lcom/lagradost/cloudstream3/ProviderType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/ProviderType; + public static fun values ()[Lcom/lagradost/cloudstream3/ProviderType; +} + +public final class com/lagradost/cloudstream3/ProvidersInfoJson { + public static final field Companion Lcom/lagradost/cloudstream3/ProvidersInfoJson$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()I + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Lcom/lagradost/cloudstream3/ProvidersInfoJson; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/ProvidersInfoJson;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/Object;)Lcom/lagradost/cloudstream3/ProvidersInfoJson; + public fun equals (Ljava/lang/Object;)Z + public final fun getCredentials ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getStatus ()I + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public final fun setCredentials (Ljava/lang/String;)V + public final fun setName (Ljava/lang/String;)V + public final fun setStatus (I)V + public final fun setUrl (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/ProvidersInfoJson$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/ProvidersInfoJson$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/ProvidersInfoJson; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/ProvidersInfoJson;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/ProvidersInfoJson$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/Score { + public static final field Companion Lcom/lagradost/cloudstream3/Score$Companion; + public static final field MAX I + public static final field MAX_ZEROS I + public static final field MIN I + public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun toByte (I)B + public final fun toDouble (I)D + public static synthetic fun toDouble$default (Lcom/lagradost/cloudstream3/Score;IILjava/lang/Object;)D + public final fun toFloat (I)F + public static synthetic fun toFloat$default (Lcom/lagradost/cloudstream3/Score;IILjava/lang/Object;)F + public final fun toInt (I)I + public static synthetic fun toInt$default (Lcom/lagradost/cloudstream3/Score;IILjava/lang/Object;)I + public final fun toLong (I)J + public static synthetic fun toLong$default (Lcom/lagradost/cloudstream3/Score;IILjava/lang/Object;)J + public final fun toOld ()I + public fun toString ()Ljava/lang/String; + public final fun toString (IIZC)Ljava/lang/String; + public static synthetic fun toString$default (Lcom/lagradost/cloudstream3/Score;IIZCILjava/lang/Object;)Ljava/lang/String; + public final fun toStringNull (DIIZC)Ljava/lang/String; + public static synthetic fun toStringNull$default (Lcom/lagradost/cloudstream3/Score;DIIZCILjava/lang/Object;)Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/Score$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/Score$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/Score; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/Score;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/Score$Companion { + public final fun from (Ljava/lang/Double;I)Lcom/lagradost/cloudstream3/Score; + public final fun from (Ljava/lang/Float;I)Lcom/lagradost/cloudstream3/Score; + public final fun from (Ljava/lang/Integer;I)Lcom/lagradost/cloudstream3/Score; + public final fun from (Ljava/lang/String;I)Lcom/lagradost/cloudstream3/Score; + public final fun from10 (Ljava/lang/Double;)Lcom/lagradost/cloudstream3/Score; + public final fun from10 (Ljava/lang/Float;)Lcom/lagradost/cloudstream3/Score; + public final fun from10 (Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/Score; + public final fun from10 (Ljava/lang/String;)Lcom/lagradost/cloudstream3/Score; + public final fun from100 (Ljava/lang/Double;)Lcom/lagradost/cloudstream3/Score; + public final fun from100 (Ljava/lang/Float;)Lcom/lagradost/cloudstream3/Score; + public final fun from100 (Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/Score; + public final fun from100 (Ljava/lang/String;)Lcom/lagradost/cloudstream3/Score; + public final fun from5 (Ljava/lang/Double;)Lcom/lagradost/cloudstream3/Score; + public final fun from5 (Ljava/lang/Float;)Lcom/lagradost/cloudstream3/Score; + public final fun from5 (Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/Score; + public final fun from5 (Ljava/lang/String;)Lcom/lagradost/cloudstream3/Score; + public final fun fromOld (Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/Score; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/SearchQuality : java/lang/Enum { + public static final field BlueRay Lcom/lagradost/cloudstream3/SearchQuality; + public static final field Cam Lcom/lagradost/cloudstream3/SearchQuality; + public static final field CamRip Lcom/lagradost/cloudstream3/SearchQuality; + public static final field DVD Lcom/lagradost/cloudstream3/SearchQuality; + public static final field FourK Lcom/lagradost/cloudstream3/SearchQuality; + public static final field HD Lcom/lagradost/cloudstream3/SearchQuality; + public static final field HDR Lcom/lagradost/cloudstream3/SearchQuality; + public static final field HQ Lcom/lagradost/cloudstream3/SearchQuality; + public static final field HdCam Lcom/lagradost/cloudstream3/SearchQuality; + public static final field SD Lcom/lagradost/cloudstream3/SearchQuality; + public static final field SDR Lcom/lagradost/cloudstream3/SearchQuality; + public static final field Telecine Lcom/lagradost/cloudstream3/SearchQuality; + public static final field Telesync Lcom/lagradost/cloudstream3/SearchQuality; + public static final field UHD Lcom/lagradost/cloudstream3/SearchQuality; + public static final field WebRip Lcom/lagradost/cloudstream3/SearchQuality; + public static final field WorkPrint Lcom/lagradost/cloudstream3/SearchQuality; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/SearchQuality; + public static fun values ()[Lcom/lagradost/cloudstream3/SearchQuality; +} + +public abstract interface class com/lagradost/cloudstream3/SearchResponse { + public abstract fun getApiName ()Ljava/lang/String; + public abstract fun getId ()Ljava/lang/Integer; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getPosterHeaders ()Ljava/util/Map; + public abstract fun getPosterUrl ()Ljava/lang/String; + public abstract fun getQuality ()Lcom/lagradost/cloudstream3/SearchQuality; + public abstract fun getScore ()Lcom/lagradost/cloudstream3/Score; + public abstract fun getType ()Lcom/lagradost/cloudstream3/TvType; + public abstract fun getUrl ()Ljava/lang/String; + public abstract fun setId (Ljava/lang/Integer;)V + public abstract fun setPosterHeaders (Ljava/util/Map;)V + public abstract fun setPosterUrl (Ljava/lang/String;)V + public abstract fun setQuality (Lcom/lagradost/cloudstream3/SearchQuality;)V + public abstract fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public abstract fun setType (Lcom/lagradost/cloudstream3/TvType;)V +} + +public final class com/lagradost/cloudstream3/SearchResponseList { + public fun (Ljava/util/List;Z)V + public synthetic fun (Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Z + public final fun copy (Ljava/util/List;Z)Lcom/lagradost/cloudstream3/SearchResponseList; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/SearchResponseList;Ljava/util/List;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/SearchResponseList; + public fun equals (Ljava/lang/Object;)Z + public final fun getHasNext ()Z + public final fun getItems ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/SeasonData { + public static final field Companion Lcom/lagradost/cloudstream3/SeasonData$Companion; + public fun (ILjava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (ILjava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (ILjava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/SeasonData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/SeasonData;ILjava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/SeasonData; + public fun equals (Ljava/lang/Object;)Z + public final fun getDisplaySeason ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public final fun getSeason ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/SeasonData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/SeasonData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/SeasonData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/SeasonData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/SeasonData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/SettingsJson { + public static final field Companion Lcom/lagradost/cloudstream3/SettingsJson$Companion; + public fun ()V + public fun (Z)V + public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun copy (Z)Lcom/lagradost/cloudstream3/SettingsJson; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/SettingsJson;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/SettingsJson; + public fun equals (Ljava/lang/Object;)Z + public final fun getEnableAdult ()Z + public fun hashCode ()I + public final fun setEnableAdult (Z)V + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/SettingsJson$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/SettingsJson$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/SettingsJson; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/SettingsJson;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/SettingsJson$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/ShowStatus : java/lang/Enum { + public static final field Completed Lcom/lagradost/cloudstream3/ShowStatus; + public static final field Ongoing Lcom/lagradost/cloudstream3/ShowStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/ShowStatus; + public static fun values ()[Lcom/lagradost/cloudstream3/ShowStatus; +} + +public final class com/lagradost/cloudstream3/SimklSyncServices : java/lang/Enum { + public static final field AniList Lcom/lagradost/cloudstream3/SimklSyncServices; + public static final field Imdb Lcom/lagradost/cloudstream3/SimklSyncServices; + public static final field Mal Lcom/lagradost/cloudstream3/SimklSyncServices; + public static final field Simkl Lcom/lagradost/cloudstream3/SimklSyncServices; + public static final field Tmdb Lcom/lagradost/cloudstream3/SimklSyncServices; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getOriginalName ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/SimklSyncServices; + public static fun values ()[Lcom/lagradost/cloudstream3/SimklSyncServices; +} + +public final class com/lagradost/cloudstream3/SubtitleFile { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/SubtitleFile; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/SubtitleFile;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/SubtitleFile; + public fun equals (Ljava/lang/Object;)Z + public final fun getHeaders ()Ljava/util/Map; + public final fun getLang ()Ljava/lang/String; + public final fun getLangTag ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public final fun setHeaders (Ljava/util/Map;)V + public final fun setLang (Ljava/lang/String;)V + public final fun setUrl (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/TorrentLoadResponse : com/lagradost/cloudstream3/LoadResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Lcom/lagradost/cloudstream3/Score; + public final fun component11 ()Ljava/util/List; + public final fun component12 ()Ljava/lang/Integer; + public final fun component13 ()Ljava/util/List; + public final fun component14 ()Ljava/util/List; + public final fun component15 ()Ljava/util/List; + public final fun component16 ()Z + public final fun component17 ()Ljava/util/Map; + public final fun component18 ()Ljava/util/Map; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/String; + public final fun component22 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/TorrentLoadResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/TorrentLoadResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TorrentLoadResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getActors ()Ljava/util/List; + public fun getApiName ()Ljava/lang/String; + public fun getBackgroundPosterUrl ()Ljava/lang/String; + public fun getComingSoon ()Z + public fun getContentRating ()Ljava/lang/String; + public fun getDuration ()Ljava/lang/Integer; + public fun getLogoUrl ()Ljava/lang/String; + public final fun getMagnet ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getPlot ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getRating ()Ljava/lang/Integer; + public fun getRecommendations ()Ljava/util/List; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getSyncData ()Ljava/util/Map; + public fun getTags ()Ljava/util/List; + public final fun getTorrent ()Ljava/lang/String; + public fun getTrailers ()Ljava/util/List; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUniqueUrl ()Ljava/lang/String; + public fun getUrl ()Ljava/lang/String; + public fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun setActors (Ljava/util/List;)V + public fun setApiName (Ljava/lang/String;)V + public fun setBackgroundPosterUrl (Ljava/lang/String;)V + public fun setComingSoon (Z)V + public fun setContentRating (Ljava/lang/String;)V + public fun setDuration (Ljava/lang/Integer;)V + public fun setLogoUrl (Ljava/lang/String;)V + public final fun setMagnet (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setPlot (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setRating (Ljava/lang/Integer;)V + public fun setRecommendations (Ljava/util/List;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setSyncData (Ljava/util/Map;)V + public fun setTags (Ljava/util/List;)V + public final fun setTorrent (Ljava/lang/String;)V + public fun setTrailers (Ljava/util/List;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun setUniqueUrl (Ljava/lang/String;)V + public fun setUrl (Ljava/lang/String;)V + public fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/TorrentSearchResponse : com/lagradost/cloudstream3/SearchResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Lcom/lagradost/cloudstream3/SearchQuality; + public final fun component8 ()Ljava/util/Map; + public final fun component9 ()Lcom/lagradost/cloudstream3/Score; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)Lcom/lagradost/cloudstream3/TorrentSearchResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/TorrentSearchResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TorrentSearchResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getApiName ()Ljava/lang/String; + public fun getId ()Ljava/lang/Integer; + public fun getName ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getQuality ()Lcom/lagradost/cloudstream3/SearchQuality; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun setId (Ljava/lang/Integer;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setQuality (Lcom/lagradost/cloudstream3/SearchQuality;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/Tracker { + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/Tracker; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/Tracker;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/Tracker; + public fun equals (Ljava/lang/Object;)Z + public final fun getAniId ()Ljava/lang/String; + public final fun getCover ()Ljava/lang/String; + public final fun getImage ()Ljava/lang/String; + public final fun getKitsuId ()Ljava/lang/String; + public final fun getMalId ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/TrackerType : java/lang/Enum { + public static final field Companion Lcom/lagradost/cloudstream3/TrackerType$Companion; + public static final field MOVIE Lcom/lagradost/cloudstream3/TrackerType; + public static final field MUSIC Lcom/lagradost/cloudstream3/TrackerType; + public static final field ONA Lcom/lagradost/cloudstream3/TrackerType; + public static final field OVA Lcom/lagradost/cloudstream3/TrackerType; + public static final field SPECIAL Lcom/lagradost/cloudstream3/TrackerType; + public static final field TV Lcom/lagradost/cloudstream3/TrackerType; + public static final field TV_SHORT Lcom/lagradost/cloudstream3/TrackerType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/TrackerType; + public static fun values ()[Lcom/lagradost/cloudstream3/TrackerType; +} + +public final class com/lagradost/cloudstream3/TrackerType$Companion { + public final fun getTypes (Lcom/lagradost/cloudstream3/TvType;)Ljava/util/Set; +} + +public final class com/lagradost/cloudstream3/TrailerData { + public fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Map;)Lcom/lagradost/cloudstream3/TrailerData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/TrailerData;Ljava/lang/String;Ljava/lang/String;ZLjava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TrailerData; + public fun equals (Ljava/lang/Object;)Z + public final fun getExtractorUrl ()Ljava/lang/String; + public final fun getHeaders ()Ljava/util/Map; + public final fun getRaw ()Z + public final fun getReferer ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/TvSeriesLoadResponse : com/lagradost/cloudstream3/EpisodeResponse, com/lagradost/cloudstream3/LoadResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/ShowStatus;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/ShowStatus;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Lcom/lagradost/cloudstream3/Score; + public final fun component11 ()Ljava/util/List; + public final fun component12 ()Ljava/lang/Integer; + public final fun component13 ()Ljava/util/List; + public final fun component14 ()Ljava/util/List; + public final fun component15 ()Ljava/util/List; + public final fun component16 ()Z + public final fun component17 ()Ljava/util/Map; + public final fun component18 ()Ljava/util/Map; + public final fun component19 ()Lcom/lagradost/cloudstream3/NextAiring; + public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/util/List; + public final fun component21 ()Ljava/lang/String; + public final fun component22 ()Ljava/lang/String; + public final fun component23 ()Ljava/lang/String; + public final fun component24 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Lcom/lagradost/cloudstream3/ShowStatus; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/ShowStatus;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/TvSeriesLoadResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/TvSeriesLoadResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/ShowStatus;Lcom/lagradost/cloudstream3/Score;Ljava/util/List;Ljava/lang/Integer;Ljava/util/List;Ljava/util/List;Ljava/util/List;ZLjava/util/Map;Ljava/util/Map;Lcom/lagradost/cloudstream3/NextAiring;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TvSeriesLoadResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getActors ()Ljava/util/List; + public fun getApiName ()Ljava/lang/String; + public fun getBackgroundPosterUrl ()Ljava/lang/String; + public fun getComingSoon ()Z + public fun getContentRating ()Ljava/lang/String; + public fun getDuration ()Ljava/lang/Integer; + public final fun getEpisodes ()Ljava/util/List; + public fun getLatestEpisodes ()Ljava/util/Map; + public fun getLogoUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getNextAiring ()Lcom/lagradost/cloudstream3/NextAiring; + public fun getPlot ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getRating ()Ljava/lang/Integer; + public fun getRecommendations ()Ljava/util/List; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getSeasonNames ()Ljava/util/List; + public fun getShowStatus ()Lcom/lagradost/cloudstream3/ShowStatus; + public fun getSyncData ()Ljava/util/Map; + public fun getTags ()Ljava/util/List; + public fun getTotalEpisodeIndex (II)I + public fun getTrailers ()Ljava/util/List; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUniqueUrl ()Ljava/lang/String; + public fun getUrl ()Ljava/lang/String; + public fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun setActors (Ljava/util/List;)V + public fun setApiName (Ljava/lang/String;)V + public fun setBackgroundPosterUrl (Ljava/lang/String;)V + public fun setComingSoon (Z)V + public fun setContentRating (Ljava/lang/String;)V + public fun setDuration (Ljava/lang/Integer;)V + public final fun setEpisodes (Ljava/util/List;)V + public fun setLogoUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setNextAiring (Lcom/lagradost/cloudstream3/NextAiring;)V + public fun setPlot (Ljava/lang/String;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setRating (Ljava/lang/Integer;)V + public fun setRecommendations (Ljava/util/List;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setSeasonNames (Ljava/util/List;)V + public fun setShowStatus (Lcom/lagradost/cloudstream3/ShowStatus;)V + public fun setSyncData (Ljava/util/Map;)V + public fun setTags (Ljava/util/List;)V + public fun setTrailers (Ljava/util/List;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public fun setUniqueUrl (Ljava/lang/String;)V + public fun setUrl (Ljava/lang/String;)V + public fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/TvSeriesSearchResponse : com/lagradost/cloudstream3/SearchResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/util/Map; + public final fun component11 ()Lcom/lagradost/cloudstream3/Score; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Ljava/lang/Integer; + public final fun component9 ()Lcom/lagradost/cloudstream3/SearchQuality; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;)Lcom/lagradost/cloudstream3/TvSeriesSearchResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/TvSeriesSearchResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/TvType;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/SearchQuality;Ljava/util/Map;Lcom/lagradost/cloudstream3/Score;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/TvSeriesSearchResponse; + public fun equals (Ljava/lang/Object;)Z + public fun getApiName ()Ljava/lang/String; + public final fun getEpisodes ()Ljava/lang/Integer; + public fun getId ()Ljava/lang/Integer; + public fun getName ()Ljava/lang/String; + public fun getPosterHeaders ()Ljava/util/Map; + public fun getPosterUrl ()Ljava/lang/String; + public fun getQuality ()Lcom/lagradost/cloudstream3/SearchQuality; + public fun getScore ()Lcom/lagradost/cloudstream3/Score; + public fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun getUrl ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public final fun setEpisodes (Ljava/lang/Integer;)V + public fun setId (Ljava/lang/Integer;)V + public fun setPosterHeaders (Ljava/util/Map;)V + public fun setPosterUrl (Ljava/lang/String;)V + public fun setQuality (Lcom/lagradost/cloudstream3/SearchQuality;)V + public fun setScore (Lcom/lagradost/cloudstream3/Score;)V + public fun setType (Lcom/lagradost/cloudstream3/TvType;)V + public final fun setYear (Ljava/lang/Integer;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/TvType : java/lang/Enum { + public static final field Anime Lcom/lagradost/cloudstream3/TvType; + public static final field AnimeMovie Lcom/lagradost/cloudstream3/TvType; + public static final field AsianDrama Lcom/lagradost/cloudstream3/TvType; + public static final field Audio Lcom/lagradost/cloudstream3/TvType; + public static final field AudioBook Lcom/lagradost/cloudstream3/TvType; + public static final field Cartoon Lcom/lagradost/cloudstream3/TvType; + public static final field CustomMedia Lcom/lagradost/cloudstream3/TvType; + public static final field Documentary Lcom/lagradost/cloudstream3/TvType; + public static final field Live Lcom/lagradost/cloudstream3/TvType; + public static final field Movie Lcom/lagradost/cloudstream3/TvType; + public static final field Music Lcom/lagradost/cloudstream3/TvType; + public static final field NSFW Lcom/lagradost/cloudstream3/TvType; + public static final field OVA Lcom/lagradost/cloudstream3/TvType; + public static final field Others Lcom/lagradost/cloudstream3/TvType; + public static final field Podcast Lcom/lagradost/cloudstream3/TvType; + public static final field Torrent Lcom/lagradost/cloudstream3/TvType; + public static final field TvSeries Lcom/lagradost/cloudstream3/TvType; + public static final field Video Lcom/lagradost/cloudstream3/TvType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/TvType; + public static fun values ()[Lcom/lagradost/cloudstream3/TvType; +} + +public abstract interface annotation class com/lagradost/cloudstream3/UnsafeSSL : java/lang/annotation/Annotation { +} + +public final class com/lagradost/cloudstream3/VPNStatus : java/lang/Enum { + public static final field MightBeNeeded Lcom/lagradost/cloudstream3/VPNStatus; + public static final field None Lcom/lagradost/cloudstream3/VPNStatus; + public static final field Torrent Lcom/lagradost/cloudstream3/VPNStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/VPNStatus; + public static fun values ()[Lcom/lagradost/cloudstream3/VPNStatus; +} + +public class com/lagradost/cloudstream3/extractors/Acefile : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Acefile$Source { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Acefile$Source; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Acefile$Source;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Acefile$Source; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/AesHelper { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/AesHelper; + public final fun decryptAES (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Ahvsh : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Aico : com/lagradost/cloudstream3/extractors/Hxfile { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Asnwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Auvexiug : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Awish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/BgwpCC : com/lagradost/cloudstream3/extractors/BigwarpIO { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/BigwarpArt : com/lagradost/cloudstream3/extractors/BigwarpIO { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/BigwarpIO : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Blogger : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/ByseBuho : com/lagradost/cloudstream3/extractors/ByseSX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/ByseQekaho : com/lagradost/cloudstream3/extractors/ByseSX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/ByseSX : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/ByseVepoin : com/lagradost/cloudstream3/extractors/ByseSX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Bysezejataos : com/lagradost/cloudstream3/extractors/ByseSX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Cavanhabg : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Cda : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Cda$PlayerData { + public fun (Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;)Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData;Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData; + public fun equals (Ljava/lang/Object;)Z + public final fun getVideo ()Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Cda$VideoPlayerData { + public fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getHash2 ()Ljava/lang/String; + public final fun getQualities ()Ljava/util/Map; + public final fun getQuality ()Ljava/lang/String; + public final fun getTs ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Cdnplayer : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/CdnwishCom : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public abstract class com/lagradost/cloudstream3/extractors/CineMMRedirect : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/CloudMailRu : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/ContentX : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/CsstOnline : com/lagradost/cloudstream3/extractors/SecvideoOnline { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/D0000d : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/D000dCom : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DBfilm : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Dailymotion : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$MetaData { + public fun (Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;)V + public final fun component1 ()Ljava/util/Map; + public final fun component2 ()Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; + public final fun copy (Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$MetaData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Dailymotion$MetaData;Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$MetaData; + public fun equals (Ljava/lang/Object;)Z + public final fun getQualities ()Ljava/util/Map; + public final fun getSubtitles ()Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$Quality { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality; + public fun equals (Ljava/lang/Object;)Z + public final fun getType ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData { + public fun (Ljava/lang/String;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData; + public fun equals (Ljava/lang/Object;)Z + public final fun getLabel ()Ljava/lang/String; + public final fun getUrls ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper { + public fun (ZLjava/util/Map;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/util/Map; + public final fun copy (ZLjava/util/Map;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;ZLjava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/util/Map; + public final fun getEnable ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DatabaseGdrive : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DatabaseGdrive2 : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DecryptKeys { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/DecryptKeys; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/DecryptKeys;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/DecryptKeys; + public fun equals (Ljava/lang/Object;)Z + public final fun getEdge1 ()Ljava/lang/String; + public final fun getEdge2 ()Ljava/lang/String; + public final fun getLegacyFallback ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DesuArcg : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DesuDrive : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DesuOdchan : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DesuOdvip : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DetailsRoot { + public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Z + public final fun component8 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)Lcom/lagradost/cloudstream3/extractors/DetailsRoot; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/DetailsRoot;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/DetailsRoot; + public fun equals (Ljava/lang/Object;)Z + public final fun getCode ()Ljava/lang/String; + public final fun getCreatedAt ()Ljava/lang/String; + public final fun getDescription ()Ljava/lang/String; + public final fun getEmbedFrameUrl ()Ljava/lang/String; + public final fun getId ()J + public final fun getOwnerPrivate ()Z + public final fun getPosterUrl ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dhcplay : com/lagradost/cloudstream3/extractors/CineMMRedirect { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dhtpre : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Dokicloud : com/lagradost/cloudstream3/extractors/Rabbitstream { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/DoodCxExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/DoodLaExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodLiExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodPmExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodShExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodSoExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodToExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodWatchExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodWfExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodWsExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodYtExtractor : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Doodporn : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Doodspro : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DoodstreamCom : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Dooood : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Ds2play : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Ds2video : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/DsstOnline : com/lagradost/cloudstream3/extractors/SecvideoOnline { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dsvplay : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Dumbalag : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Dwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Embedgram : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/EmturbovidExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Evoload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Evoload1 : com/lagradost/cloudstream3/extractors/Evoload { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Ewish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/FEmbed : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/FEnet : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Fastream : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/FeHD : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getDomainUrl ()Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setDomainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Fembed9hd : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/FileMoon : com/lagradost/cloudstream3/extractors/FilemoonV2 { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/FileMoonIn : com/lagradost/cloudstream3/extractors/FilemoonV2 { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/FileMoonSx : com/lagradost/cloudstream3/extractors/FilemoonV2 { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Filegram : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/FilemoonV2 : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Filesim : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Firestream : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/FlaswishCom : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Flyfile : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getApiUrl ()Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/FourCX : com/lagradost/cloudstream3/extractors/ContentX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/FourPichive : com/lagradost/cloudstream3/extractors/ContentX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/FourPlayRu : com/lagradost/cloudstream3/extractors/ContentX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Fplayer : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/FsstOnline : com/lagradost/cloudstream3/extractors/SecvideoOnline { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/GDMirrorbot : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/GUpload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/GamoVideo : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Gdriveplayer : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getKind ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerapi : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerapp : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerbiz : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerco : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerfun : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerio : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerme : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerorg : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayerus : com/lagradost/cloudstream3/extractors/Gdriveplayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/GenericM3U8 : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Geodailymotion : com/lagradost/cloudstream3/extractors/Dailymotion { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Gofile : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$AccountData { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData; + public fun equals (Ljava/lang/Object;)Z + public final fun getToken ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$AccountResponse { + public fun ()V + public fun (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;)V + public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;)Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse;Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$GofileData { + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;Ljava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData; + public fun equals (Ljava/lang/Object;)Z + public final fun getChildren ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$GofileFile { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Long; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile; + public fun equals (Ljava/lang/Object;)Z + public final fun getLink ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSize ()Ljava/lang/Long; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$GofileResponse { + public fun ()V + public fun (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;)V + public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse;Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/GoodstreamExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Guccihide : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Guxhag : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/HDMomPlayer : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/HDMomPlayer$Track { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getFile ()Ljava/lang/String; + public final fun getKind ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getLanguage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/HDPlayerSystem : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getHls ()Ljava/lang/String; + public final fun getSecuredLink ()Ljava/lang/String; + public final fun getVideoImage ()Ljava/lang/String; + public final fun getVideoSource ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/HDStreamAble : com/lagradost/cloudstream3/extractors/PeaceMakerst { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Habetar : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Haxloppd : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Hgcloudto : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/HglinkTo : com/lagradost/cloudstream3/extractors/CineMMRedirect { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/HgplayCDN : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/HlsWish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Hotlinger : com/lagradost/cloudstream3/extractors/ContentX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/HubCloud : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public final fun cleanTitle (Ljava/lang/String;)Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Hxfile : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRedirect ()Z + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/InternetArchive : com/lagradost/cloudstream3/utils/ExtractorApi { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/InternetArchive$Companion; + public fun ()V + public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/InternetArchive$Companion { +} + +public class com/lagradost/cloudstream3/extractors/JWPlayer : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Jeniusplay : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource { + public fun (ZLjava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (ZLjava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource;ZLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource; + public fun equals (Ljava/lang/Object;)Z + public final fun getHls ()Z + public final fun getSecuredLink ()Ljava/lang/String; + public final fun getVideoSource ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Jodwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Keephealth : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/KotakAnimeid : com/lagradost/cloudstream3/extractors/Hxfile { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z +} + +public final class com/lagradost/cloudstream3/extractors/Kotakajair : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Krakenfiles : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Kswplayer : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/LayarKaca : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Linkbox : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$Data { + public fun ()V + public fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/lang/String;)V + public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Data; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getItemId ()Ljava/lang/String; + public final fun getItemInfo ()Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$ItemInfo { + public fun ()V + public fun (Ljava/util/ArrayList;)V + public synthetic fun (Ljava/util/ArrayList;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/ArrayList; + public final fun copy (Ljava/util/ArrayList;)Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/util/ArrayList;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo; + public fun equals (Ljava/lang/Object;)Z + public final fun getResolutionList ()Ljava/util/ArrayList; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$Resolutions { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions; + public fun equals (Ljava/lang/Object;)Z + public final fun getResolution ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$Responses { + public fun ()V + public fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;)V + public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Linkbox$Data; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses;Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Lcom/lagradost/cloudstream3/extractors/Linkbox$Data; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/LuluStream : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Lulustream1 : com/lagradost/cloudstream3/extractors/LuluStream { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Lulustream2 : com/lagradost/cloudstream3/extractors/LuluStream { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Luluvdoo : com/lagradost/cloudstream3/extractors/LuluStream { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Luxubu : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Lvturbo : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/M3u8Manifest { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/M3u8Manifest; + public final fun extractLinks (Ljava/lang/String;)Ljava/util/ArrayList; +} + +public class com/lagradost/cloudstream3/extractors/MailRu : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuData { + public fun (Ljava/lang/String;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData; + public fun equals (Ljava/lang/Object;)Z + public final fun getProvider ()Ljava/lang/String; + public final fun getVideos ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData; + public fun equals (Ljava/lang/Object;)Z + public final fun getKey ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Maxstream : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Mdy : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Mediafire : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Megacloud : com/lagradost/cloudstream3/extractors/Rabbitstream { + public fun ()V + public fun extractRealKey (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getEmbed ()Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Meownime : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/MetaGnathTuggers : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/MixDrop : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MixDropAg : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MixDropBz : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MixDropCh : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MixDropPs : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MixDropSi : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MixDropTo : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Movhide : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Moviehab : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MoviehabNet : com/lagradost/cloudstream3/extractors/Moviehab { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Moviesm4u : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Mp4Upload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Multimovies : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Multimoviesshg : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Mvidoo : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Mwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/MxDropTo : com/lagradost/cloudstream3/extractors/MixDrop { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/MyVidPlay : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/NathanFromSubject : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Nekostream : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Nekowish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Neonime7n : com/lagradost/cloudstream3/extractors/Hxfile { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRedirect ()Z +} + +public final class com/lagradost/cloudstream3/extractors/Neonime8n : com/lagradost/cloudstream3/extractors/Hxfile { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRedirect ()Z +} + +public final class com/lagradost/cloudstream3/extractors/Obeywish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Odnoklassniki : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/OkRuHTTP : com/lagradost/cloudstream3/extractors/Odnoklassniki { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/OkRuHTTPMobile : com/lagradost/cloudstream3/extractors/OkRuHTTP { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/OkRuSSL : com/lagradost/cloudstream3/extractors/Odnoklassniki { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/OkRuSSLMobile : com/lagradost/cloudstream3/extractors/OkRuSSL { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/PeaceMakerst : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse { + public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getSIndex ()Ljava/lang/String; + public final fun getSourceList ()Ljava/util/Map; + public final fun getVideoImage ()Ljava/lang/String; + public final fun getVideoSources ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse { + public fun (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getMedia ()Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link; + public fun equals (Ljava/lang/Object;)Z + public final fun getSecurePath ()Ljava/lang/String; + public final fun getServiceUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media { + public fun (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; + public fun equals (Ljava/lang/Object;)Z + public final fun getLink ()Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Peytonepre : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Pichive : com/lagradost/cloudstream3/extractors/ContentX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/PixelDrain : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/PixelDrainDev : com/lagradost/cloudstream3/extractors/PixelDrain { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/PlayLtXyz : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/PlayRu : com/lagradost/cloudstream3/extractors/ContentX { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Playback { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/extractors/DecryptKeys;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/lagradost/cloudstream3/extractors/DecryptKeys; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/extractors/DecryptKeys;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Playback; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Playback;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/extractors/DecryptKeys;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Playback; + public fun equals (Ljava/lang/Object;)Z + public final fun getAlgorithm ()Ljava/lang/String; + public final fun getDecryptKeys ()Lcom/lagradost/cloudstream3/extractors/DecryptKeys; + public final fun getExpiresAt ()Ljava/lang/String; + public final fun getIv ()Ljava/lang/String; + public final fun getIv2 ()Ljava/lang/String; + public final fun getKeyParts ()Ljava/util/List; + public final fun getPayload ()Ljava/lang/String; + public final fun getPayload2 ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PlaybackDecrypt { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt; + public fun equals (Ljava/lang/Object;)Z + public final fun getSources ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PlaybackDecryptSource { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()J + public final fun component6 ()Ljava/lang/Object; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; + public fun equals (Ljava/lang/Object;)Z + public final fun getBitrateKbps ()J + public final fun getHeight ()Ljava/lang/Object; + public final fun getLabel ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun getQuality ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/PlaybackRoot { + public fun (Lcom/lagradost/cloudstream3/extractors/Playback;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Playback; + public final fun copy (Lcom/lagradost/cloudstream3/extractors/Playback;)Lcom/lagradost/cloudstream3/extractors/PlaybackRoot; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PlaybackRoot;Lcom/lagradost/cloudstream3/extractors/Playback;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackRoot; + public fun equals (Ljava/lang/Object;)Z + public final fun getPlayback ()Lcom/lagradost/cloudstream3/extractors/Playback; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/PlayerVoxzer : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Playerwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Playmogo : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Rabbitstream : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun extractRealKey (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getEmbed ()Ljava/lang/String; + public fun getKey ()Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Sources { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Boolean; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted; + public fun equals (Ljava/lang/Object;)Z + public final fun getEncrypted ()Ljava/lang/Boolean; + public final fun getSources ()Ljava/lang/String; + public final fun getTracks ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses { + public fun ()V + public fun (Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses; + public fun equals (Ljava/lang/Object;)Z + public final fun getSources ()Ljava/util/List; + public final fun getTracks ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Tracks { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getKind ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/RapidVid : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Rasacintaku : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Ryderjet : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/SBPlay : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/SBfull : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbasian : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbface : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbflix : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sblona : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sblongvu : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbnet : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbrapid : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbsonic : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbspeed : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Sbthe : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/SecvideoOnline : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Sendvid : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Server1uns : com/lagradost/cloudstream3/extractors/VidStack { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setRequiresReferer (Z)V +} + +public final class com/lagradost/cloudstream3/extractors/SfastwishCom : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/ShaveTape : com/lagradost/cloudstream3/extractors/StreamTape { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/SibNet : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Simpulumlamerop : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Slmaxed : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public final fun getEmbedRegex ()Lkotlin/text/Regex; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public final fun getResult ()Ljava/util/Map; + public final fun getStatus ()Ljava/lang/String; + public final fun getToken ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Slmaxed$Result { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Smoothpre : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Sobreatsesuyp : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Ssbstream : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/StreamEmbed : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamHLS : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/StreamM4u : com/lagradost/cloudstream3/extractors/XStreamCdn { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/StreamSB : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB$Main { + public fun (Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData;I)V + public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData; + public final fun component2 ()I + public final fun copy (Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData;I)Lcom/lagradost/cloudstream3/extractors/StreamSB$Main; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/StreamSB$Main;Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData;IILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/StreamSB$Main; + public fun equals (Ljava/lang/Object;)Z + public final fun getStatusCode ()I + public final fun getStreamData ()Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB$StreamData { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/ArrayList; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData; + public fun equals (Ljava/lang/Object;)Z + public final fun getBackup ()Ljava/lang/String; + public final fun getCdnImg ()Ljava/lang/String; + public final fun getFile ()Ljava/lang/String; + public final fun getHash ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getLength ()Ljava/lang/String; + public final fun getSubs ()Ljava/util/ArrayList; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB$Subs { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB1 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB10 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB11 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB2 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB3 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB4 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB5 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB6 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB7 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB8 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB9 : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/StreamSilk : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/StreamTape : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamTapeNet : com/lagradost/cloudstream3/extractors/StreamTape { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamTapeXyz : com/lagradost/cloudstream3/extractors/StreamTape { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/StreamWishExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Streamcash : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getCdnUrl ()Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/StreamhideCom : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/StreamhideTo : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Streamhub : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Streamhub2 : com/lagradost/cloudstream3/extractors/ZplayerV2 { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Streamlare : com/lagradost/cloudstream3/extractors/Slmaxed { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/StreamoUpload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Streamplay : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Streamplay$Source { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Streamplay$Source; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Streamplay$Source;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Streamplay$Source; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Streamsss : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Streamwish2 : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Strwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Strwish2 : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Supervideo : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Swdyu : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Swhoi : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/TRsTX : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Tantifilm : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData { + public fun (ZLjava/util/List;Ljava/util/List;Z)V + public final fun component1 ()Z + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Z + public final fun copy (ZLjava/util/List;Ljava/util/List;Z)Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData;ZLjava/util/List;Ljava/util/List;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData; + public fun equals (Ljava/lang/Object;)Z + public final fun getCaptions ()Ljava/util/List; + public final fun getData ()Ljava/util/List; + public final fun getSuccess ()Z + public fun hashCode ()I + public final fun is_vr ()Z + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/TauVideo : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoData { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData; + public fun equals (Ljava/lang/Object;)Z + public final fun getLabel ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls; + public fun equals (Ljava/lang/Object;)Z + public final fun getUrls ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Techinmind : com/lagradost/cloudstream3/extractors/GDMirrorbot { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V + public fun setRequiresReferer (Z)V +} + +public final class com/lagradost/cloudstream3/extractors/Tubeless : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Uasopt : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Up4FunTop : com/lagradost/cloudstream3/extractors/Up4Stream { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Up4Stream : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Upstream : com/lagradost/cloudstream3/extractors/ZplayerV2 { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/UpstreamExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Uqload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Uqload1 : com/lagradost/cloudstream3/extractors/Uqload { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Uqload2 : com/lagradost/cloudstream3/extractors/Uqload { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Uqloadbz : com/lagradost/cloudstream3/extractors/Uqload { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Uqloadcx : com/lagradost/cloudstream3/extractors/Uqload { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/UqloadsXyz : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Urochsunloath : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Userload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Userscloud : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Uservideo : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Uservideo$Sources { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources; + public fun equals (Ljava/lang/Object;)Z + public final fun getLabel ()Ljava/lang/String; + public final fun getSrc ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Vicloud : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/VidHideHub : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/VidHidePro : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/VidHidePro1 : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/VidHidePro2 : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/VidHidePro3 : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/VidHidePro4 : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/VidHidePro5 : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/VidHidePro6 : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/VidMoxy : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/VidNest : com/lagradost/cloudstream3/extractors/JWPlayer { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/VidStack : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/VidaaraxCom : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/VidaaraxNet : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Vidara : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/VidaraSo : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidaraa : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidaratem : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidaraw : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidarax : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidavaca : com/lagradost/cloudstream3/extractors/Vidara { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vide0Net : com/lagradost/cloudstream3/extractors/DoodLaExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Videa : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/VideoSeyred : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSSource { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getFile ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getFile ()Ljava/lang/String; + public final fun getKind ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getLanguage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource; + public fun equals (Ljava/lang/Object;)Z + public final fun getImage ()Ljava/lang/String; + public final fun getSources ()Ljava/util/List; + public final fun getTitle ()Ljava/lang/String; + public final fun getTracks ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Videzz : com/lagradost/cloudstream3/extractors/Vidoza { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidgomunime : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Vidgomunimesb : com/lagradost/cloudstream3/extractors/StreamSB { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/VidhideExtractor : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Vidmoly : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Vidmolybiz : com/lagradost/cloudstream3/extractors/Vidmoly { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidmolyme : com/lagradost/cloudstream3/extractors/Vidmoly { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vidmolyto : com/lagradost/cloudstream3/extractors/Vidmoly { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Vido : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Vidoza : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Vids : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Vidsonic : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/VinovoSi : com/lagradost/cloudstream3/extractors/VinovoTo { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/VinovoTo : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/VkExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/Voe : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/Voe1 : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Voe2 : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/Vtbe : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/WatchSB : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Watchadsontape : com/lagradost/cloudstream3/extractors/StreamTape { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/Wibufile : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/WishembedPro : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Wishfast : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Wishonly : com/lagradost/cloudstream3/extractors/StreamWishExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/extractors/XStreamCdn : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getDomainUrl ()Ljava/lang/String; + public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String; + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setDomainUrl (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Xenolyzb : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Yipsu : com/lagradost/cloudstream3/extractors/Voe { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/YourUpload : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/extractors/YoutubeExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/YoutubeMobileExtractor : com/lagradost/cloudstream3/extractors/YoutubeExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/YoutubeNoCookieExtractor : com/lagradost/cloudstream3/extractors/YoutubeExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/YoutubeShortLinkExtractor : com/lagradost/cloudstream3/extractors/YoutubeExtractor { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Yufiles : com/lagradost/cloudstream3/extractors/Hxfile { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Yuguaab : com/lagradost/cloudstream3/extractors/VidHidePro { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/Zplayer : com/lagradost/cloudstream3/extractors/ZplayerV2 { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public class com/lagradost/cloudstream3/extractors/ZplayerV2 : com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getRequiresReferer ()Z + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setMainUrl (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/extractors/Ztreamhub : com/lagradost/cloudstream3/extractors/Filesim { + public fun ()V + public fun getMainUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/helper/AesHelper { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/AesHelper; + public final fun cryptoAESHandler (Ljava/lang/String;[BZLjava/lang/String;)Ljava/lang/String; + public final fun cryptoAESHandler (Ljava/lang/String;[BZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun cryptoAESHandler$default (Lcom/lagradost/cloudstream3/extractors/helper/AesHelper;Ljava/lang/String;[BZLjava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public static synthetic fun cryptoAESHandler$default (Lcom/lagradost/cloudstream3/extractors/helper/AesHelper;Ljava/lang/String;[BZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun generateKeyAndIv ([B[BIIII)Lkotlin/Pair; + public static synthetic fun generateKeyAndIv$default (Lcom/lagradost/cloudstream3/extractors/helper/AesHelper;[B[BIIIIILjava/lang/Object;)Lkotlin/Pair; + public final fun hexToByteArray (Ljava/lang/String;)[B +} + +public final class com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper$Companion; + public fun ()V +} + +public final class com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper$Companion { + public final fun getUrls (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/helper/CryptoJS { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/CryptoJS; + public final fun decrypt (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public final fun encrypt (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper; + public final fun extractVidstream (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZLorg/jsoup/nodes/Document;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun extractVidstream$default (Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZLorg/jsoup/nodes/Document;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoJsonData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getFile ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources$Companion; + public fun (Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources; + public fun equals (Ljava/lang/Object;)Z + public final fun getSource ()Ljava/util/List; + public final fun getSourceBk ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/GogoHelper$GogoSources$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/JwPlayerHelper { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper; + public final fun canParseJwScript (Ljava/lang/String;)Z + public final fun extractStreamLinks (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun extractStreamLinks$default (Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/util/Map;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track; + public fun equals (Ljava/lang/Object;)Z + public final fun getFile ()Ljava/lang/String; + public final fun getKind ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/JwPlayerHelper$Track$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/NineAnimeHelper { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/NineAnimeHelper; + public final fun cipher (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public final fun decodeVrf (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public final fun encode (Ljava/lang/String;)Ljava/lang/String; + public final fun encodeVrf (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public final fun encrypt (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/VstreamhubHelper$Companion; + public fun ()V +} + +public final class com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper$Companion { + public final fun getUrls (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/helper/WcoHelper { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion; + public fun ()V +} + +public final class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion { + public final fun getNewWcoKey (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getWcoKey (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys; + public fun equals (Ljava/lang/Object;)Z + public final fun getWcoKey ()Ljava/lang/String; + public final fun getWcocipher ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$ExternalKeys$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys; + public fun equals (Ljava/lang/Object;)Z + public final fun getCipherkey ()Ljava/lang/String; + public final fun getEncryptKey ()Ljava/lang/String; + public final fun getMainKey ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/helper/WcoHelper$Companion$NewExternalKeys$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider : com/lagradost/cloudstream3/metaproviders/TmdbProvider { + public fun ()V + public fun getApiName ()Ljava/lang/String; + public fun getLang ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getSupportedTypes ()Ljava/util/Set; + public fun getUseMetaLoadResponse ()Z + public fun getUsesWebView ()Z + public fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun loadLinks (Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setLang (Ljava/lang/String;)V + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData$Companion; + public fun (ZLjava/util/List;)V + public synthetic fun (ZLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/util/List; + public final fun copy (ZLjava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData;ZLjava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData; + public fun equals (Ljava/lang/Object;)Z + public final fun getMovies ()Ljava/util/List; + public fun hashCode ()I + public final fun isSuccess ()Z + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider$CrossMetaData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI : com/lagradost/cloudstream3/MainAPI { + public static final field API_HOST Ljava/lang/String; + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Companion; + public static final field SITE_HOST Ljava/lang/String; + public static final field TAG Ljava/lang/String; + public fun ()V + public fun getHasMainPage ()Z + public fun getMainPage ()Ljava/util/List; + public fun getMainPage (ILcom/lagradost/cloudstream3/MainPageRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getName ()Ljava/lang/String; + public fun getProviderType ()Lcom/lagradost/cloudstream3/ProviderType; + public fun getSupportedTypes ()Ljava/util/Set; + public fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun search (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast$Companion; + public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast; + public fun equals (Ljava/lang/Object;)Z + public final fun getCharacterName ()Ljava/lang/String; + public final fun getId ()J + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun getName ()Ljava/lang/String; + public final fun getRole ()Ljava/lang/String; + public final fun getSlug ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Cast$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Companion { + public final fun getAPI_KEY ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits$Companion; + public fun (Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits; + public fun equals (Ljava/lang/Object;)Z + public final fun getCast ()Ljava/util/List; + public final fun getCrew ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Credits$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew$Companion; + public fun (JLjava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun component5 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew;JLjava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()J + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun getJob ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSlug ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Crew$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data$Companion; + public fun ()V + public fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary;)V + public synthetic fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component2 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary; + public final fun copy (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data;Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getMedia ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary; + public final fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre$Companion; + public fun (JLjava/lang/String;Ljava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre;JLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()J + public final fun getName ()Ljava/lang/String; + public final fun getSlug ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Genre$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public fun equals (Ljava/lang/Object;)Z + public final fun getMedium ()Ljava/lang/String; + public final fun getPoster ()Ljava/lang/String; + public final fun getThumb ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData$Companion; + public fun ()V + public fun (Ljava/lang/Long;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Long;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Long; + public final fun component10 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/Integer; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Long;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData; + public fun equals (Ljava/lang/Object;)Z + public final fun getAiredDate ()Ljava/lang/String; + public final fun getDate ()Ljava/lang/String; + public final fun getEpisode ()Ljava/lang/Integer; + public final fun getId ()Ljava/lang/Long; + public final fun getLastSeason ()Ljava/lang/Integer; + public final fun getOrgTitle ()Ljava/lang/String; + public final fun getSeason ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$LinkData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media$Companion; + public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;IJDLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer;JJJJJJJLjava/lang/String;Ljava/lang/String;ZLjava/util/List;J)V + public synthetic fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;IJDLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer;JJJJJJJLjava/lang/String;Ljava/lang/String;ZLjava/util/List;JIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()J + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/lang/String; + public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun component15 ()Ljava/util/List; + public final fun component16 ()Ljava/lang/Long; + public final fun component17 ()Ljava/lang/String; + public final fun component18 ()Ljava/lang/String; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/util/List; + public final fun component21 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer; + public final fun component22 ()J + public final fun component23 ()J + public final fun component24 ()J + public final fun component25 ()J + public final fun component26 ()J + public final fun component27 ()J + public final fun component28 ()J + public final fun component29 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component30 ()Ljava/lang/String; + public final fun component31 ()Z + public final fun component32 ()Ljava/util/List; + public final fun component33 ()J + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()I + public final fun component6 ()J + public final fun component7 ()D + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;IJDLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer;JJJJJJJLjava/lang/String;Ljava/lang/String;ZLjava/util/List;J)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;IJDLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer;JJJJJJJLjava/lang/String;Ljava/lang/String;ZLjava/util/List;JIILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media; + public fun equals (Ljava/lang/Object;)Z + public final fun fetchCredits (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fetchRecommendations (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fetchTrailer (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fixGenres ()Ljava/util/List; + public final fun getAiredStart ()Ljava/lang/String; + public final fun getAltTitles ()Ljava/util/List; + public final fun getCertification ()Ljava/lang/String; + public final fun getCommentsCount ()J + public final fun getCountry ()Ljava/lang/String; + public final fun getEnableAds ()Z + public final fun getEpisodes ()J + public final fun getGenres ()Ljava/util/List; + public final fun getId ()J + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun getLanguage ()Ljava/lang/String; + public final fun getMediaRating ()D + public final fun getMediaType ()Ljava/lang/String; + public final fun getMediaYear ()I + public final fun getOriginalTitle ()Ljava/lang/String; + public final fun getPermalink ()Ljava/lang/String; + public final fun getPopularity ()J + public final fun getRanked ()J + public final fun getRecsCount ()J + public final fun getReleaseDatesFmt ()Ljava/lang/String; + public final fun getReleased ()Ljava/lang/String; + public final fun getReviewsCount ()J + public final fun getRuntime ()J + public final fun getSlug ()Ljava/lang/String; + public final fun getSources ()Ljava/util/List; + public final fun getStatus ()Ljava/lang/String; + public final fun getSynopsis ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getTrailer ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer; + public final fun getType ()Ljava/lang/String; + public final fun getUpdatedAt ()J + public final fun getVotes ()Ljava/lang/Long; + public final fun getWatchers ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Media$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary$Companion; + public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;)V + public synthetic fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()J + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/Double; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary;JLjava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary; + public fun equals (Ljava/lang/Object;)Z + public final fun getCountry ()Ljava/lang/String; + public final fun getId ()J + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Images; + public final fun getLanguage ()Ljava/lang/String; + public final fun getMediaType ()Ljava/lang/String; + public final fun getOriginalTitle ()Ljava/lang/String; + public final fun getPermalink ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/Double; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$MediaSummary$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode$Companion; + public fun (IIDILjava/lang/String;)V + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()D + public final fun component4 ()I + public final fun component5 ()Ljava/lang/String; + public final fun copy (IIDILjava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode;IIDILjava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisodeNumber ()I + public final fun getId ()I + public final fun getRating ()D + public final fun getReleasedAt ()Ljava/lang/String; + public final fun getVotes ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;I)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()I + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;I)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;IILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisodes ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getReleaseDate ()Ljava/lang/String; + public final fun getTimezone ()Ljava/lang/String; + public final fun getTotal ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$ShowEpisodesItem$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source; + public fun equals (Ljava/lang/Object;)Z + public final fun getImage ()Ljava/lang/String; + public final fun getLink ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSource ()Ljava/lang/String; + public final fun getSourceType ()Ljava/lang/String; + public final fun getXid ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Source$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag$Companion; + public fun (JLjava/lang/String;Ljava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag;JLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()J + public final fun getName ()Ljava/lang/String; + public final fun getSlug ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Tag$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer$Companion; + public fun ()V + public fun (Ljava/lang/Long;)V + public synthetic fun (Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Long; + public final fun copy (Ljava/lang/Long;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer;Ljava/lang/Long;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/Long; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$Trailer$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails$Companion; + public fun (JLjava/lang/String;)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun copy (JLjava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails;JLjava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()J + public final fun getSource ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode$Companion; + public fun (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails; + public final fun copy (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode; + public fun equals (Ljava/lang/Object;)Z + public final fun getTrailerDetails ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerDetails; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot$Companion; + public fun (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode; + public final fun copy (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot; + public fun equals (Ljava/lang/Object;)Z + public final fun getTrailer ()Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerNode; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/MyDramaListAPI$TrailerRoot$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/SyncRedirector { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/SyncRedirector; + public final fun redirect (Ljava/lang/String;Lcom/lagradost/cloudstream3/MainAPI;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbLink { + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbLink; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbLink;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbLink; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisode ()Ljava/lang/Integer; + public final fun getImdbID ()Ljava/lang/String; + public final fun getMovieName ()Ljava/lang/String; + public final fun getSeason ()Ljava/lang/Integer; + public final fun getTmdbID ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagradost/cloudstream3/MainAPI { + public fun ()V + public fun fetchContentRating (Ljava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getApiName ()Ljava/lang/String; + public fun getDisableSeasonZero ()Z + public fun getHasMainPage ()Z + public fun getIncludeAdult ()Z + public fun getMainPage (ILcom/lagradost/cloudstream3/MainPageRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getProviderType ()Lcom/lagradost/cloudstream3/ProviderType; + public fun getUseMetaLoadResponse ()Z + public fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun loadFromImdb (Ljava/lang/String;)Lcom/lagradost/cloudstream3/LoadResponse; + public fun loadFromImdb (Ljava/lang/String;Ljava/util/List;)Lcom/lagradost/cloudstream3/LoadResponse; + public fun loadFromTmdb (I)Lcom/lagradost/cloudstream3/LoadResponse; + public fun loadFromTmdb (ILjava/util/List;)Lcom/lagradost/cloudstream3/LoadResponse; + public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagradost/cloudstream3/MainAPI { + public fun ()V + public fun getHasMainPage ()Z + public fun getMainPage ()Ljava/util/List; + public fun getMainPage (ILcom/lagradost/cloudstream3/MainPageRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getName ()Ljava/lang/String; + public fun getProviderType ()Lcom/lagradost/cloudstream3/ProviderType; + public fun getSupportedTypes ()Ljava/util/Set; + public fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setName (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; + public fun equals (Ljava/lang/Object;)Z + public final fun getDay ()Ljava/lang/String; + public final fun getTime ()Ljava/lang/String; + public final fun getTimezone ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast { + public fun ()V + public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/lang/Long; + public final fun component4 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person; + public final fun component5 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast;Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast; + public fun equals (Ljava/lang/Object;)Z + public final fun getCharacter ()Ljava/lang/String; + public final fun getCharacters ()Ljava/util/List; + public final fun getEpisodeCount ()Ljava/lang/Long; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getPerson ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data { + public fun ()V + public fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V + public synthetic fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/lagradost/cloudstream3/TvType; + public final fun component2 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun copy (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data;Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getMediaDetails ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun getType ()Lcom/lagradost/cloudstream3/TvType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids { + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/Integer; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public fun equals (Ljava/lang/Object;)Z + public final fun getImdb ()Ljava/lang/String; + public final fun getSlug ()Ljava/lang/String; + public final fun getTmdb ()Ljava/lang/Integer; + public final fun getTrakt ()Ljava/lang/Integer; + public final fun getTvdb ()Ljava/lang/Integer; + public final fun getTvrage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images { + public fun ()V + public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public fun equals (Ljava/lang/Object;)Z + public final fun getBanner ()Ljava/util/List; + public final fun getClearArt ()Ljava/util/List; + public final fun getFanart ()Ljava/util/List; + public final fun getHeadshot ()Ljava/util/List; + public final fun getLogo ()Ljava/util/List; + public final fun getPoster ()Ljava/util/List; + public final fun getScreenshot ()Ljava/util/List; + public final fun getThumb ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData { + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/lang/Integer; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/lang/String; + public final fun component14 ()Ljava/lang/Integer; + public final fun component15 ()Ljava/lang/String; + public final fun component16 ()Z + public final fun component17 ()Ljava/lang/Integer; + public final fun component18 ()Ljava/lang/Integer; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/String; + public final fun component22 ()Ljava/lang/String; + public final fun component23 ()Z + public final fun component24 ()Z + public final fun component25 ()Z + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData; + public fun equals (Ljava/lang/Object;)Z + public final fun getAiredDate ()Ljava/lang/String; + public final fun getAiredYear ()Ljava/lang/Integer; + public final fun getAniId ()Ljava/lang/String; + public final fun getAnimeId ()Ljava/lang/String; + public final fun getDate ()Ljava/lang/String; + public final fun getEpisode ()Ljava/lang/Integer; + public final fun getEpsTitle ()Ljava/lang/String; + public final fun getId ()Ljava/lang/Integer; + public final fun getImdbId ()Ljava/lang/String; + public final fun getJpTitle ()Ljava/lang/String; + public final fun getLastSeason ()Ljava/lang/Integer; + public final fun getOrgTitle ()Ljava/lang/String; + public final fun getSeason ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getTmdbId ()Ljava/lang/Integer; + public final fun getTraktId ()Ljava/lang/Integer; + public final fun getTraktSlug ()Ljava/lang/String; + public final fun getTvdbId ()Ljava/lang/Integer; + public final fun getTvrageId ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public final fun isAnime ()Z + public final fun isAsian ()Z + public final fun isBollywood ()Z + public final fun isCartoon ()Z + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/lang/Double; + public final fun component14 ()Ljava/lang/Long; + public final fun component15 ()Ljava/lang/Long; + public final fun component16 ()Ljava/lang/String; + public final fun component17 ()Ljava/util/List; + public final fun component18 ()Ljava/util/List; + public final fun component19 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Integer; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/Integer; + public final fun component22 ()Ljava/lang/String; + public final fun component23 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; + public final fun component24 ()Ljava/lang/String; + public final fun component25 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun component26 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public fun equals (Ljava/lang/Object;)Z + public final fun getAiredEpisodes ()Ljava/lang/Integer; + public final fun getAirs ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; + public final fun getAvailableTranslations ()Ljava/util/List; + public final fun getCertification ()Ljava/lang/String; + public final fun getCommentCount ()Ljava/lang/Long; + public final fun getCountry ()Ljava/lang/String; + public final fun getFirstAired ()Ljava/lang/String; + public final fun getGenres ()Ljava/util/List; + public final fun getHomepage ()Ljava/lang/String; + public final fun getIds ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getLanguage ()Ljava/lang/String; + public final fun getLanguages ()Ljava/util/List; + public final fun getMedia ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun getNetwork ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/Double; + public final fun getReleased ()Ljava/lang/String; + public final fun getRuntime ()Ljava/lang/Integer; + public final fun getStatus ()Ljava/lang/String; + public final fun getTagline ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getTrailer ()Ljava/lang/String; + public final fun getUpdatedAt ()Ljava/lang/String; + public final fun getVotes ()Ljava/lang/Long; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People { + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People; + public fun equals (Ljava/lang/Object;)Z + public final fun getCast ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person { + public fun ()V + public fun (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V + public synthetic fun (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun copy (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person; + public fun equals (Ljava/lang/Object;)Z + public final fun getIds ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons { + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/lang/Double; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun component6 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/Integer; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons; + public fun equals (Ljava/lang/Object;)Z + public final fun getAiredEpisodes ()Ljava/lang/Integer; + public final fun getEpisodeCount ()Ljava/lang/Integer; + public final fun getEpisodes ()Ljava/util/List; + public final fun getFirstAired ()Ljava/lang/String; + public final fun getIds ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getNetwork ()Ljava/lang/String; + public final fun getNumber ()Ljava/lang/Integer; + public final fun getOverview ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/Double; + public final fun getTitle ()Ljava/lang/String; + public final fun getUpdatedAt ()Ljava/lang/String; + public final fun getVotes ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode { + public fun ()V + public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component10 ()Ljava/lang/Double; + public final fun component11 ()Ljava/lang/Integer; + public final fun component12 ()Ljava/lang/Integer; + public final fun component13 ()Ljava/lang/String; + public final fun component14 ()Ljava/lang/String; + public final fun component15 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun component6 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Ljava/lang/Integer; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode; + public fun equals (Ljava/lang/Object;)Z + public final fun getAvailableTranslations ()Ljava/util/List; + public final fun getCommentCount ()Ljava/lang/Integer; + public final fun getEpisodeType ()Ljava/lang/String; + public final fun getFirstAired ()Ljava/lang/String; + public final fun getIds ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getNumber ()Ljava/lang/Integer; + public final fun getNumberAbs ()Ljava/lang/Integer; + public final fun getOverview ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/Double; + public final fun getRuntime ()Ljava/lang/Integer; + public final fun getSeason ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getUpdatedAt ()Ljava/lang/String; + public final fun getVotes ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt { + public static final field DEBUG_EXCEPTION Ljava/lang/String; + public static final field DEBUG_PRINT Ljava/lang/String; + public static final fun debugAssert (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V + public static final fun debugException (Lkotlin/jvm/functions/Function0;)V + public static final fun debugPrint (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V + public static synthetic fun debugPrint$default (Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V + public static final fun debugWarning (Lkotlin/jvm/functions/Function0;)V + public static final fun debugWarning (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V + public static final fun getAllMessages (Ljava/lang/Throwable;)Ljava/lang/String; + public static final fun getStackTracePretty (Ljava/lang/Throwable;Z)Ljava/lang/String; + public static synthetic fun getStackTracePretty$default (Ljava/lang/Throwable;ZILjava/lang/Object;)Ljava/lang/String; + public static final fun launchSafe (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public static synthetic fun launchSafe$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; + public static final fun logError (Ljava/lang/Throwable;)V + public static final fun normalSafeApiCall (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public static final fun safe (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public static final fun safeApiCall (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun safeAsync (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun safeFail (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; + public static final fun suspendSafeApiCall (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun throwAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; +} + +public final class com/lagradost/cloudstream3/mvvm/DebugException : java/lang/Exception { + public fun (Ljava/lang/String;)V +} + +public abstract class com/lagradost/cloudstream3/mvvm/Resource { + public static final field Companion Lcom/lagradost/cloudstream3/mvvm/Resource$Companion; +} + +public final class com/lagradost/cloudstream3/mvvm/Resource$Companion { + public final fun fromResult (Ljava/lang/Object;)Lcom/lagradost/cloudstream3/mvvm/Resource; +} + +public final class com/lagradost/cloudstream3/mvvm/Resource$Failure : com/lagradost/cloudstream3/mvvm/Resource { + public fun (ZLjava/lang/String;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun copy (ZLjava/lang/String;)Lcom/lagradost/cloudstream3/mvvm/Resource$Failure; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/mvvm/Resource$Failure;ZLjava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/mvvm/Resource$Failure; + public fun equals (Ljava/lang/Object;)Z + public final fun getErrorString ()Ljava/lang/String; + public fun hashCode ()I + public final fun isNetworkError ()Z + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/mvvm/Resource$Loading : com/lagradost/cloudstream3/mvvm/Resource { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/lagradost/cloudstream3/mvvm/Resource$Loading; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/mvvm/Resource$Loading;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/mvvm/Resource$Loading; + public fun equals (Ljava/lang/Object;)Z + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/mvvm/Resource$Success : com/lagradost/cloudstream3/mvvm/Resource { + public fun (Ljava/lang/Object;)V + public final fun component1 ()Ljava/lang/Object; + public final fun copy (Ljava/lang/Object;)Lcom/lagradost/cloudstream3/mvvm/Resource$Success; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/mvvm/Resource$Success;Ljava/lang/Object;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/mvvm/Resource$Success; + public fun equals (Ljava/lang/Object;)Z + public final fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/network/WebViewResolver : okhttp3/Interceptor { + public static final field Companion Lcom/lagradost/cloudstream3/network/WebViewResolver$Companion; + public fun (Lkotlin/text/Regex;Ljava/util/List;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function1;J)V + public synthetic fun (Lkotlin/text/Regex;Ljava/util/List;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function1;JILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; + public final fun resolveUsingWebView (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun resolveUsingWebView (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun resolveUsingWebView (Lokhttp3/Request;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun resolveUsingWebView$default (Lcom/lagradost/cloudstream3/network/WebViewResolver;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun resolveUsingWebView$default (Lcom/lagradost/cloudstream3/network/WebViewResolver;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun resolveUsingWebView$default (Lcom/lagradost/cloudstream3/network/WebViewResolver;Lokhttp3/Request;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/network/WebViewResolver$Companion { + public final fun getDEFAULT_TIMEOUT ()J + public final fun getWebViewUserAgent ()Ljava/lang/String; + public final fun setWebViewUserAgent (Ljava/lang/String;)V +} + +public abstract class com/lagradost/cloudstream3/plugins/BasePlugin { + public fun ()V + public fun beforeUnload ()V + public final fun getFilename ()Ljava/lang/String; + public final fun get__filename ()Ljava/lang/String; + public fun load ()V + public final fun registerExtractorAPI (Lcom/lagradost/cloudstream3/utils/ExtractorApi;)V + public final fun registerMainAPI (Lcom/lagradost/cloudstream3/MainAPI;)V + public final fun setFilename (Ljava/lang/String;)V + public final fun set__filename (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/plugins/BasePlugin$Manifest { + public static final field Companion Lcom/lagradost/cloudstream3/plugins/BasePlugin$Manifest$Companion; + public fun ()V + public final fun getName ()Ljava/lang/String; + public final fun getPluginClassName ()Ljava/lang/String; + public final fun getRequiresResources ()Z + public final fun getVersion ()Ljava/lang/Integer; + public final fun setName (Ljava/lang/String;)V + public final fun setPluginClassName (Ljava/lang/String;)V + public final fun setRequiresResources (Z)V + public final fun setVersion (Ljava/lang/Integer;)V +} + +public final synthetic class com/lagradost/cloudstream3/plugins/BasePlugin$Manifest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/plugins/BasePlugin$Manifest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/plugins/BasePlugin$Manifest; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/plugins/BasePlugin$Manifest;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/plugins/BasePlugin$Manifest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/plugins/BasePluginKt { + public static final field PLUGIN_TAG Ljava/lang/String; +} + +public abstract interface annotation class com/lagradost/cloudstream3/plugins/CloudstreamPlugin : java/lang/annotation/Annotation { +} + +public final class com/lagradost/cloudstream3/syncproviders/SyncIdName : java/lang/Enum { + public static final field Anilist Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static final field Imdb Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static final field Kitsu Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static final field LocalList Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static final field MyAnimeList Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static final field Simkl Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static final field Trakt Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; + public static fun values ()[Lcom/lagradost/cloudstream3/syncproviders/SyncIdName; +} + +public final class com/lagradost/cloudstream3/utils/AppUtils { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/AppUtils; + public final fun toJson (Ljava/lang/Object;)Ljava/lang/String; +} + +public class com/lagradost/cloudstream3/utils/AtomicList : java/util/List, kotlin/jvm/internal/markers/KMappedMarker { + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun add (ILjava/lang/Object;)V + public fun add (Ljava/lang/Object;)Z + public fun addAll (ILjava/util/Collection;)Z + public fun addAll (Ljava/util/Collection;)Z + public fun clear ()V + public fun contains (Ljava/lang/Object;)Z + public fun containsAll (Ljava/util/Collection;)Z + public final fun distinctBy (Lkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/utils/AtomicList; + public final fun filter (Lkotlin/jvm/functions/Function1;)Lcom/lagradost/cloudstream3/utils/AtomicList; + public fun get (I)Ljava/lang/Object; + public fun getSize ()I + public fun indexOf (Ljava/lang/Object;)I + public fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public fun lastIndexOf (Ljava/lang/Object;)I + public fun listIterator ()Ljava/util/ListIterator; + public fun listIterator (I)Ljava/util/ListIterator; + public final fun plus (Ljava/lang/Object;)Lcom/lagradost/cloudstream3/utils/AtomicList; + public final fun plus (Ljava/util/Collection;)Lcom/lagradost/cloudstream3/utils/AtomicList; + public fun remove (I)Ljava/lang/Object; + public fun remove (Ljava/lang/Object;)Z + public fun removeAll (Ljava/util/Collection;)Z + public fun replaceAll (Ljava/util/function/UnaryOperator;)V + public fun retainAll (Ljava/util/Collection;)Z + public fun set (ILjava/lang/Object;)Ljava/lang/Object; + public final fun size ()I + public fun sort (Ljava/util/Comparator;)V + public fun subList (II)Ljava/util/List; + public fun toArray ()[Ljava/lang/Object; + public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; + public final fun withLock (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/utils/AtomicMutableList : com/lagradost/cloudstream3/utils/AtomicList, java/util/List, kotlin/jvm/internal/markers/KMutableList { + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun add (ILjava/lang/Object;)V + public fun add (Ljava/lang/Object;)Z + public fun addAll (ILjava/util/Collection;)Z + public fun addAll (Ljava/util/Collection;)Z + public fun clear ()V + public fun iterator ()Ljava/util/Iterator; + public fun listIterator ()Ljava/util/ListIterator; + public fun listIterator (I)Ljava/util/ListIterator; + public final fun remove (I)Ljava/lang/Object; + public fun remove (Ljava/lang/Object;)Z + public fun removeAll (Ljava/util/Collection;)Z + public fun removeAt (I)Ljava/lang/Object; + public fun retainAll (Ljava/util/Collection;)Z + public fun set (ILjava/lang/Object;)Ljava/lang/Object; + public fun subList (II)Ljava/util/List; +} + +public final class com/lagradost/cloudstream3/utils/Coroutines { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/Coroutines; + public final fun atomicListOf ([Ljava/lang/Object;)Lcom/lagradost/cloudstream3/utils/AtomicMutableList; + public final fun ioSafe (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/Job; + public final fun ioWork (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun ioWorkSafe (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun main (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public final fun mainWork (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun runOnMainThread (Lkotlin/jvm/functions/Function0;)V + public final fun threadSafeListOf ([Ljava/lang/Object;)Ljava/util/List; +} + +public final class com/lagradost/cloudstream3/utils/Coroutines_jvmKt { + public static final fun runOnMainThreadNative (Lkotlin/jvm/functions/Function0;)V +} + +public class com/lagradost/cloudstream3/utils/DrmExtractorLink : com/lagradost/cloudstream3/utils/ExtractorLink { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/util/HashMap;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/util/HashMap;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/util/HashMap;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/util/HashMap;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getAudioTracks ()Ljava/util/List; + public fun getExtractorData ()Ljava/lang/String; + public fun getHeaders ()Ljava/util/Map; + public fun getKey ()Ljava/lang/String; + public fun getKeyRequestParameters ()Ljava/util/HashMap; + public fun getKid ()Ljava/lang/String; + public fun getKty ()Ljava/lang/String; + public fun getLicenseUrl ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getQuality ()I + public fun getReferer ()Ljava/lang/String; + public fun getSource ()Ljava/lang/String; + public fun getType ()Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public fun getUrl ()Ljava/lang/String; + public final synthetic fun getUuid ()Ljava/util/UUID; + public fun getUuid ()Lkotlin/uuid/Uuid; + public fun setAudioTracks (Ljava/util/List;)V + public fun setExtractorData (Ljava/lang/String;)V + public fun setHeaders (Ljava/util/Map;)V + public fun setKey (Ljava/lang/String;)V + public fun setKeyRequestParameters (Ljava/util/HashMap;)V + public fun setKid (Ljava/lang/String;)V + public fun setKty (Ljava/lang/String;)V + public fun setLicenseUrl (Ljava/lang/String;)V + public fun setQuality (I)V + public fun setReferer (Ljava/lang/String;)V + public fun setType (Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;)V + public final synthetic fun setUuid (Ljava/util/UUID;)V + public fun setUuid (Lkotlin/uuid/Uuid;)V +} + +public abstract class com/lagradost/cloudstream3/utils/ExtractorApi { + public fun ()V + public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String; + public abstract fun getMainUrl ()Ljava/lang/String; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getRequiresReferer ()Z + public final fun getSafeUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getSafeUrl$default (Lcom/lagradost/cloudstream3/utils/ExtractorApi;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getSourcePlugin ()Ljava/lang/String; + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getUrl$default (Lcom/lagradost/cloudstream3/utils/ExtractorApi;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun getUrl$default (Lcom/lagradost/cloudstream3/utils/ExtractorApi;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setSourcePlugin (Ljava/lang/String;)V +} + +public final class com/lagradost/cloudstream3/utils/ExtractorApiKt { + public static final fun fixUrl (Lcom/lagradost/cloudstream3/utils/ExtractorApi;Ljava/lang/String;)Ljava/lang/String; + public static final fun getAndUnpack (Ljava/lang/String;)Ljava/lang/String; + public static final fun getCLEARKEY_UUID ()Ljava/util/UUID; + public static final fun getExtractorApiFromName (Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/ExtractorApi; + public static final fun getExtractorApis ()Lcom/lagradost/cloudstream3/utils/AtomicMutableList; + public static final fun getINFER_TYPE ()Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static final fun getPLAYREADY_UUID ()Ljava/util/UUID; + public static final fun getPacked (Ljava/lang/String;)Ljava/lang/String; + public static final fun getPostForm (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getQualityFromName (Ljava/lang/String;)I + public static final fun getSchemaStripRegex ()Lkotlin/text/Regex; + public static final fun getWIDEVINE_UUID ()Ljava/util/UUID; + public static final fun httpsify (Ljava/lang/String;)Ljava/lang/String; + public static final fun loadExtractor (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun loadExtractor (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun loadExtractor$default (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newDrmExtractorLink (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/UUID;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newDrmExtractorLink$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/UUID;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun newExtractorLink (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newExtractorLink$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun requireReferer (Ljava/lang/String;)Z + public static final fun toUs (J)J + public static final fun unshortenLinkSafe (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class com/lagradost/cloudstream3/utils/ExtractorLink : com/lagradost/cloudstream3/IDownloadableMinimum { + public static final field Companion Lcom/lagradost/cloudstream3/utils/ExtractorLink$Companion; + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZLjava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZLjava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZLjava/util/Map;Ljava/lang/String;Z)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZLjava/util/Map;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAllHeaders ()Ljava/util/Map; + public fun getAudioTracks ()Ljava/util/List; + public fun getExtractorData ()Ljava/lang/String; + public fun getHeaders ()Ljava/util/Map; + public fun getName ()Ljava/lang/String; + public fun getQuality ()I + public fun getReferer ()Ljava/lang/String; + public fun getSource ()Ljava/lang/String; + public fun getType ()Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public fun getUrl ()Ljava/lang/String; + public final fun getVideoSize (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getVideoSize$default (Lcom/lagradost/cloudstream3/utils/ExtractorLink;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun isDash ()Z + public final fun isM3u8 ()Z + public fun setAudioTracks (Ljava/util/List;)V + public fun setExtractorData (Ljava/lang/String;)V + public fun setHeaders (Ljava/util/Map;)V + public fun setQuality (I)V + public fun setReferer (Ljava/lang/String;)V + public fun setType (Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;)V + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lcom/lagradost/cloudstream3/utils/ExtractorLink;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final synthetic class com/lagradost/cloudstream3/utils/ExtractorLink$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/ExtractorLink$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/utils/ExtractorLink; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/utils/ExtractorLink;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/utils/ExtractorLink$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/utils/ExtractorLinkPlayList : com/lagradost/cloudstream3/utils/ExtractorLink { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;IZLjava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;IZLjava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()I + public final fun component6 ()Ljava/util/Map; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public final fun component9 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;)Lcom/lagradost/cloudstream3/utils/ExtractorLinkPlayList; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/ExtractorLinkPlayList;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/util/Map;Ljava/lang/String;Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/ExtractorLinkPlayList; + public fun equals (Ljava/lang/Object;)Z + public fun getAudioTracks ()Ljava/util/List; + public fun getExtractorData ()Ljava/lang/String; + public fun getHeaders ()Ljava/util/Map; + public fun getName ()Ljava/lang/String; + public final fun getPlaylist ()Ljava/util/List; + public fun getQuality ()I + public fun getReferer ()Ljava/lang/String; + public fun getSource ()Ljava/lang/String; + public fun getType ()Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public fun hashCode ()I + public fun setAudioTracks (Ljava/util/List;)V + public fun setExtractorData (Ljava/lang/String;)V + public fun setHeaders (Ljava/util/Map;)V + public fun setQuality (I)V + public fun setReferer (Ljava/lang/String;)V + public fun setType (Lcom/lagradost/cloudstream3/utils/ExtractorLinkType;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/ExtractorLinkType : java/lang/Enum { + public static final field DASH Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static final field M3U8 Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static final field MAGNET Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static final field TORRENT Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static final field VIDEO Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getMimeType ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; + public static fun values ()[Lcom/lagradost/cloudstream3/utils/ExtractorLinkType; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser; + public final fun parse (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$C { + public static final field CENC_TYPE_cbc1 Ljava/lang/String; + public static final field CENC_TYPE_cbcs Ljava/lang/String; + public static final field CENC_TYPE_cenc Ljava/lang/String; + public static final field CENC_TYPE_cens Ljava/lang/String; + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$C; + public static final field ROLE_FLAG_ALTERNATE I + public static final field ROLE_FLAG_CAPTION I + public static final field ROLE_FLAG_COMMENTARY I + public static final field ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND I + public static final field ROLE_FLAG_DESCRIBES_VIDEO I + public static final field ROLE_FLAG_DUB I + public static final field ROLE_FLAG_EASY_TO_READ I + public static final field ROLE_FLAG_EMERGENCY I + public static final field ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY I + public static final field ROLE_FLAG_MAIN I + public static final field ROLE_FLAG_SIGN I + public static final field ROLE_FLAG_SUBTITLE I + public static final field ROLE_FLAG_SUPPLEMENTARY I + public static final field ROLE_FLAG_TRANSCRIBES_DIALOG I + public static final field ROLE_FLAG_TRICK_PLAY I + public static final field SELECTION_FLAG_AUTOSELECT I + public static final field SELECTION_FLAG_DEFAULT I + public static final field SELECTION_FLAG_FORCED I + public static final field TRACK_TYPE_AUDIO I + public static final field TRACK_TYPE_CAMERA_MOTION I + public static final field TRACK_TYPE_DEFAULT I + public static final field TRACK_TYPE_IMAGE I + public static final field TRACK_TYPE_METADATA I + public static final field TRACK_TYPE_NONE I + public static final field TRACK_TYPE_TEXT I + public static final field TRACK_TYPE_UNKNOWN I + public static final field TRACK_TYPE_VIDEO I + public final fun getCLEARKEY_UUID ()Ljava/util/UUID; + public final fun getPLAYREADY_UUID ()Ljava/util/UUID; + public final fun getWIDEVINE_UUID ()Ljava/util/UUID; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData { + public fun (Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public final fun copy (Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData;Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData; + public fun equals (Ljava/lang/Object;)Z + public final fun getSchemeDatas ()[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public final fun getSchemeType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Format { + public static final field Companion Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format$Companion; + public static final field NO_VALUE I + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIFIFII)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIFIFIIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()I + public final fun component13 ()I + public final fun component14 ()F + public final fun component15 ()I + public final fun component16 ()F + public final fun component17 ()I + public final fun component18 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()I + public final fun component5 ()I + public final fun component6 ()I + public final fun component7 ()I + public final fun component8 ()I + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIFIFII)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIFIFIIILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public fun equals (Ljava/lang/Object;)Z + public final fun getAccessibilityChannel ()I + public final fun getAverageBitrate ()I + public final fun getBitrate ()I + public final fun getChannelCount ()I + public final fun getCodecs ()Ljava/lang/String; + public final fun getContainerMimeType ()Ljava/lang/String; + public final fun getFrameRate ()F + public final fun getHeight ()I + public final fun getId ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getLanguage ()Ljava/lang/String; + public final fun getPeakBitrate ()I + public final fun getPixelWidthHeightRatio ()F + public final fun getRoleFlags ()I + public final fun getRotationDegrees ()I + public final fun getSampleMimeType ()Ljava/lang/String; + public final fun getSelectionFlags ()I + public final fun getWidth ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Format$Companion { +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist { + public fun (Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/util/List;Ljava/util/Map;Ljava/util/List;Z)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/util/List;Ljava/util/Map;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/util/Map; + public final fun component11 ()Ljava/util/List; + public final fun component12 ()Z + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public final fun component9 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/util/List;Ljava/util/Map;Ljava/util/List;Z)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/util/List;Ljava/util/Map;Ljava/util/List;ZILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; + public fun equals (Ljava/lang/Object;)Z + public final fun getAudios ()Ljava/util/List; + public final fun getBaseUri ()Ljava/lang/String; + public final fun getClosedCaptions ()Ljava/util/List; + public final fun getHasIndependentSegments ()Z + public final fun getMuxedAudioFormat ()Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public final fun getMuxedCaptionFormats ()Ljava/util/List; + public final fun getSessionKeyDrmInitData ()Ljava/util/List; + public final fun getSubtitles ()Ljava/util/List; + public final fun getTags ()Ljava/util/List; + public final fun getVariableDefinitions ()Ljava/util/Map; + public final fun getVariants ()Ljava/util/List; + public final fun getVideos ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes { + public static final field APPLICATION_AIT Ljava/lang/String; + public static final field APPLICATION_CAMERA_MOTION Ljava/lang/String; + public static final field APPLICATION_CEA608 Ljava/lang/String; + public static final field APPLICATION_CEA708 Ljava/lang/String; + public static final field APPLICATION_DEPTH_METADATA Ljava/lang/String; + public static final field APPLICATION_DVBSUBS Ljava/lang/String; + public static final field APPLICATION_EMSG Ljava/lang/String; + public static final field APPLICATION_EXIF Ljava/lang/String; + public static final field APPLICATION_EXTERNALLY_LOADED_IMAGE Ljava/lang/String; + public static final field APPLICATION_ICY Ljava/lang/String; + public static final field APPLICATION_ID3 Ljava/lang/String; + public static final field APPLICATION_M3U8 Ljava/lang/String; + public static final field APPLICATION_MATROSKA Ljava/lang/String; + public static final field APPLICATION_MEDIA3_CUES Ljava/lang/String; + public static final field APPLICATION_MP4 Ljava/lang/String; + public static final field APPLICATION_MP4CEA608 Ljava/lang/String; + public static final field APPLICATION_MP4VTT Ljava/lang/String; + public static final field APPLICATION_MPD Ljava/lang/String; + public static final field APPLICATION_PGS Ljava/lang/String; + public static final field APPLICATION_RTSP Ljava/lang/String; + public static final field APPLICATION_SCTE35 Ljava/lang/String; + public static final field APPLICATION_SDP Ljava/lang/String; + public static final field APPLICATION_SS Ljava/lang/String; + public static final field APPLICATION_SUBRIP Ljava/lang/String; + public static final field APPLICATION_TTML Ljava/lang/String; + public static final field APPLICATION_TX3G Ljava/lang/String; + public static final field APPLICATION_VOBSUB Ljava/lang/String; + public static final field APPLICATION_WEBM Ljava/lang/String; + public static final field AUDIO_AAC Ljava/lang/String; + public static final field AUDIO_AC3 Ljava/lang/String; + public static final field AUDIO_AC4 Ljava/lang/String; + public static final field AUDIO_ALAC Ljava/lang/String; + public static final field AUDIO_ALAW Ljava/lang/String; + public static final field AUDIO_AMR Ljava/lang/String; + public static final field AUDIO_AMR_NB Ljava/lang/String; + public static final field AUDIO_AMR_WB Ljava/lang/String; + public static final field AUDIO_DTS Ljava/lang/String; + public static final field AUDIO_DTS_EXPRESS Ljava/lang/String; + public static final field AUDIO_DTS_HD Ljava/lang/String; + public static final field AUDIO_DTS_X Ljava/lang/String; + public static final field AUDIO_EXOPLAYER_MIDI Ljava/lang/String; + public static final field AUDIO_E_AC3 Ljava/lang/String; + public static final field AUDIO_E_AC3_JOC Ljava/lang/String; + public static final field AUDIO_FLAC Ljava/lang/String; + public static final field AUDIO_IAMF Ljava/lang/String; + public static final field AUDIO_MATROSKA Ljava/lang/String; + public static final field AUDIO_MIDI Ljava/lang/String; + public static final field AUDIO_MLAW Ljava/lang/String; + public static final field AUDIO_MP4 Ljava/lang/String; + public static final field AUDIO_MPEG Ljava/lang/String; + public static final field AUDIO_MPEGH_MHA1 Ljava/lang/String; + public static final field AUDIO_MPEGH_MHM1 Ljava/lang/String; + public static final field AUDIO_MPEG_L1 Ljava/lang/String; + public static final field AUDIO_MPEG_L2 Ljava/lang/String; + public static final field AUDIO_MSGSM Ljava/lang/String; + public static final field AUDIO_OGG Ljava/lang/String; + public static final field AUDIO_OPUS Ljava/lang/String; + public static final field AUDIO_RAW Ljava/lang/String; + public static final field AUDIO_TRUEHD Ljava/lang/String; + public static final field AUDIO_UNKNOWN Ljava/lang/String; + public static final field AUDIO_VORBIS Ljava/lang/String; + public static final field AUDIO_WAV Ljava/lang/String; + public static final field AUDIO_WEBM Ljava/lang/String; + public static final field BASE_TYPE_APPLICATION Ljava/lang/String; + public static final field BASE_TYPE_AUDIO Ljava/lang/String; + public static final field BASE_TYPE_IMAGE Ljava/lang/String; + public static final field BASE_TYPE_TEXT Ljava/lang/String; + public static final field BASE_TYPE_VIDEO Ljava/lang/String; + public static final field CODEC_E_AC3_JOC Ljava/lang/String; + public static final field IMAGE_AVIF Ljava/lang/String; + public static final field IMAGE_BMP Ljava/lang/String; + public static final field IMAGE_HEIC Ljava/lang/String; + public static final field IMAGE_HEIF Ljava/lang/String; + public static final field IMAGE_JPEG Ljava/lang/String; + public static final field IMAGE_JPEG_R Ljava/lang/String; + public static final field IMAGE_PNG Ljava/lang/String; + public static final field IMAGE_RAW Ljava/lang/String; + public static final field IMAGE_WEBP Ljava/lang/String; + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes; + public static final field TEXT_SSA Ljava/lang/String; + public static final field TEXT_UNKNOWN Ljava/lang/String; + public static final field TEXT_VTT Ljava/lang/String; + public static final field VIDEO_APV Ljava/lang/String; + public static final field VIDEO_AV1 Ljava/lang/String; + public static final field VIDEO_AVI Ljava/lang/String; + public static final field VIDEO_DIVX Ljava/lang/String; + public static final field VIDEO_DOLBY_VISION Ljava/lang/String; + public static final field VIDEO_FLV Ljava/lang/String; + public static final field VIDEO_H263 Ljava/lang/String; + public static final field VIDEO_H264 Ljava/lang/String; + public static final field VIDEO_H265 Ljava/lang/String; + public static final field VIDEO_MATROSKA Ljava/lang/String; + public static final field VIDEO_MJPEG Ljava/lang/String; + public static final field VIDEO_MP2T Ljava/lang/String; + public static final field VIDEO_MP4 Ljava/lang/String; + public static final field VIDEO_MP42 Ljava/lang/String; + public static final field VIDEO_MP43 Ljava/lang/String; + public static final field VIDEO_MP4V Ljava/lang/String; + public static final field VIDEO_MPEG Ljava/lang/String; + public static final field VIDEO_MPEG2 Ljava/lang/String; + public static final field VIDEO_MV_HEVC Ljava/lang/String; + public static final field VIDEO_OGG Ljava/lang/String; + public static final field VIDEO_PS Ljava/lang/String; + public static final field VIDEO_RAW Ljava/lang/String; + public static final field VIDEO_UNKNOWN Ljava/lang/String; + public static final field VIDEO_VC1 Ljava/lang/String; + public static final field VIDEO_VP8 Ljava/lang/String; + public static final field VIDEO_VP9 Ljava/lang/String; + public static final field VIDEO_WEBM Ljava/lang/String; + public final fun getMediaMimeType (Ljava/lang/String;)Ljava/lang/String; + public final fun getMimeTypeFromMp4ObjectType (I)Ljava/lang/String; + public final fun getObjectTypeFromMp4aRFC6381CodecString (Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes$Mp4aObjectType; + public final fun getTrackType (Ljava/lang/String;)I + public final fun getTrackTypeOfCodec (Ljava/lang/String;)I + public final fun isAudio (Ljava/lang/String;)Z + public final fun isDolbyVisionCodec (Ljava/lang/String;Ljava/lang/String;)Z + public final fun isImage (Ljava/lang/String;)Z + public final fun isText (Ljava/lang/String;)Z + public final fun isVideo (Ljava/lang/String;)Z +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes$Mp4aObjectType { + public fun (II)V + public final fun component1 ()I + public final fun component2 ()I + public final fun copy (II)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes$Mp4aObjectType; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes$Mp4aObjectType;IIILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$MimeTypes$Mp4aObjectType; + public fun equals (Ljava/lang/Object;)Z + public final fun getAudioObjectTypeIndication ()I + public final fun getObjectTypeIndication ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Mp4Box { + public static final field FULL_HEADER_SIZE I + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Mp4Box; + public static final field TYPE_pssh I +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException : java/io/IOException { + public static final field Companion Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException$Companion; + public static final field DATA_TYPE_MANIFEST I + public fun (Ljava/lang/String;Ljava/lang/Throwable;ZI)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Throwable; + public final fun component3 ()Z + public final fun component4 ()I + public final fun copy (Ljava/lang/String;Ljava/lang/Throwable;ZI)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException;Ljava/lang/String;Ljava/lang/Throwable;ZIILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException; + public fun equals (Ljava/lang/Object;)Z + public fun getCause ()Ljava/lang/Throwable; + public final fun getContentIsMalformed ()Z + public final fun getDataType ()I + public fun getMessage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException$Companion { + public final fun createForMalformedManifest (Ljava/lang/String;Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserException; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil; + public final fun buildPsshAtom (Ljava/util/UUID;[Ljava/util/UUID;[B)[B +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition { + public fun (Lio/ktor/http/Url;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Lio/ktor/http/Url; + public final fun component2 ()Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Lio/ktor/http/Url;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition;Lio/ktor/http/Url;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition; + public fun equals (Ljava/lang/Object;)Z + public final fun getFormat ()Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public final fun getGroupId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getUrl ()Lio/ktor/http/Url; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData { + public fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)V + public synthetic fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/UUID; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()[B + public final fun copy (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()[B + public final fun getLicenseServerUrl ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun getUuid ()Ljava/util/UUID; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$UrlUtil { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$UrlUtil; + public final fun resolveToUrl (Ljava/lang/String;Ljava/lang/String;)Lio/ktor/http/Url; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Util { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Util; + public final fun getCodecsOfType (Ljava/lang/String;I)Ljava/lang/String; + public final fun getCodecsWithoutType (Ljava/lang/String;I)Ljava/lang/String; + public final fun split (Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String; + public final fun splitAtFirst (Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String; + public final fun splitCodecs (Ljava/lang/String;)[Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Variant { + public fun (Lio/ktor/http/Url;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Lio/ktor/http/Url; + public final fun component2 ()Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Lio/ktor/http/Url;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Variant; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Variant;Lio/ktor/http/Url;Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Variant; + public fun equals (Ljava/lang/Object;)Z + public final fun getAudioGroupId ()Ljava/lang/String; + public final fun getCaptionGroupId ()Ljava/lang/String; + public final fun getFormat ()Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$Format; + public final fun getSubtitleGroupId ()Ljava/lang/String; + public final fun getUrl ()Lio/ktor/http/Url; + public final fun getVideoGroupId ()Ljava/lang/String; + public fun hashCode ()I + public final fun isPlayableStandalone (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist;)Z + public final fun isTrickPlay ()Z + public final fun mustContainAudio (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist;)Z + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$VariantInfo { + public fun (IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun copy (IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$VariantInfo; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$VariantInfo;IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$VariantInfo; + public fun equals (Ljava/lang/Object;)Z + public final fun getAudioGroupId ()Ljava/lang/String; + public final fun getAverageBitrate ()I + public final fun getCaptionGroupId ()Ljava/lang/String; + public final fun getPeakBitrate ()I + public final fun getSubtitleGroupId ()Ljava/lang/String; + public final fun getVideoGroupId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/JsContext { + public synthetic fun (JJLkotlinx/coroutines/CoroutineScope;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun eval (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun get (Ljava/lang/String;)Ljava/lang/Object; + public final fun getMaxExecutionTime-UwyO8pc ()J + public final fun getMaxInstructions ()J + public final fun set (Ljava/lang/String;Ljava/lang/Object;)V + public final fun setMaxExecutionTime-LRDsOJo (J)V + public final fun setMaxInstructions (J)V +} + +public final class com/lagradost/cloudstream3/utils/JsHunter { + public fun (Ljava/lang/String;)V + public final fun dehunt ()Ljava/lang/String; + public final fun detect ()Z +} + +public final class com/lagradost/cloudstream3/utils/JsInterpreterKt { + public static final fun evalJs-1Y68eR8 (Ljava/lang/String;Ljava/lang/String;JJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun evalJs-1Y68eR8$default (Ljava/lang/String;Ljava/lang/String;JJLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun jsValueToString (Ljava/lang/Object;)Ljava/lang/String; + public static final fun newJsContext (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun newJsContext$default (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/utils/JsUnpacker { + public static final field Companion Lcom/lagradost/cloudstream3/utils/JsUnpacker$Companion; + public fun (Ljava/lang/String;)V + public final fun detect ()Z + public final fun unpack ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/JsUnpacker$Companion { + public final fun getC ()Ljava/util/List; + public final fun getZ ()Ljava/util/List; + public final fun load (Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/Levenshtein { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/Levenshtein; + public final fun partialRatio (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)I + public static synthetic fun partialRatio$default (Lcom/lagradost/cloudstream3/utils/Levenshtein;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I + public final fun ratio (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)I + public static synthetic fun ratio$default (Lcom/lagradost/cloudstream3/utils/Levenshtein;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I +} + +public final class com/lagradost/cloudstream3/utils/M3u8Helper { + public static final field Companion Lcom/lagradost/cloudstream3/utils/M3u8Helper$Companion; + public fun ()V + public final fun m3u8Generation (Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;Ljava/lang/Boolean;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun m3u8Generation$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper;Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;Ljava/lang/Boolean;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/utils/M3u8Helper$Companion { + public final fun generateM3u8 (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun generateM3u8$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream { + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;)Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream; + public fun equals (Ljava/lang/Object;)Z + public final fun getHeaders ()Ljava/util/Map; + public final fun getQuality ()Ljava/lang/Integer; + public final fun getStreamUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/M3u8Helper2 { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/M3u8Helper2; + public final fun generateM3u8 (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun generateM3u8$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getDecrypted ([B[B[BI)[B + public static synthetic fun getDecrypted$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2;[B[B[BIILjava/lang/Object;)[B + public final fun hslLazy (Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;ZZILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun hslLazy$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2;Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;ZZILkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun m3u8Generation (Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun m3u8Generation$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2;Lcom/lagradost/cloudstream3/utils/M3u8Helper$M3u8Stream;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/utils/M3u8Helper2$LazyHlsDownloadData { + public fun ([B[BZLjava/util/List;Ljava/lang/String;Ljava/util/Map;)V + public final fun component3 ()Z + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/util/Map; + public final fun copy ([B[BZLjava/util/List;Ljava/lang/String;Ljava/util/Map;)Lcom/lagradost/cloudstream3/utils/M3u8Helper2$LazyHlsDownloadData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2$LazyHlsDownloadData;[B[BZLjava/util/List;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/M3u8Helper2$LazyHlsDownloadData; + public fun equals (Ljava/lang/Object;)Z + public final fun getAllTsLinks ()Ljava/util/List; + public final fun getHeaders ()Ljava/util/Map; + public final fun getRelativeUrl ()Ljava/lang/String; + public final fun getSize ()I + public fun hashCode ()I + public final fun isEncrypted ()Z + public final fun resolveLink (ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun resolveLinkSafe (IIJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun resolveLinkSafe$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2$LazyHlsDownloadData;IIJLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun resolveLinkWhileSafe (IIJLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun resolveLinkWhileSafe$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2$LazyHlsDownloadData;IIJLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/M3u8Helper2$TsLink { + public fun (Ljava/lang/String;Ljava/lang/Double;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Double; + public final fun copy (Ljava/lang/String;Ljava/lang/Double;)Lcom/lagradost/cloudstream3/utils/M3u8Helper2$TsLink; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/M3u8Helper2$TsLink;Ljava/lang/String;Ljava/lang/Double;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/M3u8Helper2$TsLink; + public fun equals (Ljava/lang/Object;)Z + public final fun getTime ()Ljava/lang/Double; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/PlayListItem { + public fun (Ljava/lang/String;J)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()J + public final fun copy (Ljava/lang/String;J)Lcom/lagradost/cloudstream3/utils/PlayListItem; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/PlayListItem;Ljava/lang/String;JILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/PlayListItem; + public fun equals (Ljava/lang/Object;)Z + public final fun getDurationUs ()J + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/Qualities : java/lang/Enum { + public static final field Companion Lcom/lagradost/cloudstream3/utils/Qualities$Companion; + public static final field P1080 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P144 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P1440 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P2160 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P240 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P360 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P480 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field P720 Lcom/lagradost/cloudstream3/utils/Qualities; + public static final field Unknown Lcom/lagradost/cloudstream3/utils/Qualities; + public final fun getDefaultPriority ()I + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public final fun setValue (I)V + public static fun valueOf (Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/Qualities; + public static fun values ()[Lcom/lagradost/cloudstream3/utils/Qualities; +} + +public final class com/lagradost/cloudstream3/utils/Qualities$Companion { + public final fun getStringByInt (Ljava/lang/Integer;)Ljava/lang/String; + public final fun getStringByIntFull (I)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/ShortLink { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/ShortLink; + public final fun isShortLink (Ljava/lang/String;)Z + public final fun unshorten (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun unshorten$default (Lcom/lagradost/cloudstream3/utils/ShortLink;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun unshortenAdfly (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unshortenDavisonbarker (Ljava/lang/String;)Ljava/lang/String; + public final fun unshortenIsecure (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unshortenLinksafe (Ljava/lang/String;)Ljava/lang/String; + public final fun unshortenLinkup (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unshortenNuovoIndirizzo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unshortenNuovoLink (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unshortenUprot (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/lagradost/cloudstream3/utils/ShortLink$ShortUrl { + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V + public fun (Lkotlin/text/Regex;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V + public final fun component1 ()Lkotlin/text/Regex; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlin/jvm/functions/Function2; + public final fun copy (Lkotlin/text/Regex;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcom/lagradost/cloudstream3/utils/ShortLink$ShortUrl; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/ShortLink$ShortUrl;Lkotlin/text/Regex;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/ShortLink$ShortUrl; + public fun equals (Ljava/lang/Object;)Z + public final fun getFunction ()Lkotlin/jvm/functions/Function2; + public final fun getRegex ()Lkotlin/text/Regex; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/StringUtils { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/StringUtils; + public final fun decodeUri (Ljava/lang/String;)Ljava/lang/String; + public final fun decodeUrl (Ljava/lang/String;)Ljava/lang/String; + public final fun encodeUri (Ljava/lang/String;)Ljava/lang/String; + public final fun encodeUrl (Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/SubtitleHelper { + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/SubtitleHelper; + public final fun fromCodeToLangTagIETF (Ljava/lang/String;)Ljava/lang/String; + public final fun fromCodeToOpenSubtitlesTag (Ljava/lang/String;)Ljava/lang/String; + public final fun fromLanguageToTagIETF (Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String; + public static synthetic fun fromLanguageToTagIETF$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Ljava/lang/String; + public final fun fromLanguageToThreeLetters (Ljava/lang/String;)Ljava/lang/String; + public final fun fromLanguageToTwoLetters (Ljava/lang/String;Z)Ljava/lang/String; + public final fun fromTagToEnglishLanguageName (Ljava/lang/String;)Ljava/lang/String; + public final fun fromTagToLanguageName (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun fromTagToLanguageName$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public final fun fromThreeLettersToLanguage (Ljava/lang/String;)Ljava/lang/String; + public final fun fromTwoLettersToLanguage (Ljava/lang/String;)Ljava/lang/String; + public final fun getFlagFromIso (Ljava/lang/String;)Ljava/lang/String; + public final fun getIndexMapIETF_tag ()Ljava/util/Map; + public final fun getIndexMapISO_639_1 ()Ljava/util/Map; + public final fun getIndexMapISO_639_2_B ()Ljava/util/Map; + public final fun getIndexMapISO_639_3 ()Ljava/util/Map; + public final fun getIndexMapLanguageName ()Ljava/util/Map; + public final fun getIndexMapNativeName ()Ljava/util/Map; + public final fun getIndexMapOpenSubtitles ()Ljava/util/Map; + public final fun getLanguages ()Ljava/util/List; + public final fun getNameNextToFlagEmoji (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun getNameNextToFlagEmoji$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public final fun isWellFormedTagIETF (Ljava/lang/String;)Z +} + +public final class com/lagradost/cloudstream3/utils/SubtitleHelper$Language639 { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/SubtitleHelper$Language639; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper$Language639;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/SubtitleHelper$Language639; + public fun equals (Ljava/lang/Object;)Z + public final fun getISO_639_1 ()Ljava/lang/String; + public final fun getISO_639_2_B ()Ljava/lang/String; + public final fun getISO_639_2_T ()Ljava/lang/String; + public final fun getISO_639_3 ()Ljava/lang/String; + public final fun getISO_639_6 ()Ljava/lang/String; + public final fun getLanguageName ()Ljava/lang/String; + public final fun getNativeName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetadata { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetadata; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetadata;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetadata; + public fun equals (Ljava/lang/Object;)Z + public final fun getIETF_tag ()Ljava/lang/String; + public final fun getISO_639_1 ()Ljava/lang/String; + public final fun getISO_639_2_B ()Ljava/lang/String; + public final fun getISO_639_3 ()Ljava/lang/String; + public final fun getLanguageName ()Ljava/lang/String; + public final fun getNativeName ()Ljava/lang/String; + public final fun getOpenSubtitles ()Ljava/lang/String; + public fun hashCode ()I + public final fun localizedName (Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun localizedName$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetadata;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public final fun nameNextToFlagEmoji (Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun nameNextToFlagEmoji$default (Lcom/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetadata;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class com/lagradost/cloudstream3/utils/SubtitleHelperKt { + public static final fun getCurrentLocale ()Ljava/lang/String; +} + +public abstract class com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer : kotlinx/serialization/json/JsonTransformingSerializer { + public fun (Lkotlinx/serialization/KSerializer;)V + protected fun transformSerialize (Lkotlinx/serialization/json/JsonElement;)Lkotlinx/serialization/json/JsonElement; +} + +public abstract class com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer : kotlinx/serialization/json/JsonTransformingSerializer { + public fun (Lkotlinx/serialization/KSerializer;Ljava/util/Set;)V + protected fun transformSerialize (Lkotlinx/serialization/json/JsonElement;)Lkotlinx/serialization/json/JsonElement; +} + diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 979e09844..aa460164e 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -87,6 +87,18 @@ kotlin { androidMain { dependsOn(jvmCommonMain) } jvmMain { dependsOn(jvmCommonMain) } } + + @OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class) + // https://kotlinlang.org/docs/gradle-binary-compatibility-validation.html + abiValidation { + enabled.set(true) + this.filters { + exclude { + annotatedWith.add("com.lagradost.cloudstream3.Prerelease") + annotatedWith.add("com.lagradost.cloudstream3.InternalAPI") + } + } + } } tasks.withType { From 4a79ca1c9f90f7412bfaaa31f9ba34c276c242e8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:21:11 -0600 Subject: [PATCH 054/105] Add missing types to getKey calls (#3056) --- .../main/java/com/lagradost/cloudstream3/MainActivity.kt | 4 ++-- .../com/lagradost/cloudstream3/actions/temp/VlcPackage.kt | 4 ++-- .../com/lagradost/cloudstream3/plugins/PluginManager.kt | 4 ++-- .../lagradost/cloudstream3/plugins/RepositoryManager.kt | 4 ++-- .../com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt | 2 +- .../ui/player/source_priority/QualityDataHelper.kt | 6 +++--- .../lagradost/cloudstream3/ui/settings/SettingsGeneral.kt | 2 +- .../ui/subtitles/ChromecastSubtitlesFragment.kt | 2 +- .../cloudstream3/ui/subtitles/SubtitlesFragment.kt | 6 +++--- .../com/lagradost/cloudstream3/utils/DataStoreHelper.kt | 2 +- .../com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 8 ++++---- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 7db33284e..5d39f6554 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -1215,7 +1215,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa // backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting? safe { val appVer = BuildConfig.VERSION_NAME - val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: "" + val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: "" if (appVer != lastAppAutoBackup) { setKey("VERSION_NAME", BuildConfig.VERSION_NAME) if (lastAppAutoBackup.isEmpty()) return@safe @@ -2018,7 +2018,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } try { - if (getKey(HAS_DONE_SETUP_KEY, false) != true) { + if (getKey(HAS_DONE_SETUP_KEY, false) != true) { navController.navigate(R.id.navigation_setup_language) // If no plugins bring up extensions screen } else if (PluginManager.getPluginsOnline().isEmpty() diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index 46b46a2c2..b6478b1d9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -60,7 +60,7 @@ open class VlcPackage: OpenInAppAction( intent.putExtra("secure_uri", true) intent.putExtra("title", video.name) - val subsLang = getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" + val subsLang = getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" result.subs.firstOrNull { subsLang == it.languageCode }?.let { @@ -74,4 +74,4 @@ open class VlcPackage: OpenInAppAction( Log.d("VLC", "Position: $position, Duration: $duration") updateDurationAndPosition(position, duration) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 829d871d2..6054bbfaf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -176,11 +176,11 @@ object PluginManager { fun getPluginsOnline(): Array { - return getKey(PLUGINS_KEY) ?: emptyArray() + return getKey>(PLUGINS_KEY) ?: emptyArray() } fun getPluginsLocal(): Array { - return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray() + return getKey>(PLUGINS_KEY_LOCAL) ?: emptyArray() } private val CLOUD_STREAM_FOLDER = diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index 7fec67637..3879ddca4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -98,7 +98,7 @@ data class PluginWrapper( object RepositoryManager { const val ONLINE_PLUGINS_FOLDER = "Extensions" val PREBUILT_REPOSITORIES: Array by lazy { - getKey("PREBUILT_REPOSITORIES") ?: emptyArray() + getKey>("PREBUILT_REPOSITORIES") ?: emptyArray() } private val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$") @@ -240,7 +240,7 @@ object RepositoryManager { } fun getRepositories(): Array { - return getKey(REPOSITORIES_KEY) ?: emptyArray() + return getKey>(REPOSITORIES_KEY) ?: emptyArray() } // Don't want to read before we write in another thread 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 d7e10c814..d316f28cd 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 @@ -719,7 +719,7 @@ class CS3IPlayer : IPlayer { **/ var preferredAudioTrackLanguage: String? = null get() { - return field ?: getKey( + return field ?: getKey( "$currentAccount/$PREFERRED_AUDIO_LANGUAGE_KEY", field )?.also { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt index 02470484e..15d25148c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt @@ -55,7 +55,7 @@ object QualityDataHelper { fun getSourcePriority(profile: Int, name: String?): Int { if (name == null) return DEFAULT_SOURCE_PRIORITY - return getKey( + return getKey( "$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile", name, DEFAULT_SOURCE_PRIORITY @@ -94,7 +94,7 @@ object QualityDataHelper { } fun getQualityPriority(profile: Int, quality: Qualities): Int { - return getKey( + return getKey( "$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile", quality.value.toString(), quality.defaultPriority @@ -223,4 +223,4 @@ object QualityDataHelper { if (target == null) return Qualities.Unknown return Qualities.entries.minBy { abs(it.value - target) } } -} \ No newline at end of file +} 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 840c6aede..354424978 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 @@ -359,7 +359,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { } ?: emptyList() } - settingsManager.edit { putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false) } + settingsManager.edit { putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false) } getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue -> setKey(getString(R.string.jsdelivr_proxy_key), newValue) return@setOnPreferenceChangeListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt index 95ef264f8..ab64e9993 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt @@ -101,7 +101,7 @@ class ChromecastSubtitlesFragment : BaseFragment(CHROME_SUBTITLE_KEY) ?: defaultState } private val defaultState = SaveChromeCaptionStyle() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index 860cb4fcb..cf892424f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -261,7 +261,7 @@ class SubtitlesFragment : BaseDialogFragment( } fun getCurrentSavedStyle(): SaveCaptionStyle { - return cachedSubtitleStyle ?: (getKey(SUBTITLE_KEY) ?: SaveCaptionStyle( + return cachedSubtitleStyle ?: (getKey(SUBTITLE_KEY) ?: SaveCaptionStyle( foregroundColor = getDefColor(0), backgroundColor = getDefColor(2), windowColor = getDefColor(3), @@ -293,11 +293,11 @@ class SubtitlesFragment : BaseDialogFragment( } fun getDownloadSubsLanguageTagIETF(): List { - return getKey(SUBTITLE_DOWNLOAD_KEY) ?: listOf("en") + return getKey>(SUBTITLE_DOWNLOAD_KEY) ?: listOf("en") } fun getAutoSelectLanguageTagIETF(): String { - return getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" + return getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index ec896facb..340266f6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -185,7 +185,7 @@ object DataStoreHelper { * Setting this does not automatically reload the homepage. */ var currentHomePage: String? - get() = getKey("$currentAccount/$USER_SELECTED_HOMEPAGE_API") + get() = getKey("$currentAccount/$USER_SELECTED_HOMEPAGE_API") set(value) { val key = "$currentAccount/$USER_SELECTED_HOMEPAGE_API" if (value == null) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index feecbe312..4c0717b3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -23,15 +23,15 @@ const val PROGRAM_ID_LIST_KEY = "persistent_program_ids" object TvChannelUtils { fun Context.saveProgramId(programId: Long) { - val existing: List = getKey(PROGRAM_ID_LIST_KEY) ?: emptyList() + val existing: List = getKey>(PROGRAM_ID_LIST_KEY) ?: emptyList() val updated = (existing + programId).distinct() setKey(PROGRAM_ID_LIST_KEY, updated) } fun Context.getStoredProgramIds(): List { - return getKey(PROGRAM_ID_LIST_KEY) ?: emptyList() + return getKey>(PROGRAM_ID_LIST_KEY) ?: emptyList() } fun Context.removeProgramId(programId: Long) { - val existing: List = getKey(PROGRAM_ID_LIST_KEY) ?: emptyList() + val existing: List = getKey>(PROGRAM_ID_LIST_KEY) ?: emptyList() val updated = existing.filter { it != programId } setKey(PROGRAM_ID_LIST_KEY, updated) } @@ -161,4 +161,4 @@ object TvChannelUtils { } } -} \ No newline at end of file +} From 11792dd65c4f7fc534b1df7e17aa5fc645b0d17b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:21:42 -0600 Subject: [PATCH 055/105] Add type for tryParseJson (#3057) --- .../src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index c29ef150f..289e96998 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -1823,7 +1823,7 @@ interface LoadResponse { /** Read the id string to get all other ids */ fun readIdFromString(idString: String?): Map { - return tryParseJson(idString) ?: return emptyMap() + return tryParseJson>(idString) ?: return emptyMap() } fun LoadResponse.isMovie(): Boolean { From c265d630ac81a05204f50cb29a2085bea7ed63f9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 11 Jul 2026 21:21:43 +0200 Subject: [PATCH 056/105] Translated using Weblate (Latvian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 81.4% (594 of 729 strings) Translated using Weblate (Bengali) Currently translated at 48.1% (351 of 729 strings) Translated using Weblate (Lithuanian) Currently translated at 35.6% (260 of 729 strings) Translated using Weblate (Dutch) Currently translated at 91.0% (664 of 729 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Arabic (Saudi Arabia)) Currently translated at 100.0% (4 of 4 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (729 of 729 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Vietnamese) Currently translated at 100.0% (729 of 729 strings) Translated using Weblate (Malayalam) Currently translated at 37.8% (276 of 729 strings) Translated using Weblate (Japanese) Currently translated at 99.5% (726 of 729 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Korean) Currently translated at 100.0% (729 of 729 strings) Co-authored-by: Aishik Mukherjee Co-authored-by: Hosted Weblate Co-authored-by: Kie Takaba Co-authored-by: Man Co-authored-by: Nori Masomi (Mystical) Co-authored-by: Saleh ALHarbi Co-authored-by: caviaz Co-authored-by: gabix Co-authored-by: hou1234 Co-authored-by: trodiz Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/bn/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ja/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ko/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/lt/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/lv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ml/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/nl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/ar_SA/ Translation: Cloudstream/App Translation: Cloudstream/Fastlane --- app/src/main/res/values-b+bn/strings.xml | 1 + app/src/main/res/values-b+ja/strings.xml | 3 + app/src/main/res/values-b+lt/strings.xml | 75 ++++++++++++++++++----- app/src/main/res/values-b+lv/strings.xml | 15 +++-- app/src/main/res/values-b+nl/strings.xml | 5 ++ fastlane/metadata/android/ar-SA/title.txt | 2 +- 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index adc1b3f19..e90608793 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -353,4 +353,5 @@ শুরু থেকে চালু করুন স্পিচ রিকগনিশন উপলব্ধ নেই কথা বলা শুরু করুন… + ডাউনলোড তালিকা diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index 2b611ef69..c5e6272b2 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -730,4 +730,7 @@ ソースの優先順位 プレイヤーでのビデオソースの並び順を設定します プレイヤーメタデータオーバーレイを表示 + 動画 + プレビュー + ライブ diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index db7e994f0..bccc4cf59 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -25,18 +25,18 @@ Kopijuoti Duoti bananai Daugiau informacijos - Atsiuntimai + Atsisiuntimai Automatiškai pasirinkti kalbą Klaida kraunant nuorodas +30 - Atsiuntimas baigtas + Atsisiuntimas baigtas Tęsti žiūrėjimą - Rastas atnaujinimas! \n%1$s -> %2$s + Rastas atnaujinimas!\n%1$s -> %2$s Atsisiųsti kalbas Ieškoti naudojant tiekėjus Grįžti atgal Siunčiama - Daugiau pasirinkčiu + Daugiau parinkčių Paleisti seriją Grotuvo greitis %d Bananai duoti kūrėjams @@ -45,7 +45,7 @@ Žanrai -30 Serijos plakatas - Gali reikėti VPN šitam tiekėjui, kad veiktų teisingai + Gali reikėti VPN šiam tiekėjui, kad veiktų teisingai Ieškoti %s… Github Nėra duotu bananų @@ -66,7 +66,7 @@ Atšaukti Pradėti Filmukas - Atsiuntimas atšauktas + Atsisiuntimas atšauktas Išplėstinė paieška Tuščias sąrašas. Pabandykite pasirinkti kitą sąrašą. Chromecast subtitrai @@ -93,10 +93,10 @@ Užbaigta Naudoti sistemos ryškumą programos grotuve vietoj tamsumo Nepavyko atstatyti duomenis iš failo %s - Paleisti anonsa - Paleisti gyva transliacija + Paleisti anonsą + Paleisti gyvą transliaciją Nerasta serijų - Šis tiekėjas yra iš Torrentų, VPN rekomenduojama naudoti + Šis tiekėjas yra iš Torrent\'ų, rekomenduojamas VPN Nepavyko Plakatas Paleisti failą @@ -108,21 +108,21 @@ Perdaryti nustatymo procesą %1$d-%2$d Duoti bananą kūrėjams - Sugryšti + Sugrįšti Nuoroda nukopijuota į iškarpinę Paieška Informacija - Praleisti įkėlima + Praleisti įkėlimą Informacija Serija %d bus išleista Išsaugoti Perdaug teksto. Nepavyko išsaugoti i iškarpynę. - Atsiuntimas nepavyko + Atsisiuntimas nepavyko Pasidalinti Pagrindinis Plakatas Šaltiniai Nustatymai - Ieškoti + Paieška Kraunama… Pašalinti Daugiau informacijos @@ -138,7 +138,7 @@ Uždaryti Nustatyti žiūrėjimo statusą Paleisti su CloudStream - Subtitrų iškėlimas + Subtitrų lygis Serijos Skleisti: %s Sezonas @@ -248,4 +248,51 @@ Ar tikrai norite išeiti? Pašalinti iš žiūrimų Garso takelis + Užpildymas + min + Namai + Atsisiuntimų eilė + Balso atpažinimas nepasiekiamas + Pradėkite kalbėti… + Nežiūrimas + Paleisti visą seriją + Šis įrašas yra Torrent\'e, tai reiškia, kad tavo įrašo veikla gali būti sekama. Įsitikinkite, kad suprantate Torrenting prieš tęsiant. + Atkurti ryšį… + Pasirinkite elementus, kuriuos norite pašalinti + Šiuo metu atsisiuntimų nėra. + Šiuo metu atsisiuntimų eilėje nėra. + Pasiekiama žiūrėti neprisijungus + Pasirinkti viską + Panaikinti visus pasirinkimus + Tinklo srautas + Atidaryti vietinį video + Nuorodos perkrautos + Subtitrai + Repozitorijos pavadinimas ir adresas + nukopijuota! + Naujos serijos pranešimas + Ieškoti kituose plėtiniuose + Rodyti rekomendacijas + Apvado spalva + Rėmelio tipas + Ieškoti naudojant tipus + Laikyti ilgai, kad sugražinti pradinius nustatymus + Įkelti šriftus sudedant juos į %s + Metaduomenys nesuteikiamos svetainės, vaizdo įkėlimas nepavyks, jeigu jo nebus svetainėje. + Aprašymas + Nėra siužeto aprašymo + Aprašymas nerastas + Rodyti Logcat + Vaizdas vaizde + Tęsia atkūrimą mažame grotuve virš kitų programų + Grotuvo dydžio pakeitimo mygtukas + Pašalinti juodas paraštes + Subtitrai + Chromecast subtitrų nustatymai + Atkūrimo sparta + Prideda greičio pasirinkimą grotuve + Braukite, kas ieškotumėte + Braukite į šonus, kad kontroliuotumėte padėtį vaizdo įraše + Braukite, kad pakeistumėte nustatymus + Braukite aukštyn arba žemyn ekrano kairėje arba dešinėje, jei norite pakeisti ryškumą arba garsą diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 9ab29cd43..531b5420b 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -287,12 +287,12 @@ Parasts Izskats Funkcijas - Ģenerāls + Vispārīgie Randomā poga Rādīt izlases pogu Sākums un Bibliotēka sadaļās Papildinājuma valodas Lietotnes izkārtojums - Izvēlētā media + Vēlamie multimediji Iespējot nepiedienīgu, izaicinošu saturu (NSFW) atbalstītajos papildinājumos Subtitru kodējums Devēji @@ -372,7 +372,7 @@ Kļūda Noņemt slēgtos parakstus no subtitriem Noņemt lieko no subtitriem (piemēram, reklāmu) - Filtrēt pēc vēlamās multivides valodas + Atlasīt pēc vēlamās multimediju valodas Ekstras Treileris https://piemērs.com/piemērs.mp4 @@ -382,7 +382,7 @@ Iepriekšējais Izlaist uzstādīšanu Mainiet lietotnes izskatu, lai tā atbilstu savai ierīcei - Ko tu vēlies redzēt + Ko jūs vēlētos skatīt Pabeigts Papildinājumi Pievienot repozitoriju @@ -607,4 +607,11 @@ Vai tiešām vēlaties neatgriezeniski dzēst visas šī seriāla, raidījuma epizodes?\n\n%s Pašlaik nav nevienas rindā ievietotas lejupielādes. Atvērt vietējo video + Multimedija + Multimediju informācija + Iespējojiet torrentu Iestatījumi/Pakalpojumu sniedzēji/Vēlamie multimediji sadaļā + Paziņojums par jaunu epizodi + Papildu spilgtums + Papildu spilgtums iespējots + Meklēšanas ieteikumi diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index 3164e6b4e..8a2424326 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -677,4 +677,9 @@ Bevestig voor afsluiten Toon dialoogvenster voordat de app wordt afgesloten Randgrote + Laat Speler Metadata Overlay zien + Kon de apparaat PIN niet vinden, probeer locale authenticatie + Zet torrent aan in Instellingen/Providers/Media voorkeur + Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan. + Handmatige Update Knop diff --git a/fastlane/metadata/android/ar-SA/title.txt b/fastlane/metadata/android/ar-SA/title.txt index 7977b2906..dde89d58f 100644 --- a/fastlane/metadata/android/ar-SA/title.txt +++ b/fastlane/metadata/android/ar-SA/title.txt @@ -1 +1 @@ -كلاودستريم +CloudStream From 70469f55dbc6a8535ff2b5a57b0f83e79825aa23 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:43:59 +0000 Subject: [PATCH 057/105] [skip ci] Migration guide (#3058) --- COMPOSE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 COMPOSE.md diff --git a/COMPOSE.md b/COMPOSE.md new file mode 100644 index 000000000..8d83a50ae --- /dev/null +++ b/COMPOSE.md @@ -0,0 +1,21 @@ +# Migration guide to Compose + +### 1. MVI instead of MVVM + +The current design of CloudStream loosely uses the MVVM architecture. + +This means that the UI invokes the viewmodel with function calls, and it responds with LiveData fields that are observed. While this has worked, it generates a lot of boilerplate and has created some friction. + +To make it easier to work with Compose, the new architecture will be based on MVI. In short this means that the viewmodel exposes a singular immutable class that is observed, and receives all UI events with a singular event that is a sealed class. All the UI should be able to be recreated based on this singular state class, and all interactions should be able to be replayed using only the event callback. + +For a more detailed overview, see: https://www.youtube.com/watch?v=b2z1jvD4VMQ + +This is part of the effort to make CloudStream cross platform, as it allows us to decouple UI and logic. + +### 2. KMP-compatible libraries + +We plan to leverage Kotlin's KMP project to compile our code to different architectures. However, this requires us to only use KMP-compatible libraries, no Java. Therefore any pull requests must ensure that they use KMP-compatible libraries only. + +### 3. UI Changes + +While migrating to the new compose UI, you also have the opportunity to change the UI. However, this should only be to freshen up the UI, not completely redesign it. It is also important to stress that this process should not lose any features of the old UI, and be very conservative with adding new features. \ No newline at end of file From 505722c1b5c1a79d8295e984e55d3c72457b2e44 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:09:36 -0600 Subject: [PATCH 058/105] [skip ci] Torrent: migrate to kotlinx serialization (#2882) --- .../cloudstream3/ui/player/Torrent.kt | 163 +++++++----------- 1 file changed, 61 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt index 2e554f75e..7e5d04eb0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import torrServer.TorrServer import java.io.File import java.net.ConnectException @@ -32,14 +34,14 @@ object Torrent { /** Returns true if the server is up */ private suspend fun echo(): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { app.get( "$TORRENT_SERVER_URL/echo", ).text.isNotEmpty() - } catch (e: ConnectException) { + } catch (_: ConnectException) { // `Failed to connect to /127.0.0.1:8090` if the server is down false } catch (t: Throwable) { @@ -52,7 +54,7 @@ object Torrent { /** Gracefully shutdown the server. * should not be used because I am unable to start it again, and the stopTorrentServer() crashes the app */ suspend fun shutdown(): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { @@ -68,7 +70,7 @@ object Torrent { /** Lists all torrents by the server */ @Throws private suspend fun list(): Array { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { throw ErrorLoadingException("Not initialized") } return app.post( @@ -83,7 +85,7 @@ object Torrent { /** Drops a single torrent, (I think) this means closing the stream. Returns returns if it is successful */ private suspend fun drop(hash: String): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { @@ -104,7 +106,7 @@ object Torrent { /** Removes a single torrent from the server registry */ private suspend fun rem(hash: String): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return false } return try { @@ -126,7 +128,7 @@ object Torrent { /** Removes all torrents from the server, and returns if it is successful */ suspend fun clearAll(): Boolean { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { return true } return try { @@ -164,10 +166,8 @@ object Torrent { /** Gets all the metadata of a torrent, will throw if that hash does not exists * https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/web/api/torrents.go#L126 */ @Throws - suspend fun get( - hash: String, - ): TorrentStatus { - if(TORRENT_SERVER_URL.isEmpty()) { + suspend fun get(hash: String): TorrentStatus { + if (TORRENT_SERVER_URL.isEmpty()) { throw ErrorLoadingException("Not initialized") } return app.post( @@ -184,7 +184,7 @@ object Torrent { /** Adds a torrent to the server, this is needed for us to get the hash for further modification, as well as start streaming it*/ @Throws private suspend fun add(url: String): TorrentStatus { - if(TORRENT_SERVER_URL.isEmpty()) { + if (TORRENT_SERVER_URL.isEmpty()) { throw ErrorLoadingException("Not initialized") } return app.post( @@ -204,7 +204,7 @@ object Torrent { return true } val port = TorrServer.startTorrentServer(dir, 0) - if(port < 0) { + if (port < 0) { return false } TORRENT_SERVER_URL = "http://127.0.0.1:$port" @@ -278,94 +278,55 @@ object Torrent { // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/web/api/torrents.go#L18 // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/main/web/api/route.go#L7 + @Serializable data class TorrentRequest( - @JsonProperty("action") - val action: String, - @JsonProperty("hash") - val hash: String = "", - @JsonProperty("link") - val link: String = "", - @JsonProperty("title") - val title: String = "", - @JsonProperty("poster") - val poster: String = "", - @JsonProperty("data") - val data: String = "", - @JsonProperty("save_to_db") - val saveToDB: Boolean = false, + @JsonProperty("action") @SerialName("action") val action: String, + @JsonProperty("hash") @SerialName("hash") val hash: String = "", + @JsonProperty("link") @SerialName("link") val link: String = "", + @JsonProperty("title") @SerialName("title") val title: String = "", + @JsonProperty("poster") @SerialName("poster") val poster: String = "", + @JsonProperty("data") @SerialName("data") val data: String = "", + @JsonProperty("save_to_db") @SerialName("save_to_db") val saveToDB: Boolean = false, ) // https://github.com/Diegopyl1209/torrentserver-aniyomi/blob/c18f58e51b6738f053261bc863177078aa9c1c98/torr/state/state.go#L33 // omitempty = nullable + @Serializable data class TorrentStatus( - @JsonProperty("title") - var title: String, - @JsonProperty("poster") - var poster: String, - @JsonProperty("data") - var data: String?, - @JsonProperty("timestamp") - var timestamp: Long, - @JsonProperty("name") - var name: String?, - @JsonProperty("hash") - var hash: String?, - @JsonProperty("stat") - var stat: Int, - @JsonProperty("stat_string") - var statString: String, - @JsonProperty("loaded_size") - var loadedSize: Long?, - @JsonProperty("torrent_size") - var torrentSize: Long?, - @JsonProperty("preloaded_bytes") - var preloadedBytes: Long?, - @JsonProperty("preload_size") - var preloadSize: Long?, - @JsonProperty("download_speed") - var downloadSpeed: Double?, - @JsonProperty("upload_speed") - var uploadSpeed: Double?, - @JsonProperty("total_peers") - var totalPeers: Int?, - @JsonProperty("pending_peers") - var pendingPeers: Int?, - @JsonProperty("active_peers") - var activePeers: Int?, - @JsonProperty("connected_seeders") - var connectedSeeders: Int?, - @JsonProperty("half_open_peers") - var halfOpenPeers: Int?, - @JsonProperty("bytes_written") - var bytesWritten: Long?, - @JsonProperty("bytes_written_data") - var bytesWrittenData: Long?, - @JsonProperty("bytes_read") - var bytesRead: Long?, - @JsonProperty("bytes_read_data") - var bytesReadData: Long?, - @JsonProperty("bytes_read_useful_data") - var bytesReadUsefulData: Long?, - @JsonProperty("chunks_written") - var chunksWritten: Long?, - @JsonProperty("chunks_read") - var chunksRead: Long?, - @JsonProperty("chunks_read_useful") - var chunksReadUseful: Long?, - @JsonProperty("chunks_read_wasted") - var chunksReadWasted: Long?, - @JsonProperty("pieces_dirtied_good") - var piecesDirtiedGood: Long?, - @JsonProperty("pieces_dirtied_bad") - var piecesDirtiedBad: Long?, - @JsonProperty("duration_seconds") - var durationSeconds: Double?, - @JsonProperty("bit_rate") - var bitRate: String?, - @JsonProperty("file_stats") - var fileStats: List?, - @JsonProperty("trackers") - var trackers: List?, + @JsonProperty("title") @SerialName("title") var title: String, + @JsonProperty("poster") @SerialName("poster") var poster: String, + @JsonProperty("data") @SerialName("data") var data: String?, + @JsonProperty("timestamp") @SerialName("timestamp") var timestamp: Long, + @JsonProperty("name") @SerialName("name") var name: String?, + @JsonProperty("hash") @SerialName("hash") var hash: String?, + @JsonProperty("stat") @SerialName("stat") var stat: Int, + @JsonProperty("stat_string") @SerialName("stat_string") var statString: String, + @JsonProperty("loaded_size") @SerialName("loaded_size") var loadedSize: Long?, + @JsonProperty("torrent_size") @SerialName("torrent_size") var torrentSize: Long?, + @JsonProperty("preloaded_bytes") @SerialName("preloaded_bytes") var preloadedBytes: Long?, + @JsonProperty("preload_size") @SerialName("preload_size") var preloadSize: Long?, + @JsonProperty("download_speed") @SerialName("download_speed") var downloadSpeed: Double?, + @JsonProperty("upload_speed") @SerialName("upload_speed") var uploadSpeed: Double?, + @JsonProperty("total_peers") @SerialName("total_peers") var totalPeers: Int?, + @JsonProperty("pending_peers") @SerialName("pending_peers") var pendingPeers: Int?, + @JsonProperty("active_peers") @SerialName("active_peers") var activePeers: Int?, + @JsonProperty("connected_seeders") @SerialName("connected_seeders") var connectedSeeders: Int?, + @JsonProperty("half_open_peers") @SerialName("half_open_peers") var halfOpenPeers: Int?, + @JsonProperty("bytes_written") @SerialName("bytes_written") var bytesWritten: Long?, + @JsonProperty("bytes_written_data") @SerialName("bytes_written_data") var bytesWrittenData: Long?, + @JsonProperty("bytes_read") @SerialName("bytes_read") var bytesRead: Long?, + @JsonProperty("bytes_read_data") @SerialName("bytes_read_data") var bytesReadData: Long?, + @JsonProperty("bytes_read_useful_data") @SerialName("bytes_read_useful_data") var bytesReadUsefulData: Long?, + @JsonProperty("chunks_written") @SerialName("chunks_written") var chunksWritten: Long?, + @JsonProperty("chunks_read") @SerialName("chunks_read") var chunksRead: Long?, + @JsonProperty("chunks_read_useful") @SerialName("chunks_read_useful") var chunksReadUseful: Long?, + @JsonProperty("chunks_read_wasted") @SerialName("chunks_read_wasted") var chunksReadWasted: Long?, + @JsonProperty("pieces_dirtied_good") @SerialName("pieces_dirtied_good") var piecesDirtiedGood: Long?, + @JsonProperty("pieces_dirtied_bad") @SerialName("pieces_dirtied_bad") var piecesDirtiedBad: Long?, + @JsonProperty("duration_seconds") @SerialName("duration_seconds") var durationSeconds: Double?, + @JsonProperty("bit_rate") @SerialName("bit_rate") var bitRate: String?, + @JsonProperty("file_stats") @SerialName("file_stats") var fileStats: List?, + @JsonProperty("trackers") @SerialName("trackers") var trackers: List?, ) { fun streamUrl(url: String): String { val fileName = @@ -381,12 +342,10 @@ object Torrent { } } + @Serializable data class TorrentFileStat( - @JsonProperty("id") - val id: Int?, - @JsonProperty("path") - val path: String?, - @JsonProperty("length") - val length: Long?, + @JsonProperty("id") @SerialName("id") val id: Int?, + @JsonProperty("path") @SerialName("path") val path: String?, + @JsonProperty("length") @SerialName("length") val length: Long?, ) -} \ No newline at end of file +} From 15632b864ac60a1eac66be4cd23e810945437c58 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:18:22 -0600 Subject: [PATCH 059/105] [skip ci] TraktProvider: migrate to kotlinx serialization (#2872) --- library/api/jvm/library.api | 225 +++++++++++++- .../metaproviders/TraktProvider.kt | 276 ++++++++++-------- 2 files changed, 366 insertions(+), 135 deletions(-) diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 025ebc391..cd2aaeea1 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -5831,6 +5831,7 @@ public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagrad } public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5847,7 +5848,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Airs$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/Long;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5868,7 +5885,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Cast;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Cast$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data$Companion; public fun ()V public fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V public synthetic fun (Lcom/lagradost/cloudstream3/TvType;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5883,7 +5916,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Data;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$Companion; public fun ()V public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5906,7 +5955,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Ids$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images$Companion; public fun ()V public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5933,7 +5998,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Images$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$Companion; public fun ()V public fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ)V public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ZLjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5994,10 +6075,26 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkDa public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$LinkData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$Companion; public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; public final fun component11 ()Ljava/lang/String; @@ -6016,7 +6113,7 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public final fun component23 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; public final fun component24 ()Ljava/lang/String; public final fun component25 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; - public final fun component26 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun component26 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; public final fun component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -6024,8 +6121,8 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public final fun component7 ()Ljava/lang/Integer; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; public fun equals (Ljava/lang/Object;)Z public final fun getAiredEpisodes ()Ljava/lang/Integer; public final fun getAirs ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Airs; @@ -6040,7 +6137,7 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; public final fun getLanguage ()Ljava/lang/String; public final fun getLanguages ()Ljava/util/List; - public final fun getMedia ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public final fun getMedia ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; public final fun getNetwork ()Ljava/lang/String; public final fun getOverview ()Ljava/lang/String; public final fun getRating ()Ljava/lang/Double; @@ -6057,7 +6154,60 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaD public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaDetails$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun component4 ()Ljava/lang/Double; + public final fun component5 ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;Ljava/lang/String;Ljava/lang/Integer;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; + public fun equals (Ljava/lang/Object;)Z + public final fun getIds ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids; + public final fun getImages ()Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images; + public final fun getRating ()Ljava/lang/Double; + public final fun getTitle ()Ljava/lang/String; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$MediaSummary$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People$Companion; public fun ()V public fun (Ljava/util/List;)V public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6070,7 +6220,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$People$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$People;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$People$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person$Companion; public fun ()V public fun (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;)V public synthetic fun (Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6087,7 +6253,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Person;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Person$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$Companion; public fun ()V public fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6124,7 +6306,23 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Season public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$Seasons$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$Companion; public fun ()V public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Ids;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$Images;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6165,6 +6363,21 @@ public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktE public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TraktProvider$TraktEpisode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt { public static final field DEBUG_EXCEPTION Ljava/lang/String; public static final field DEBUG_PRINT Ljava/lang/String; diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt index dafec4d97..6011e14ea 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt @@ -34,6 +34,10 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames open class TraktProvider : MainAPI() { override var name = "Trakt" @@ -46,7 +50,6 @@ open class TraktProvider : MainAPI() { ) private val traktApiUrl = "https://api.trakt.tv" - private val traktClientId: String = BuildConfig.TRAKT_CLIENT_ID override val mainPage = mainPageOf( @@ -58,16 +61,21 @@ open class TraktProvider : MainAPI() { override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { val apiResponse = getApi("${request.data}?extended=full,images&page=$page") - val results = parseJson>(apiResponse).map { element -> element.toSearchResponse() } + return newHomePageResponse(request.name, results) } private fun MediaDetails.toSearchResponse(): SearchResponse { - - val media = this.media ?: this + val media = this.media ?: MediaSummary( + title = this.title, + year = this.year, + ids = this.ids, + rating = this.rating, + images = this.images, + ) val mediaType = if (media.ids?.tvdb == null) TvType.Movie else TvType.TvSeries val poster = media.images?.poster?.firstOrNull() return if (mediaType == TvType.Movie) { @@ -75,7 +83,7 @@ open class TraktProvider : MainAPI() { name = media.title ?: "", url = Data( type = mediaType, - mediaDetails = media, + mediaDetails = this, ).toJson(), type = TvType.Movie, ) { @@ -87,7 +95,7 @@ open class TraktProvider : MainAPI() { name = media.title ?: "", url = Data( type = mediaType, - mediaDetails = media, + mediaDetails = this, ).toJson(), type = TvType.TvSeries, ) { @@ -98,9 +106,7 @@ open class TraktProvider : MainAPI() { } override suspend fun search(query: String, page: Int): SearchResponseList? { - val apiResponse = - getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query") - + val apiResponse = getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query") return newSearchResponseList(parseJson>(apiResponse).map { element -> element.toSearchResponse() }) @@ -115,9 +121,7 @@ open class TraktProvider : MainAPI() { val backDropUrl = fixPath(mediaDetails?.images?.fanart?.firstOrNull()) val logoUrl = fixPath(mediaDetails?.images?.logo?.firstOrNull()) - val resActor = - getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=full,images") - + val resActor = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=full,images") val actors = parseJson(resActor).cast?.map { ActorData( Actor( @@ -128,9 +132,7 @@ open class TraktProvider : MainAPI() { ) } - val resRelated = - getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=full,images&limit=20") - + val resRelated = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=full,images&limit=20") val relatedMedia = parseJson>(resRelated).map { it.toSearchResponse() } val isCartoon = @@ -142,7 +144,6 @@ open class TraktProvider : MainAPI() { val uniqueUrl = data.mediaDetails?.ids?.trakt?.toJson() ?: data.toJson() if (data.type == TvType.Movie) { - val linkData = LinkData( id = mediaDetails?.ids?.tmdb, traktId = mediaDetails?.ids?.trakt, @@ -156,7 +157,7 @@ open class TraktProvider : MainAPI() { year = mediaDetails?.year, orgTitle = mediaDetails?.title, isAnime = isAnime, - //jpTitle = later if needed as it requires another network request, + // jpTitle = later if needed as it requires another network request, airedDate = mediaDetails?.released ?: mediaDetails?.firstAired, isAsian = isAsian, @@ -190,7 +191,6 @@ open class TraktProvider : MainAPI() { addTMDbId(mediaDetails.ids?.tmdb.toString()) } } else { - val resSeasons = getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=full,images,episodes") val episodes = mutableListOf() @@ -198,9 +198,7 @@ open class TraktProvider : MainAPI() { var nextAir: NextAiring? = null seasons.forEach { season -> - season.episodes?.map { episode -> - val linkData = LinkData( id = mediaDetails?.ids?.tmdb, traktId = mediaDetails?.ids?.trakt, @@ -234,8 +232,7 @@ open class TraktProvider : MainAPI() { this.episode = episode.number this.description = episode.overview this.runTime = episode.runtime - this.posterUrl = fixPath( episode.images?.screenshot?.firstOrNull()) - //this.rating = episode.rating?.times(10)?.roundToInt() + this.posterUrl = fixPath(episode.images?.screenshot?.firstOrNull()) this.score = Score.from10(episode.rating) this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") @@ -307,143 +304,164 @@ open class TraktProvider : MainAPI() { return "https://$url" } + @Serializable data class Data( - val type: TvType? = null, - val mediaDetails: MediaDetails? = null, + @JsonProperty("type") @SerialName("type") val type: TvType? = null, + @JsonProperty("mediaDetails") @SerialName("mediaDetails") val mediaDetails: MediaDetails? = null, ) + @Serializable + data class MediaSummary( + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + ) + + @OptIn(ExperimentalSerializationApi::class) // JsonNames is an experimental annotation for now + @Serializable data class MediaDetails( - @JsonProperty("title") val title: String? = null, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("tagline") val tagline: String? = null, - @JsonProperty("overview") val overview: String? = null, - @JsonProperty("released") val released: String? = null, - @JsonProperty("runtime") val runtime: Int? = null, - @JsonProperty("country") val country: String? = null, - @JsonProperty("updatedAt") val updatedAt: String? = null, - @JsonProperty("trailer") val trailer: String? = null, - @JsonProperty("homepage") val homepage: String? = null, - @JsonProperty("status") val status: String? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("votes") val votes: Long? = null, - @JsonProperty("comment_count") val commentCount: Long? = null, - @JsonProperty("language") val language: String? = null, - @JsonProperty("languages") val languages: List? = null, - @JsonProperty("available_translations") val availableTranslations: List? = null, - @JsonProperty("genres") val genres: List? = null, - @JsonProperty("certification") val certification: String? = null, - @JsonProperty("aired_episodes") val airedEpisodes: Int? = null, - @JsonProperty("first_aired") val firstAired: String? = null, - @JsonProperty("airs") val airs: Airs? = null, - @JsonProperty("network") val network: String? = null, - @JsonProperty("images") val images: Images? = null, - @JsonProperty("movie") @JsonAlias("show") val media: MediaDetails? = null + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("tagline") @SerialName("tagline") val tagline: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("released") @SerialName("released") val released: String? = null, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, + @JsonProperty("country") @SerialName("country") val country: String? = null, + @JsonProperty("updatedAt") @SerialName("updatedAt") val updatedAt: String? = null, + @JsonProperty("trailer") @SerialName("trailer") val trailer: String? = null, + @JsonProperty("homepage") @SerialName("homepage") val homepage: String? = null, + @JsonProperty("status") @SerialName("status") val status: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Long? = null, + @JsonProperty("comment_count") @SerialName("comment_count") val commentCount: Long? = null, + @JsonProperty("language") @SerialName("language") val language: String? = null, + @JsonProperty("languages") @SerialName("languages") val languages: List? = null, + @JsonProperty("available_translations") @SerialName("available_translations") val availableTranslations: List? = null, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("certification") @SerialName("certification") val certification: String? = null, + @JsonProperty("aired_episodes") @SerialName("aired_episodes") val airedEpisodes: Int? = null, + @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null, + @JsonProperty("airs") @SerialName("airs") val airs: Airs? = null, + @JsonProperty("network") @SerialName("network") val network: String? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + @JsonProperty("media") @JsonAlias("movie", "show") @SerialName("media") @JsonNames("movie", "show") val media: MediaSummary? = null, ) + @Serializable data class Airs( - @JsonProperty("day") val day: String? = null, - @JsonProperty("time") val time: String? = null, - @JsonProperty("timezone") val timezone: String? = null, + @JsonProperty("day") @SerialName("day") val day: String? = null, + @JsonProperty("time") @SerialName("time") val time: String? = null, + @JsonProperty("timezone") @SerialName("timezone") val timezone: String? = null, ) + @Serializable data class Ids( - @JsonProperty("trakt") val trakt: Int? = null, - @JsonProperty("slug") val slug: String? = null, - @JsonProperty("tvdb") val tvdb: Int? = null, - @JsonProperty("imdb") val imdb: String? = null, - @JsonProperty("tmdb") val tmdb: Int? = null, - @JsonProperty("tvrage") val tvrage: String? = null, + @JsonProperty("trakt") @SerialName("trakt") val trakt: Int? = null, + @JsonProperty("slug") @SerialName("slug") val slug: String? = null, + @JsonProperty("tvdb") @SerialName("tvdb") val tvdb: Int? = null, + @JsonProperty("imdb") @SerialName("imdb") val imdb: String? = null, + @JsonProperty("tmdb") @SerialName("tmdb") val tmdb: Int? = null, + @JsonProperty("tvrage") @SerialName("tvrage") val tvrage: String? = null, ) + @Serializable data class Images( - @JsonProperty("poster") val poster: List? = null, - @JsonProperty("fanart") val fanart: List? = null, - @JsonProperty("logo") val logo: List? = null, - @JsonProperty("clearart") val clearArt: List? = null, - @JsonProperty("banner") val banner: List? = null, - @JsonProperty("thumb") val thumb: List? = null, - @JsonProperty("screenshot") val screenshot: List? = null, - @JsonProperty("headshot") val headshot: List? = null, + @JsonProperty("poster") @SerialName("poster") val poster: List? = null, + @JsonProperty("fanart") @SerialName("fanart") val fanart: List? = null, + @JsonProperty("logo") @SerialName("logo") val logo: List? = null, + @JsonProperty("clearart") @SerialName("clearart") val clearArt: List? = null, + @JsonProperty("banner") @SerialName("banner") val banner: List? = null, + @JsonProperty("thumb") @SerialName("thumb") val thumb: List? = null, + @JsonProperty("screenshot") @SerialName("screenshot") val screenshot: List? = null, + @JsonProperty("headshot") @SerialName("headshot") val headshot: List? = null, ) + @Serializable data class People( - @JsonProperty("cast") val cast: List? = null, + @JsonProperty("cast") @SerialName("cast") val cast: List? = null, ) + @Serializable data class Cast( - @JsonProperty("character") val character: String? = null, - @JsonProperty("characters") val characters: List? = null, - @JsonProperty("episode_count") val episodeCount: Long? = null, - @JsonProperty("person") val person: Person? = null, - @JsonProperty("images") val images: Images? = null, + @JsonProperty("character") @SerialName("character") val character: String? = null, + @JsonProperty("characters") @SerialName("characters") val characters: List? = null, + @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Long? = null, + @JsonProperty("person") @SerialName("person") val person: Person? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, ) + @Serializable data class Person( - @JsonProperty("name") val name: String? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("images") val images: Images? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, ) + @Serializable data class Seasons( - @JsonProperty("aired_episodes") val airedEpisodes: Int? = null, - @JsonProperty("episode_count") val episodeCount: Int? = null, - @JsonProperty("episodes") val episodes: List? = null, - @JsonProperty("first_aired") val firstAired: String? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("images") val images: Images? = null, - @JsonProperty("network") val network: String? = null, - @JsonProperty("number") val number: Int? = null, - @JsonProperty("overview") val overview: String? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("updated_at") val updatedAt: String? = null, - @JsonProperty("votes") val votes: Int? = null, + @JsonProperty("aired_episodes") @SerialName("aired_episodes") val airedEpisodes: Int? = null, + @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Int? = null, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, + @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + @JsonProperty("network") @SerialName("network") val network: String? = null, + @JsonProperty("number") @SerialName("number") val number: Int? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: String? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Int? = null, ) + @Serializable data class TraktEpisode( - @JsonProperty("available_translations") val availableTranslations: List? = null, - @JsonProperty("comment_count") val commentCount: Int? = null, - @JsonProperty("episode_type") val episodeType: String? = null, - @JsonProperty("first_aired") val firstAired: String? = null, - @JsonProperty("ids") val ids: Ids? = null, - @JsonProperty("images") val images: Images? = null, - @JsonProperty("number") val number: Int? = null, - @JsonProperty("number_abs") val numberAbs: Int? = null, - @JsonProperty("overview") val overview: String? = null, - @JsonProperty("rating") val rating: Double? = null, - @JsonProperty("runtime") val runtime: Int? = null, - @JsonProperty("season") val season: Int? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("updated_at") val updatedAt: String? = null, - @JsonProperty("votes") val votes: Int? = null, + @JsonProperty("available_translations") @SerialName("available_translations") val availableTranslations: List? = null, + @JsonProperty("comment_count") @SerialName("comment_count") val commentCount: Int? = null, + @JsonProperty("episode_type") @SerialName("episode_type") val episodeType: String? = null, + @JsonProperty("first_aired") @SerialName("first_aired") val firstAired: String? = null, + @JsonProperty("ids") @SerialName("ids") val ids: Ids? = null, + @JsonProperty("images") @SerialName("images") val images: Images? = null, + @JsonProperty("number") @SerialName("number") val number: Int? = null, + @JsonProperty("number_abs") @SerialName("number_abs") val numberAbs: Int? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: Double? = null, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, + @JsonProperty("season") @SerialName("season") val season: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("updated_at") @SerialName("updated_at") val updatedAt: String? = null, + @JsonProperty("votes") @SerialName("votes") val votes: Int? = null, ) + @Serializable data class LinkData( - @JsonProperty("id") val id: Int? = null, - @JsonProperty("trakt_id") val traktId: Int? = null, - @JsonProperty("trakt_slug") val traktSlug: String? = null, - @JsonProperty("tmdb_id") val tmdbId: Int? = null, - @JsonProperty("imdb_id") val imdbId: String? = null, - @JsonProperty("tvdb_id") val tvdbId: Int? = null, - @JsonProperty("tvrage_id") val tvrageId: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("season") val season: Int? = null, - @JsonProperty("episode") val episode: Int? = null, - @JsonProperty("ani_id") val aniId: String? = null, - @JsonProperty("anime_id") val animeId: String? = null, - @JsonProperty("title") val title: String? = null, - @JsonProperty("year") val year: Int? = null, - @JsonProperty("org_title") val orgTitle: String? = null, - @JsonProperty("is_anime") val isAnime: Boolean = false, - @JsonProperty("aired_year") val airedYear: Int? = null, - @JsonProperty("last_season") val lastSeason: Int? = null, - @JsonProperty("eps_title") val epsTitle: String? = null, - @JsonProperty("jp_title") val jpTitle: String? = null, - @JsonProperty("date") val date: String? = null, - @JsonProperty("aired_date") val airedDate: String? = null, - @JsonProperty("is_asian") val isAsian: Boolean = false, - @JsonProperty("is_bollywood") val isBollywood: Boolean = false, - @JsonProperty("is_cartoon") val isCartoon: Boolean = false, + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("trakt_id") @SerialName("trakt_id") val traktId: Int? = null, + @JsonProperty("trakt_slug") @SerialName("trakt_slug") val traktSlug: String? = null, + @JsonProperty("tmdb_id") @SerialName("tmdb_id") val tmdbId: Int? = null, + @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, + @JsonProperty("tvdb_id") @SerialName("tvdb_id") val tvdbId: Int? = null, + @JsonProperty("tvrage_id") @SerialName("tvrage_id") val tvrageId: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("season") @SerialName("season") val season: Int? = null, + @JsonProperty("episode") @SerialName("episode") val episode: Int? = null, + @JsonProperty("ani_id") @SerialName("ani_id") val aniId: String? = null, + @JsonProperty("anime_id") @SerialName("anime_id") val animeId: String? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("year") @SerialName("year") val year: Int? = null, + @JsonProperty("org_title") @SerialName("org_title") val orgTitle: String? = null, + @JsonProperty("is_anime") @SerialName("is_anime") val isAnime: Boolean = false, + @JsonProperty("aired_year") @SerialName("aired_year") val airedYear: Int? = null, + @JsonProperty("last_season") @SerialName("last_season") val lastSeason: Int? = null, + @JsonProperty("eps_title") @SerialName("eps_title") val epsTitle: String? = null, + @JsonProperty("jp_title") @SerialName("jp_title") val jpTitle: String? = null, + @JsonProperty("date") @SerialName("date") val date: String? = null, + @JsonProperty("aired_date") @SerialName("aired_date") val airedDate: String? = null, + @JsonProperty("is_asian") @SerialName("is_asian") val isAsian: Boolean = false, + @JsonProperty("is_bollywood") @SerialName("is_bollywood") val isBollywood: Boolean = false, + @JsonProperty("is_cartoon") @SerialName("is_cartoon") val isCartoon: Boolean = false, ) } From 7b3c938ba72f11303ec38bdf3b12e5560285bca2 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:38:08 -0600 Subject: [PATCH 060/105] Add more custom serializers for kotlinx serialization (#2978) This adds numerous other serializers to replicate Jackson mapper configuration behavior, that some extensions will need. --- .../utils/serializers/SerializerTest.kt | 157 ----- .../utils/serializers/UriSerializerTest.kt | 41 ++ .../utils/serializers/FloatAsIntSerializer.kt | 42 ++ .../serializers/FloatAsLongSerializer.kt | 42 ++ .../serializers/JsonTransformSerializer.kt | 75 +++ .../utils/serializers/NonEmptySerializer.kt | 9 +- .../serializers/NullableStringSerializer.kt | 49 ++ .../utils/serializers/WriteOnlySerializer.kt | 6 +- .../utils/serializers/SerializersTest.kt | 602 ++++++++++++++++++ 9 files changed, 858 insertions(+), 165 deletions(-) delete mode 100644 app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt create mode 100644 app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt create mode 100644 library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt deleted file mode 100644 index 15ad532f8..000000000 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/SerializerTest.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.lagradost.cloudstream3.utils.serializers - -import android.net.Uri -import com.lagradost.cloudstream3.utils.AppUtils.parseJson -import com.lagradost.cloudstream3.utils.AppUtils.toJson -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KeepGeneratedSerializer -import kotlinx.serialization.Serializable -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test - -@OptIn(ExperimentalSerializationApi::class) -@KeepGeneratedSerializer -@Serializable(with = NonEmptyData.Serializer::class) -data class NonEmptyData( - val title: String = "", - val tags: List = emptyList(), - val meta: Map = emptyMap(), - val name: String = "hello", -) { - object Serializer : NonEmptySerializer(NonEmptyData.generatedSerializer()) -} - -@OptIn(ExperimentalSerializationApi::class) -@KeepGeneratedSerializer -@Serializable(with = WriteOnlyData.Serializer::class) -data class WriteOnlyData( - val fieldA: String = "", - val fieldB: String = "", -) { - object Serializer : WriteOnlySerializer( - WriteOnlyData.generatedSerializer(), - setOf("fieldB"), - ) -} - -@OptIn(ExperimentalSerializationApi::class) -@KeepGeneratedSerializer -@Serializable(with = MultiWriteOnly.Serializer::class) -data class MultiWriteOnly( - val fieldA: String = "", - val fieldB: String = "", - val fieldC: String = "", -) { - object Serializer : WriteOnlySerializer( - MultiWriteOnly.generatedSerializer(), - setOf("fieldB", "fieldC"), - ) -} - -@Serializable -data class UriData( - @Serializable(with = UriSerializer::class) - val uri: Uri = Uri.EMPTY, -) - -class SerializerTest { - - @Test - fun nonEmptySerializerOmitsEmptyStrings() { - val data = NonEmptyData(title = "", name = "hello") - val result = data.toJson() - assertFalse(result.contains("title")) - assertTrue(result.contains("name")) - } - - @Test - fun nonEmptySerializerOmitsEmptyLists() { - val data = NonEmptyData(tags = emptyList(), name = "hello") - val result = data.toJson() - assertFalse(result.contains("tags")) - } - - @Test - fun nonEmptySerializerOmitsEmptyMaps() { - val data = NonEmptyData(meta = emptyMap(), name = "hello") - val result = data.toJson() - assertFalse(result.contains("meta")) - } - - @Test - fun nonEmptySerializerKeepsNonEmptyFields() { - val data = NonEmptyData(title = "hello", tags = listOf("a"), meta = mapOf("k" to "v")) - val result = data.toJson() - assertTrue(result.contains("title")) - assertTrue(result.contains("tags")) - assertTrue(result.contains("meta")) - } - - @Test - fun nonEmptySerializerDoesNotAffectDeserialization() { - val input = """{"title":"hello","tags":["a"],"meta":{"k":"v"},"name":"world"}""" - val result = parseJson(input) - assertEquals("hello", result.title) - assertEquals(listOf("a"), result.tags) - assertEquals(mapOf("k" to "v"), result.meta) - assertEquals("world", result.name) - } - - @Test - fun writeOnlySerializerOmitsFieldOnSerialize() { - val data = WriteOnlyData(fieldA = "hello", fieldB = "secret") - val result = data.toJson() - assertTrue(result.contains("fieldA")) - assertFalse(result.contains("fieldB")) - } - - @Test - fun writeOnlySerializerDeserializesNormally() { - val input = """{"fieldA":"hello","fieldB":"secret"}""" - val result = parseJson(input) - assertEquals("hello", result.fieldA) - assertEquals("secret", result.fieldB) - } - - @Test - fun writeOnlySerializerDeserializesMissingAsDefault() { - val input = """{"fieldA":"hello"}""" - val result = parseJson(input) - assertEquals("hello", result.fieldA) - assertEquals("", result.fieldB) - } - - @Test - fun writeOnlySerializerHandlesMultipleKeys() { - val data = MultiWriteOnly(fieldA = "hello", fieldB = "secret1", fieldC = "secret2") - val result = data.toJson() - assertTrue(result.contains("fieldA")) - assertFalse(result.contains("fieldB")) - assertFalse(result.contains("fieldC")) - } - - @Test - fun uriSerializerSerializesUriToString() { - val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) - val result = data.toJson() - assertTrue(result.contains("https://example.com/path?query=1")) - } - - @Test - fun uriSerializerDeserializesStringToUri() { - val input = """{"uri":"https://example.com/path?query=1"}""" - val result = parseJson(input) - assertEquals(Uri.parse("https://example.com/path?query=1"), result.uri) - } - - @Test - fun uriSerializerRoundtripsCorrectly() { - val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) - val encoded = data.toJson() - val decoded = parseJson(encoded) - assertEquals(data.uri, decoded.uri) - } -} diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt new file mode 100644 index 000000000..3ffd37124 --- /dev/null +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/utils/serializers/UriSerializerTest.kt @@ -0,0 +1,41 @@ +package com.lagradost.cloudstream3.utils.serializers + +import android.net.Uri +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +@Serializable +data class UriData( + @Serializable(with = UriSerializer::class) + @SerialName("uri") val uri: Uri = Uri.EMPTY, +) + +class UriSerializerTest { + + @Test + fun uriSerializerSerializesUriToString() { + val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) + val result = data.toJson() + assertTrue(result.contains("https://example.com/path?query=1")) + } + + @Test + fun uriSerializerDeserializesStringToUri() { + val input = """{"uri":"https://example.com/path?query=1"}""" + val result = parseJson(input) + assertEquals(Uri.parse("https://example.com/path?query=1"), result.uri) + } + + @Test + fun uriSerializerRoundtripsCorrectly() { + val data = UriData(uri = Uri.parse("https://example.com/path?query=1")) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data.uri, decoded.uri) + } +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt new file mode 100644 index 000000000..12c8f6ed0 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsIntSerializer.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.intOrNull + +/** + * Replicates Jackson's ACCEPT_FLOAT_AS_INT behaviour for Int fields. + * A floating-point JSON number is truncated to Int by dropping the + * fractional part, exactly like Jackson's default truncation behaviour. + * + * Usage: + * + * @Serializable + * data class MyData( + * @Serializable(with = FloatAsIntSerializer::class) + * @SerialName("count") val count: Int = 0, + * ) + */ +@Prerelease +object FloatAsIntSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("FloatAsInt", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): Int { + val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeInt() + val element = jsonDecoder.decodeJsonElement() + if (element !is JsonPrimitive) return 0 + return element.intOrNull ?: element.doubleOrNull?.toInt() ?: 0 + } + + override fun serialize(encoder: Encoder, value: Int) = encoder.encodeInt(value) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt new file mode 100644 index 000000000..7fc394107 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/FloatAsLongSerializer.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.longOrNull + +/** + * Replicates Jackson's ACCEPT_FLOAT_AS_INT behaviour for Long fields. + * A floating-point JSON number is truncated to Long by dropping the + * fractional part, exactly like Jackson's default truncation behaviour. + * + * Usage: + * + * @Serializable + * data class MyData( + * @Serializable(with = FloatAsLongSerializer::class) + * @SerialName("timestamp") val timestamp: Long = 0L, + * ) + */ +@Prerelease +object FloatAsLongSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("FloatAsLong", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): Long { + val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeLong() + val element = jsonDecoder.decodeJsonElement() + if (element !is JsonPrimitive) return 0L + return element.longOrNull ?: element.doubleOrNull?.toLong() ?: 0L + } + + override fun serialize(encoder: Encoder, value: Long) = encoder.encodeLong(value) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt new file mode 100644 index 000000000..6b9c25b59 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/JsonTransformSerializer.kt @@ -0,0 +1,75 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.JsonTransformingSerializer + +/** + * Composable deserialize transformer that applies multiple Jackson-equivalent + * behaviours to specific fields by key. Pass only the sets you need; all + * default to empty (no-op). + * + * Behaviours (applied in order per field): + * singleValueAsListKeys ACCEPT_SINGLE_VALUE_AS_ARRAY + * emptyStringAsNullKeys ACCEPT_EMPTY_STRING_AS_NULL_OBJECT + * emptyArrayAsNullKeys ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT + * + * Usage: + * + * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + * @KeepGeneratedSerializer + * @Serializable(with = MyData.Serializer::class) + * data class MyData( + * @SerialName("title") val title: String? = null, + * @SerialName("tags") val tags: List? = null, + * @SerialName("meta") val meta: MyMeta? = null, + * ) { + * object Serializer : JsonTransformSerializer( + * MyData.generatedSerializer(), + * singleValueAsListKeys = setOf("tags"), + * emptyStringAsNullKeys = setOf("title", "tags"), + * emptyArrayAsNullKeys = setOf("tags", "meta"), + * ) + * } + */ +@Prerelease +abstract class JsonTransformSerializer( + tSerializer: KSerializer, + private val singleValueAsListKeys: Set = emptySet(), + private val emptyArrayAsNullKeys: Set = emptySet(), + private val emptyStringAsNullKeys: Set = emptySet(), +) : JsonTransformingSerializer(tSerializer) { + + override fun transformDeserialize(element: JsonElement): JsonElement { + if (element !is JsonObject) return element + return JsonObject(element.mapValues { (key, value) -> + var result = value + if (key in singleValueAsListKeys) { + result = when (result) { + is JsonArray -> result + JsonNull -> JsonArray(emptyList()) + else -> JsonArray(listOf(result)) + } + } + + if (key in emptyStringAsNullKeys) { + if (result is JsonPrimitive && result.isString && result.content.isEmpty()) { + result = JsonNull + } + } + + if (key in emptyArrayAsNullKeys) { + if (result is JsonArray && result.isEmpty()) { + result = JsonNull + } + } + + result + }) + } +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt index 62b306e1f..8bc8f1734 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer.kt @@ -17,13 +17,13 @@ import kotlinx.serialization.json.JsonTransformingSerializer * * Usage: * - * @OptIn(ExperimentalSerializationApi::class) + * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now * @KeepGeneratedSerializer * @Serializable(with = MyData.Serializer::class) * data class MyData( - * val tags: List = emptyList(), - * val title: String = "", - * val meta: Map = emptyMap(), + * @SerialName("tags") val tags: List = emptyList(), + * @SerialName("title") val title: String = "", + * @SerialName("meta") val meta: Map = emptyMap(), * ) { * object Serializer : NonEmptySerializer(MyData.generatedSerializer()) * } @@ -33,7 +33,6 @@ abstract class NonEmptySerializer(tSerializer: KSerializer) : override fun transformSerialize(element: JsonElement): JsonElement { if (element !is JsonObject) return element - return JsonObject(element.filterValues { value -> when (value) { is JsonPrimitive -> value.content.isNotEmpty() diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt new file mode 100644 index 000000000..6755304a5 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/NullableStringSerializer.kt @@ -0,0 +1,49 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.Prerelease +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.decodeFromJsonElement + +/** + * Convenience serializer for nullable String fields that combines + * ACCEPT_EMPTY_STRING_AS_NULL_OBJECT with null passthrough. + * An empty JSON string (`""`) or JSON null is decoded as null. + * + * Usage: + * + * @Serializable + * data class MyData( + * @Serializable(with = NullableStringSerializer::class) + * @SerialName("title") val title: String? = null, + * ) + */ +@Prerelease +object NullableStringSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("NullableString", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): String? { + val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeString() + return when (val element = jsonDecoder.decodeJsonElement()) { + is JsonPrimitive -> element.content.ifEmpty { null } + JsonNull -> null + else -> jsonDecoder.json.decodeFromJsonElement(String.serializer(), element) + } + } + + override fun serialize(encoder: Encoder, value: String?) { + @OptIn(ExperimentalSerializationApi::class) // encodeNull is experimental for now + if (value == null) encoder.encodeNull() else encoder.encodeString(value) + } +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt index 1d06b7fca..1b6ca9aee 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/serializers/WriteOnlySerializer.kt @@ -12,12 +12,12 @@ import kotlinx.serialization.json.JsonTransformingSerializer * * Usage: * - * @OptIn(ExperimentalSerializationApi::class) + * @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now * @KeepGeneratedSerializer * @Serializable(with = MyData.Serializer::class) * data class MyData( - * val fieldA: String = "", - * val fieldB: String = "", + * @SerialName("fieldA") val fieldA: String = "", + * @SerialName("fieldB") val fieldB: String = "", * ) { * object Serializer : WriteOnlySerializer( * MyData.generatedSerializer(), diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt new file mode 100644 index 000000000..89c0fea81 --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/serializers/SerializersTest.kt @@ -0,0 +1,602 @@ +package com.lagradost.cloudstream3.utils.serializers + +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KeepGeneratedSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = NonEmptyData.Serializer::class) +data class NonEmptyData( + @SerialName("title") val title: String = "", + @SerialName("tags") val tags: List = emptyList(), + @SerialName("meta") val meta: Map = emptyMap(), + @SerialName("name") val name: String = "hello", +) { + object Serializer : NonEmptySerializer(NonEmptyData.generatedSerializer()) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = WriteOnlyData.Serializer::class) +data class WriteOnlyData( + @SerialName("fieldA") val fieldA: String = "", + @SerialName("fieldB") val fieldB: String = "", +) { + object Serializer : WriteOnlySerializer( + WriteOnlyData.generatedSerializer(), + setOf("fieldB"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = MultiWriteOnly.Serializer::class) +data class MultiWriteOnly( + @SerialName("fieldA") val fieldA: String = "", + @SerialName("fieldB") val fieldB: String = "", + @SerialName("fieldC") val fieldC: String = "", +) { + object Serializer : WriteOnlySerializer( + MultiWriteOnly.generatedSerializer(), + setOf("fieldB", "fieldC"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = SingleValueData.Serializer::class) +data class SingleValueData( + @SerialName("tags") val tags: List = emptyList(), + @SerialName("nums") val nums: List = emptyList(), +) { + object Serializer : JsonTransformSerializer( + SingleValueData.generatedSerializer(), + singleValueAsListKeys = setOf("tags", "nums"), + ) +} + +@Serializable +data class NestedMeta( + @SerialName("key") val key: String = "", +) + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = EmptyArrayData.Serializer::class) +data class EmptyArrayData( + @SerialName("meta") val meta: NestedMeta? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + EmptyArrayData.generatedSerializer(), + emptyArrayAsNullKeys = setOf("meta"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = EmptyStringData.Serializer::class) +data class EmptyStringData( + @SerialName("title") val title: String? = null, + @SerialName("episode") val episode: NestedMeta? = null, + @SerialName("keep") val keep: String? = "hello", +) { + object Serializer : JsonTransformSerializer( + EmptyStringData.generatedSerializer(), + emptyStringAsNullKeys = setOf("title", "episode", "keep"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = SingleValueOrNullData.Serializer::class) +data class SingleValueOrNullData( + @SerialName("tags") val tags: List? = null, + @SerialName("nums") val nums: List? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + SingleValueOrNullData.generatedSerializer(), + singleValueAsListKeys = setOf("tags", "nums"), + emptyArrayAsNullKeys = setOf("tags", "nums"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = NullableFieldsData.Serializer::class) +data class NullableFieldsData( + @SerialName("title") val title: String? = null, + @SerialName("meta") val meta: NestedMeta? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + NullableFieldsData.generatedSerializer(), + emptyStringAsNullKeys = setOf("title"), + emptyArrayAsNullKeys = setOf("meta"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = SingleValueOrEmptyStringData.Serializer::class) +data class SingleValueOrEmptyStringData( + @SerialName("tags") val tags: List = emptyList(), + @SerialName("title") val title: String? = null, +) { + object Serializer : JsonTransformSerializer( + SingleValueOrEmptyStringData.generatedSerializer(), + singleValueAsListKeys = setOf("tags"), + emptyStringAsNullKeys = setOf("title"), + ) +} + +@OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now +@KeepGeneratedSerializer +@Serializable(with = AllTransformsData.Serializer::class) +data class AllTransformsData( + @SerialName("tags") val tags: List? = null, + @SerialName("title") val title: String? = null, + @SerialName("meta") val meta: NestedMeta? = null, + @SerialName("other") val other: String = "hello", +) { + object Serializer : JsonTransformSerializer( + AllTransformsData.generatedSerializer(), + singleValueAsListKeys = setOf("tags"), + emptyArrayAsNullKeys = setOf("tags", "meta"), + emptyStringAsNullKeys = setOf("title", "tags"), + ) +} + +@Serializable +data class FloatIntData( + @Serializable(with = FloatAsIntSerializer::class) + @SerialName("count") val count: Int = 0, +) + +@Serializable +data class FloatLongData( + @Serializable(with = FloatAsLongSerializer::class) + @SerialName("timestamp") val timestamp: Long = 0L, +) + +@Serializable +data class NullableStringData( + @Serializable(with = NullableStringSerializer::class) + @SerialName("title") val title: String? = null, +) + +class SerializersTest { + + @Test + fun nonEmptySerializerOmitsEmptyStrings() { + val data = NonEmptyData(title = "", name = "hello") + val result = data.toJson() + assertFalse(result.contains("title")) + assertTrue(result.contains("name")) + } + + @Test + fun nonEmptySerializerOmitsEmptyLists() { + val data = NonEmptyData(tags = emptyList(), name = "hello") + val result = data.toJson() + assertFalse(result.contains("tags")) + } + + @Test + fun nonEmptySerializerOmitsEmptyMaps() { + val data = NonEmptyData(meta = emptyMap(), name = "hello") + val result = data.toJson() + assertFalse(result.contains("meta")) + } + + @Test + fun nonEmptySerializerKeepsNonEmptyFields() { + val data = NonEmptyData(title = "hello", tags = listOf("a"), meta = mapOf("k" to "v")) + val result = data.toJson() + assertTrue(result.contains("title")) + assertTrue(result.contains("tags")) + assertTrue(result.contains("meta")) + } + + @Test + fun nonEmptySerializerDoesNotAffectDeserialization() { + val input = """{"title":"hello","tags":["a"],"meta":{"k":"v"},"name":"world"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + assertEquals(listOf("a"), result.tags) + assertEquals(mapOf("k" to "v"), result.meta) + assertEquals("world", result.name) + } + + @Test + fun writeOnlySerializerOmitsFieldOnSerialize() { + val data = WriteOnlyData(fieldA = "hello", fieldB = "secret") + val result = data.toJson() + assertTrue(result.contains("fieldA")) + assertFalse(result.contains("fieldB")) + } + + @Test + fun writeOnlySerializerDeserializesNormally() { + val input = """{"fieldA":"hello","fieldB":"secret"}""" + val result = parseJson(input) + assertEquals("hello", result.fieldA) + assertEquals("secret", result.fieldB) + } + + @Test + fun writeOnlySerializerDeserializesMissingAsDefault() { + val input = """{"fieldA":"hello"}""" + val result = parseJson(input) + assertEquals("hello", result.fieldA) + assertEquals("", result.fieldB) + } + + @Test + fun writeOnlySerializerHandlesMultipleKeys() { + val data = MultiWriteOnly(fieldA = "hello", fieldB = "secret1", fieldC = "secret2") + val result = data.toJson() + assertTrue(result.contains("fieldA")) + assertFalse(result.contains("fieldB")) + assertFalse(result.contains("fieldC")) + } + + @Test + fun singleValueAsListDecodesArrayNormally() { + val input = """{"tags":["a","b"],"nums":[1,2]}""" + val result = parseJson(input) + assertEquals(listOf("a", "b"), result.tags) + assertEquals(listOf(1, 2), result.nums) + } + + @Test + fun singleValueAsListWrapsBareStringInList() { + val input = """{"tags":"a","nums":[1]}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + } + + @Test + fun singleValueAsListWrapsBareIntInList() { + val input = """{"tags":[],"nums":42}""" + val result = parseJson(input) + assertEquals(listOf(42), result.nums) + } + + @Test + fun singleValueAsListDecodesNullAsEmptyList() { + val input = """{"tags":null,"nums":[]}""" + val result = parseJson(input) + assertEquals(emptyList(), result.tags) + } + + @Test + fun singleValueAsListRoundtripsCorrectly() { + val data = SingleValueData(tags = listOf("x", "y"), nums = listOf(1, 2)) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun emptyArrayAsNullDecodesEmptyArrayAsNull() { + val input = """{"meta":[],"other":"hello"}""" + val result = parseJson(input) + assertNull(result.meta) + } + + @Test + fun emptyArrayAsNullDecodesNullAsNull() { + val input = """{"meta":null,"other":"hello"}""" + val result = parseJson(input) + assertNull(result.meta) + } + + @Test + fun emptyArrayAsNullDecodesObjectNormally() { + val input = """{"meta":{"key":"value"},"other":"hello"}""" + val result = parseJson(input) + assertEquals(NestedMeta("value"), result.meta) + } + + @Test + fun emptyArrayAsNullDoesNotAffectOtherFields() { + val input = """{"meta":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun emptyArrayAsNullRoundtripsCorrectly() { + val data = EmptyArrayData(meta = NestedMeta("test"), other = "hello") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun emptyStringAsNullDecodesEmptyStringAsNull() { + val input = """{"title":"","episode":null,"keep":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun emptyStringAsNullDecodesNullAsNull() { + val input = """{"title":null,"episode":null,"keep":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun emptyStringAsNullKeepsNonEmptyString() { + val input = """{"title":"hello","episode":null,"keep":"world"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + } + + @Test + fun emptyStringAsNullDecodesObjectNormally() { + val input = """{"title":null,"episode":{"key":"value"},"keep":"hello"}""" + val result = parseJson(input) + assertEquals(NestedMeta("value"), result.episode) + } + + @Test + fun emptyStringAsNullDoesNotAffectNonEmptyFields() { + val input = """{"title":"","episode":null,"keep":"world"}""" + val result = parseJson(input) + assertEquals("world", result.keep) + } + + @Test + fun emptyStringAsNullRoundtripsCorrectly() { + val data = EmptyStringData(title = "hello", episode = NestedMeta("x"), keep = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun singleValueOrNullWrapsBareValueInList() { + val input = """{"tags":"a","nums":1,"other":"hello"}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + assertEquals(listOf(1), result.nums) + } + + @Test + fun singleValueOrNullTreatsEmptyArrayAsNull() { + val input = """{"tags":[],"nums":[],"other":"hello"}""" + val result = parseJson(input) + assertNull(result.tags) + assertNull(result.nums) + } + + @Test + fun singleValueOrNullDecodesArrayNormally() { + val input = """{"tags":["a","b"],"nums":[1,2],"other":"hello"}""" + val result = parseJson(input) + assertEquals(listOf("a", "b"), result.tags) + assertEquals(listOf(1, 2), result.nums) + } + + @Test + fun singleValueOrNullDoesNotAffectOtherFields() { + val input = """{"tags":[],"nums":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun nullableFieldsEmptyStringBecomesNull() { + val input = """{"title":"","meta":{"key":"value"},"other":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + assertEquals(NestedMeta("value"), result.meta) + } + + @Test + fun nullableFieldsEmptyArrayBecomesNull() { + val input = """{"title":"hello","meta":[],"other":"hello"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + assertNull(result.meta) + } + + @Test + fun nullableFieldsBothNullAtOnce() { + val input = """{"title":"","meta":[],"other":"hello"}""" + val result = parseJson(input) + assertNull(result.title) + assertNull(result.meta) + } + + @Test + fun nullableFieldsDoesNotAffectOtherFields() { + val input = """{"title":"","meta":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun nullableFieldsRoundtripsCorrectly() { + val data = NullableFieldsData(title = "hello", meta = NestedMeta("x"), other = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun singleValueOrEmptyStringWrapsBareString() { + val input = """{"tags":"a","title":"hello"}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + assertEquals("hello", result.title) + } + + @Test + fun singleValueOrEmptyStringTurnsEmptyTitleToNull() { + val input = """{"tags":["a"],"title":""}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + assertNull(result.title) + } + + @Test + fun singleValueOrEmptyStringRoundtripsCorrectly() { + val data = SingleValueOrEmptyStringData(tags = listOf("x"), title = "hello") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun allTransformsWrapsBareStringInList() { + val input = """{"tags":"a","title":"hello","meta":{"key":"value"},"other":"world"}""" + val result = parseJson(input) + assertEquals(listOf("a"), result.tags) + } + + @Test + fun allTransformsEmptyArrayTagsBecomesNull() { + val input = """{"tags":[],"title":"hello","meta":{"key":"value"},"other":"world"}""" + val result = parseJson(input) + assertNull(result.tags) + } + + @Test + fun allTransformsEmptyMetaBecomesNull() { + val input = """{"tags":["a"],"title":"hello","meta":[],"other":"world"}""" + val result = parseJson(input) + assertNull(result.meta) + } + + @Test + fun allTransformsEmptyTitleBecomesNull() { + val input = """{"tags":["a"],"title":"","meta":{"key":"value"},"other":"world"}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun allTransformsAllNullAtOnce() { + val input = """{"tags":[],"title":"","meta":[],"other":"world"}""" + val result = parseJson(input) + assertNull(result.tags) + assertNull(result.title) + assertNull(result.meta) + } + + @Test + fun allTransformsDoesNotAffectOtherFields() { + val input = """{"tags":[],"title":"","meta":[],"other":"world"}""" + val result = parseJson(input) + assertEquals("world", result.other) + } + + @Test + fun allTransformsRoundtripsCorrectly() { + val data = AllTransformsData(tags = listOf("a"), title = "hello", meta = NestedMeta("x"), other = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun floatAsIntTruncatesFloat() { + val input = """{"count":3.9}""" + val result = parseJson(input) + assertEquals(3, result.count) + } + + @Test + fun floatAsIntDecodesIntNormally() { + val input = """{"count":42}""" + val result = parseJson(input) + assertEquals(42, result.count) + } + + @Test + fun floatAsIntHandlesNegativeFloat() { + val input = """{"count":-2.7}""" + val result = parseJson(input) + assertEquals(-2, result.count) + } + + @Test + fun floatAsIntRoundtripsCorrectly() { + val data = FloatIntData(count = 7) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun floatAsLongTruncatesFloat() { + val input = """{"timestamp":1234567890.9}""" + val result = parseJson(input) + assertEquals(1234567890L, result.timestamp) + } + + @Test + fun floatAsLongDecodesLongNormally() { + val input = """{"timestamp":9999999999}""" + val result = parseJson(input) + assertEquals(9999999999L, result.timestamp) + } + + @Test + fun floatAsLongHandlesNegativeFloat() { + val input = """{"timestamp":-100.6}""" + val result = parseJson(input) + assertEquals(-100L, result.timestamp) + } + + @Test + fun floatAsLongRoundtripsCorrectly() { + val data = FloatLongData(timestamp = 1700000000L) + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } + + @Test + fun nullableStringDecodesEmptyStringAsNull() { + val input = """{"title":""}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun nullableStringDecodesNullAsNull() { + val input = """{"title":null}""" + val result = parseJson(input) + assertNull(result.title) + } + + @Test + fun nullableStringKeepsNonEmptyString() { + val input = """{"title":"hello"}""" + val result = parseJson(input) + assertEquals("hello", result.title) + } + + @Test + fun nullableStringRoundtripsCorrectly() { + val data = NullableStringData(title = "world") + val encoded = data.toJson() + val decoded = parseJson(encoded) + assertEquals(data, decoded) + } +} From 2a75b8cb08aa5b384633dd08b76d546ed0173acd Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 06:40:47 -0600 Subject: [PATCH 061/105] [skip ci] Mark UriSerializer InternalAPI (#3062) We shouldn't really be encouraging extensions to use it and use ktor Url instead which has a serializer for it built into ktor. Eventually I think we should factor out the two usages of an actual Uri in serialization and remove this serializer. --- .../lagradost/cloudstream3/utils/serializers/UriSerializer.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/serializers/UriSerializer.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/serializers/UriSerializer.kt index 7c73a6889..adceede42 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/serializers/UriSerializer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/serializers/UriSerializer.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.utils.serializers import android.net.Uri +import com.lagradost.cloudstream3.InternalAPI import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor @@ -26,6 +27,7 @@ import kotlinx.serialization.encoding.Encoder * val uri: Uri, * ) */ +@InternalAPI object UriSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING) From 105f168b2d6f31cd5a18dacfb6190de370a08eed Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 06:42:15 -0600 Subject: [PATCH 062/105] [skip ci] Add new splitUrlParameters API accepting Ktor Url or string (#2931) --- .../cloudstream3/syncproviders/AuthAPI.kt | 9 +-- .../cloudstream3/utils/AppContextUtils.kt | 26 +++---- .../com/lagradost/cloudstream3/MainAPI.kt | 54 ++++++++++++- .../cloudstream3/SplitUrlParametersTest.kt | 76 +++++++++++++++++++ 4 files changed, 141 insertions(+), 24 deletions(-) create mode 100644 library/src/commonTest/kotlin/com/lagradost/cloudstream3/SplitUrlParametersTest.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt index 60e41e8b3..c0f0e4a03 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt @@ -6,13 +6,12 @@ import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.base64Encode +import com.lagradost.cloudstream3.splitUrlParameters import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING -import com.lagradost.cloudstream3.utils.AppContextUtils.splitQuery import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonNames -import java.net.URI import java.security.SecureRandom data class AuthLoginPage( @@ -172,10 +171,8 @@ abstract class AuthAPI { get() = unixTimeMS fun splitRedirectUrl(redirectUrl: String): Map { - return splitQuery( - URI( - redirectUrl.replace(APP_STRING, "https").replace("/#", "?") - ).toURL() + return splitUrlParameters( + redirectUrl.replace(APP_STRING, "https").replace("/#", "?") ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index c0be85a3f..8d322fd04 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -90,12 +90,9 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import okhttp3.Cache import java.io.File -import java.net.URL -import java.net.URLDecoder import java.util.concurrent.Executor import java.util.concurrent.Executors - object AppContextUtils { fun RecyclerView.isRecyclerScrollable(): Boolean { val layoutManager = @@ -635,16 +632,17 @@ object AppContextUtils { } } - fun splitQuery(url: URL): Map { - val queryPairs: MutableMap = LinkedHashMap() - val query: String = url.query - val pairs = query.split("&").toTypedArray() - for (pair in pairs) { - val idx = pair.indexOf("=") - queryPairs[URLDecoder.decode(pair.substring(0, idx), "UTF-8")] = - URLDecoder.decode(pair.substring(idx + 1), "UTF-8") - } - return queryPairs + // Deprecate after next stable + /* @Deprecated( + message = "Use splitUrlParameters instead.", + replaceWith = ReplaceWith( + expression = "splitUrlParameters(url.toString())", + imports = ["com.lagradost.cloudstream3.splitUrlParameters"], + ), + level = DeprecationLevel.WARNING, + ) */ + fun splitQuery(url: java.net.URL): Map { + return com.lagradost.cloudstream3.splitUrlParameters(url.toString()) } /**| S1:E2 Hello World @@ -902,4 +900,4 @@ object AppContextUtils { } else null return currentAudioFocusRequest } -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 289e96998..3e8774db2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -761,18 +761,64 @@ fun MainAPI.fixUrl(url: String): String { } } -/** Sort the urls based on quality +/** + * Sort the urls based on quality + * * @param urls Set of [ExtractorLink] - * */ + */ fun sortUrls(urls: Set): List { return urls.sortedBy { t -> -t.quality } } -/** Capitalize the first letter of string. +/** + * Splits the parameters of a [Url] into a map of key-value pairs. + * + * Unlike a manual `split("&")` / `split("=")` implementation, this relies on Ktor's + * built-in parameters parser ([Url.parameters]), which already handles URL-decoding, + * malformed pairs, and parameters without a value. + * + * Note: if a parameter key appears multiple times (e.g. `?a=1&a=2`), only the **first** + * value is kept, since the return type is `Map`. Use [Url.parameters] + * directly if you need all values for repeated keys. + * + * @param url the [Url] whose parameters should be extracted. + * @return a map of decoded parameter names to their first decoded value. + * + * @sample + * splitUrlParameters(Url("https://example.com/path?foo=bar&baz=qux")) + * // returns {"foo": "bar", "baz": "qux"} + */ +@Prerelease +fun splitUrlParameters(url: Url): Map { + return url.parameters.entries().associate { (key, values) -> key to values.firstOrNull().orEmpty() } +} + +/** + * Splits the parameters of a raw URL [String] into a map of key-value pairs. + * + * Convenience overload for callers that have a URL as plain text rather than a parsed + * [Url] instance. Internally parses [url] with Ktor's [Url] constructor and delegates + * to [splitUrlParameters]. + * + * @param url the URL string whose parameters should be extracted. + * @return a map of decoded parameter names to their first decoded value. + * + * @sample + * splitUrlParameters("https://example.com/path?foo=bar&baz=qux") + * // returns {"foo": "bar", "baz": "qux"} + */ +@Prerelease +fun splitUrlParameters(url: String): Map { + return splitUrlParameters(Url(url)) +} + +/** + * Capitalize the first letter of string. + * * @param str String to be capitalized * @return non-nullable String * @see capitalizeStringNullable - * */ + */ fun capitalizeString(str: String): String { return capitalizeStringNullable(str) ?: str } diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/SplitUrlParametersTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/SplitUrlParametersTest.kt new file mode 100644 index 000000000..7e58c70be --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/SplitUrlParametersTest.kt @@ -0,0 +1,76 @@ +package com.lagradost.cloudstream3 + +import io.ktor.http.Url +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SplitUrlParametersTest { + + @Test + fun splitsBasicParameters() { + val url = Url("https://example.com/path?foo=bar&baz=qux") + val result = splitUrlParameters(url) + assertEquals(mapOf("foo" to "bar", "baz" to "qux"), result) + } + + @Test + fun decodesUrlEncodedKeysAndValues() { + val url = Url("https://example.com/path?na%20me=hello%20world&sp%26ec=a%2Bb") + val result = splitUrlParameters(url) + assertEquals(mapOf("na me" to "hello world", "sp&ec" to "a+b"), result) + } + + @Test + fun returnsEmptyMapWhenThereAreNoParameters() { + val url = Url("https://example.com/path") + val result = splitUrlParameters(url) + assertTrue(result.isEmpty()) + } + + @Test + fun keepsOnlyFirstValueForRepeatedKeys() { + val url = Url("https://example.com/path?a=1&a=2&a=3") + val result = splitUrlParameters(url) + assertEquals(mapOf("a" to "1"), result) + } + + @Test + fun handlesParameterWithNoValue() { + val url = Url("https://example.com/path?flag&foo=bar") + val result = splitUrlParameters(url) + assertEquals("bar", result["foo"]) + assertEquals("", result["flag"]) + } + + @Test + fun stringOverloadSplitsBasicParameters() { + val result = splitUrlParameters("https://example.com/path?foo=bar&baz=qux") + assertEquals(mapOf("foo" to "bar", "baz" to "qux"), result) + } + + @Test + fun stringOverloadDecodesUrlEncodedKeysAndValues() { + val result = splitUrlParameters("https://example.com/path?na%20me=hello%20world&sp%26ec=a%2Bb") + assertEquals(mapOf("na me" to "hello world", "sp&ec" to "a+b"), result) + } + + @Test + fun stringOverloadReturnsEmptyMapWhenThereAreNoParameters() { + val result = splitUrlParameters("https://example.com/path") + assertTrue(result.isEmpty()) + } + + @Test + fun stringOverloadKeepsOnlyFirstValueForRepeatedKeys() { + val result = splitUrlParameters("https://example.com/path?a=1&a=2&a=3") + assertEquals(mapOf("a" to "1"), result) + } + + @Test + fun stringOverloadHandlesParameterWithNoValue() { + val result = splitUrlParameters("https://example.com/path?flag&foo=bar") + assertEquals("bar", result["foo"]) + assertEquals("", result["flag"]) + } +} From 86d713c33ce29e38f9b2af5f696419af24783ce3 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 06:57:43 -0600 Subject: [PATCH 063/105] [skip ci] Improve ContextHelper API for KMP (#2834) --- library/api/jvm/library.api | 2 +- .../com/lagradost/api/ContextHelper.android.kt | 15 +++++---------- .../kotlin/com/lagradost/api/ContextHelper.kt | 11 +++++------ .../kotlin/com/lagradost/api/ContextHelper.jvm.kt | 10 ++-------- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index cd2aaeea1..f680e6086 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -6,7 +6,7 @@ public final class com/lagradost/api/BuildConfig { public final class com/lagradost/api/ContextHelper_jvmKt { public static final fun getContext ()Ljava/lang/Object; - public static final fun setContext (Ljava/lang/ref/WeakReference;)V + public static final fun setContext (Ljava/lang/Object;)V } public final class com/lagradost/api/Log { diff --git a/library/src/androidMain/kotlin/com/lagradost/api/ContextHelper.android.kt b/library/src/androidMain/kotlin/com/lagradost/api/ContextHelper.android.kt index 5e5897deb..6ace780ec 100644 --- a/library/src/androidMain/kotlin/com/lagradost/api/ContextHelper.android.kt +++ b/library/src/androidMain/kotlin/com/lagradost/api/ContextHelper.android.kt @@ -3,19 +3,14 @@ package com.lagradost.api import android.content.Context import java.lang.ref.WeakReference -var ctx: WeakReference? = null +private var contextRef: WeakReference? = null /** * Helper function for Android specific context. Not usable in JVM. * Do not use this unless absolutely necessary. */ -actual fun getContext(): Any? { - return ctx?.get() -} +actual fun getContext(): Any? = contextRef?.get() -actual fun setContext(context: WeakReference) { - val actualContext = context.get() as? Context - if (actualContext != null) { - ctx = WeakReference(actualContext) - } -} \ No newline at end of file +actual fun setContext(context: Any?) { + contextRef = (context as? Context)?.let { WeakReference(it) } +} diff --git a/library/src/commonMain/kotlin/com/lagradost/api/ContextHelper.kt b/library/src/commonMain/kotlin/com/lagradost/api/ContextHelper.kt index fb54e3ca9..451ec80e8 100644 --- a/library/src/commonMain/kotlin/com/lagradost/api/ContextHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/api/ContextHelper.kt @@ -1,16 +1,15 @@ package com.lagradost.api -import java.lang.ref.WeakReference - /** - * Set context for android specific code such as webview. - * Does nothing on JVM. + * Set context for Android-specific code such as webview. + * Does nothing on non-Android platforms. */ -expect fun setContext(context: WeakReference) +expect fun setContext(context: Any?) + /** * Helper function for Android specific context. * Do not use this unless absolutely necessary. * setContext() must be called before this is called. - * @return Context if on android, null if not. + * @return Context if on Android, null if not. */ expect fun getContext(): Any? diff --git a/library/src/jvmMain/kotlin/com/lagradost/api/ContextHelper.jvm.kt b/library/src/jvmMain/kotlin/com/lagradost/api/ContextHelper.jvm.kt index a30810b89..83cae11a5 100644 --- a/library/src/jvmMain/kotlin/com/lagradost/api/ContextHelper.jvm.kt +++ b/library/src/jvmMain/kotlin/com/lagradost/api/ContextHelper.jvm.kt @@ -1,10 +1,4 @@ package com.lagradost.api -import java.lang.ref.WeakReference - -actual fun getContext(): Any? { - return null -} - -actual fun setContext(context: WeakReference) { -} \ No newline at end of file +actual fun getContext(): Any? = null +actual fun setContext(context: Any?) = Unit From eef67fa69994e7cce5d3f8e7c4f3aedb6f5ae577 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 07:14:51 -0600 Subject: [PATCH 064/105] Replace usage of gson in extractors (#2983) --- library/api/jvm/library.api | 78 ++++++++++++--- .../cloudstream3/extractors/Dailymotion.kt | 56 ++++++----- .../cloudstream3/extractors/GDMirrorbot.kt | 99 +++++++++++-------- .../lagradost/cloudstream3/extractors/Voe.kt | 51 ++++++---- 4 files changed, 185 insertions(+), 99 deletions(-) diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index f680e6086..d0d80346a 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -1968,12 +1968,13 @@ public class com/lagradost/cloudstream3/extractors/Dailymotion : com/lagradost/c public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class com/lagradost/cloudstream3/extractors/Dailymotion$MetaData { +public final class com/lagradost/cloudstream3/extractors/Dailymotion$Metadata { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata$Companion; public fun (Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;)V public final fun component1 ()Ljava/util/Map; public final fun component2 ()Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; - public final fun copy (Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$MetaData; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Dailymotion$MetaData;Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$MetaData; + public final fun copy (Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata;Ljava/util/Map;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata; public fun equals (Ljava/lang/Object;)Z public final fun getQualities ()Ljava/util/Map; public final fun getSubtitles ()Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; @@ -1981,7 +1982,23 @@ public final class com/lagradost/cloudstream3/extractors/Dailymotion$MetaData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Dailymotion$Metadata$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Dailymotion$Metadata;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$Metadata$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Dailymotion$Quality { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -1994,7 +2011,23 @@ public final class com/lagradost/cloudstream3/extractors/Dailymotion$Quality { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Dailymotion$Quality$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Dailymotion$Quality;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$Quality$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData$Companion; public fun (Ljava/lang/String;Ljava/util/List;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/util/List; @@ -2007,7 +2040,23 @@ public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitleDat public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitleData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper$Companion; public fun (ZLjava/util/Map;)V public final fun component1 ()Z public final fun component2 ()Ljava/util/Map; @@ -2020,6 +2069,21 @@ public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWr public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWrapper$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/DatabaseGdrive : com/lagradost/cloudstream3/extractors/Gdriveplayer { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2435,8 +2499,6 @@ public class com/lagradost/cloudstream3/extractors/GDMirrorbot : com/lagradost/c public fun getName ()Ljava/lang/String; public fun getRequiresReferer ()Z public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public class com/lagradost/cloudstream3/extractors/GUpload : com/lagradost/cloudstream3/utils/ExtractorApi { @@ -3701,7 +3763,6 @@ public final class com/lagradost/cloudstream3/extractors/Simpulumlamerop : com/l public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V } public class com/lagradost/cloudstream3/extractors/Slmaxed : com/lagradost/cloudstream3/utils/ExtractorApi { @@ -4203,9 +4264,6 @@ public final class com/lagradost/cloudstream3/extractors/Techinmind : com/lagrad public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; public fun getRequiresReferer ()Z - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V - public fun setRequiresReferer (Z)V } public final class com/lagradost/cloudstream3/extractors/Tubeless : com/lagradost/cloudstream3/extractors/Voe { @@ -4296,7 +4354,6 @@ public final class com/lagradost/cloudstream3/extractors/Urochsunloath : com/lag public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V } public class com/lagradost/cloudstream3/extractors/Userload : com/lagradost/cloudstream3/utils/ExtractorApi { @@ -4743,7 +4800,6 @@ public final class com/lagradost/cloudstream3/extractors/Yipsu : com/lagradost/c public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V } public class com/lagradost/cloudstream3/extractors/YourUpload : com/lagradost/cloudstream3/utils/ExtractorApi { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt index 2b2a820fe..88b407f3b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -1,15 +1,18 @@ package com.lagradost.cloudstream3.extractors -import com.google.gson.Gson +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.newSubtitleFile +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.newExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType import io.ktor.http.Url import io.ktor.http.decodeURLPart +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class Geodailymotion : Dailymotion() { override val name = "GeoDailymotion" @@ -28,25 +31,25 @@ open class Dailymotion : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val embedUrl = getEmbedUrl(url) ?: return val id = getVideoId(embedUrl) ?: return - val metaDataUrl = "$baseUrl/player/metadata/video/$id" - - val response = app.get(metaDataUrl, referer = embedUrl).text - val gson = Gson() - val meta = gson.fromJson(response, MetaData::class.java) + val metadataUrl = "$baseUrl/player/metadata/video/$id" + val response = app.get(metadataUrl, referer = embedUrl).text + val meta = parseJson(response) meta.qualities?.get("auto")?.forEach { quality -> val videoUrl = quality.url if (!videoUrl.isNullOrEmpty() && videoUrl.contains(".m3u8")) { - callback.invoke(newExtractorLink( - name, - name, - videoUrl, - ExtractorLinkType.M3U8 - )) + callback.invoke( + newExtractorLink( + name, + name, + videoUrl, + ExtractorLinkType.M3U8, + ) + ) } } @@ -55,7 +58,7 @@ open class Dailymotion : ExtractorApi() { subtitleCallback( newSubtitleFile( subData.label, - subUrl + subUrl, ) ) } @@ -68,6 +71,7 @@ open class Dailymotion : ExtractorApi() { val videoId = url.substringAfter("video=") return "$baseUrl/embed/video/$videoId" } + return null } @@ -77,23 +81,27 @@ open class Dailymotion : ExtractorApi() { return if (id.matches(videoIdRegex)) id else null } - data class MetaData( - val qualities: Map>?, - val subtitles: SubtitlesWrapper? + @Serializable + data class Metadata( + @JsonProperty("qualities") @SerialName("qualities") val qualities: Map>?, + @JsonProperty("subtitles") @SerialName("subtitles") val subtitles: SubtitlesWrapper?, ) + @Serializable data class Quality( - val type: String?, - val url: String? + @JsonProperty("type") @SerialName("type") val type: String?, + @JsonProperty("url") @SerialName("url") val url: String?, ) + @Serializable data class SubtitlesWrapper( - val enable: Boolean, - val data: Map? + @JsonProperty("enable") @SerialName("enable") val enable: Boolean, + @JsonProperty("data") @SerialName("data") val data: Map?, ) + @Serializable data class SubtitleData( - val label: String, - val urls: List + @JsonProperty("label") @SerialName("label") val label: String, + @JsonProperty("urls") @SerialName("urls") val urls: List, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt index ba297067e..b6563fd63 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GDMirrorbot.kt @@ -1,31 +1,34 @@ package com.lagradost.cloudstream3.extractors -import com.google.gson.JsonParser +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import io.ktor.http.Url +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -class Techinmind: GDMirrorbot() { - override var name = "Techinmind Cloud AIO" - override var mainUrl = "https://stream.techinmind.space" - override var requiresReferer = true +class Techinmind : GDMirrorbot() { + override val name = "Techinmind Cloud AIO" + override val mainUrl = "https://stream.techinmind.space" + override val requiresReferer = true } open class GDMirrorbot : ExtractorApi() { - override var name = "GDMirrorbot" - override var mainUrl = "https://gdmirrorbot.nl" + override val name = "GDMirrorbot" + override val mainUrl = "https://gdmirrorbot.nl" override val requiresReferer = true override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val (sid, host) = if (!url.contains("key=")) { Pair(url.substringAfterLast("embed/"), getBaseUrl(app.get(url).url)) @@ -36,63 +39,58 @@ open class GDMirrorbot : ExtractorApi() { val idType = Regex("""idType\s*=\s*"([^"]+)"""").find(pageText)?.groupValues?.get(1) ?: "imdbid" val baseUrl = Regex("""let\s+baseUrl\s*=\s*"([^"]+)"""").find(pageText)?.groupValues?.get(1) val hostUrl = baseUrl?.let { getBaseUrl(it) } - if (finalId != null && myKey != null) { val apiUrl = if (url.contains("/tv/")) { val season = Regex("""/tv/\d+/(\d+)/""").find(url)?.groupValues?.get(1) ?: "1" val episode = Regex("""/tv/\d+/\d+/(\d+)""").find(url)?.groupValues?.get(1) ?: "1" "$mainUrl/myseriesapi?tmdbid=$finalId&season=$season&epname=$episode&key=$myKey" - } else { - "$mainUrl/mymovieapi?$idType=$finalId&key=$myKey" - } + } else "$mainUrl/mymovieapi?$idType=$finalId&key=$myKey" pageText = app.get(apiUrl).text } - val jsonElement = JsonParser.parseString(pageText) - if (!jsonElement.isJsonObject) return - val jsonObject = jsonElement.asJsonObject - + val embedData = tryParseJson(pageText) val embedId = url.substringAfterLast("/") - val sidValue = jsonObject["data"]?.asJsonArray - ?.takeIf { it.size() > 0 } - ?.get(0)?.asJsonObject - ?.get("fileslug")?.asString + val sidValue = embedData?.data?.firstOrNull()?.fileSlug ?.takeIf { it.isNotBlank() } ?: embedId - Pair(sidValue, hostUrl) } val postData = mapOf("sid" to sid) val responseText = app.post("$host/embedhelper.php", data = postData).text - val rootElement = JsonParser.parseString(responseText) - if (!rootElement.isJsonObject) return - val root = rootElement.asJsonObject + val root = tryParseJson(responseText) ?: return + val siteUrls = root.siteUrls ?: return + val siteFriendlyNames = root.siteFriendlyNames - val siteUrls = root["siteUrls"]?.asJsonObject ?: return - val siteFriendlyNames = root["siteFriendlyNames"]?.asJsonObject - - val decodedMresult = when { - root["mresult"]?.isJsonObject == true -> root["mresult"]!!.asJsonObject - root["mresult"]?.isJsonPrimitive == true -> try { - base64Decode(root["mresult"]!!.asString) - .let { JsonParser.parseString(it).asJsonObject } - } catch (e: Exception) { - Log.e("GDMirrorbot", "Failed to decode mresult: $e") - return + // mresult can arrive as a JSON object or a base64-encoded string + val mresult: Map? = run { + val raw = responseText + .substringAfter("\"mresult\":") + .trimStart() + when { + raw.startsWith("\"") -> { + // base64-encoded string + tryParseJson>( + try { base64Decode(raw.trim('"')) } catch (_: Exception) { return } + ) + } + raw.startsWith("{") -> tryParseJson>( + raw.substringBefore("\n}").substringBefore(",\n\"").let { "$it" } + .let { responseText.substringAfter("\"mresult\":").trimStart() } + ) + else -> null } - else -> return } - siteUrls.keySet().intersect(decodedMresult.keySet()).forEach { key -> - val base = siteUrls[key]?.asString?.trimEnd('/') ?: return@forEach - val path = decodedMresult[key]?.asString?.trimStart('/') ?: return@forEach + if (mresult == null) return + siteUrls.keys.intersect(mresult.keys).forEach { key -> + val base = siteUrls[key]?.trimEnd('/') ?: return@forEach + val path = mresult[key]?.trimStart('/') ?: return@forEach val fullUrl = "$base/$path" - val friendlyName = siteFriendlyNames?.get(key)?.asString ?: key - + val friendlyName = siteFriendlyNames?.get(key) ?: key try { when (friendlyName) { - "StreamHG","EarnVids" -> VidHidePro().getUrl(fullUrl, referer, subtitleCallback, callback) + "StreamHG", "EarnVids" -> VidHidePro().getUrl(fullUrl, referer, subtitleCallback, callback) "RpmShare", "UpnShare", "StreamP2p" -> VidStack().getUrl(fullUrl, referer, subtitleCallback, callback) else -> loadExtractor(fullUrl, referer ?: mainUrl, subtitleCallback, callback) } @@ -105,5 +103,20 @@ open class GDMirrorbot : ExtractorApi() { private fun getBaseUrl(url: String): String { return Url(url).let { "${it.protocol.name}://${it.host}" } } -} + @Serializable + private data class EmbedData( + @JsonProperty("data") @SerialName("data") val data: List? = null, + ) + + @Serializable + private data class FileSlug( + @JsonProperty("fileslug") @SerialName("fileslug") val fileSlug: String? = null, + ) + + @Serializable + private data class EmbedHelper( + @JsonProperty("siteUrls") @SerialName("siteUrls") val siteUrls: Map? = null, + @JsonProperty("siteFriendlyNames") @SerialName("siteFriendlyNames") val siteFriendlyNames: Map? = null, + ) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt index 67eb49c9a..b3ea4df1b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Voe.kt @@ -1,16 +1,18 @@ package com.lagradost.cloudstream3.extractors -import com.google.gson.JsonObject -import com.google.gson.JsonParser +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class Tubeless : Voe() { override val name = "Tubeless" @@ -19,12 +21,12 @@ class Tubeless : Voe() { class Simpulumlamerop : Voe() { override val name = "Simplum" - override var mainUrl = "https://simpulumlamerop.com" + override val mainUrl = "https://simpulumlamerop.com" } class Urochsunloath : Voe() { override val name = "Uroch" - override var mainUrl = "https://urochsunloath.com" + override val mainUrl = "https://urochsunloath.com" } class NathanFromSubject : Voe() { @@ -33,7 +35,7 @@ class NathanFromSubject : Voe() { class Yipsu : Voe() { override val name = "Yipsu" - override var mainUrl = "https://yip.su" + override val mainUrl = "https://yip.su" } class MetaGnathTuggers : Voe() { @@ -59,38 +61,43 @@ open class Voe : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { var res = app.get(url, referer = referer) val redirectUrl = redirectRegex.find(res.document.data())?.groupValues?.get(1) if (redirectUrl != null) { res = app.get(redirectUrl, referer = referer) } - val encodedString = res.document.selectFirst("script[type=application/json]")?.data()?.trim()?.substringAfter("[\"")?.substringBeforeLast("\"]") + + val encodedString = res.document.selectFirst("script[type=application/json]") + ?.data()?.trim() + ?.substringAfter("[\"") + ?.substringBeforeLast("\"]") + if (encodedString == null) { println("encoded string not found.") return } - val decryptedJson = decryptF7(encodedString) - val m3u8 = decryptedJson.get("source")?.asString - val mp4 = decryptedJson.get("direct_access_url")?.asString + val decryptedJson = decryptF7(encodedString) + val m3u8 = decryptedJson?.source + val mp4 = decryptedJson?.directAccessUrl if (m3u8 != null) { M3u8Helper.generateM3u8( name, m3u8, "$mainUrl/", - headers = mapOf("Origin" to "$mainUrl/") + headers = mapOf("Origin" to "$mainUrl/"), ).forEach(callback) } - if (mp4!=null) - { + + if (mp4 != null) { callback.invoke( newExtractorLink( source = "$name MP4", name = "$name MP4", url = mp4, - INFER_TYPE + INFER_TYPE, ) { this.referer = url this.quality = Qualities.Unknown.value @@ -99,20 +106,19 @@ open class Voe : ExtractorApi() { } } - private fun decryptF7(p8: String): JsonObject { + private fun decryptF7(p8: String): VoeDecrypted? { return try { val vF = rot13(p8) val vF2 = replacePatterns(vF) val vF3 = removeUnderscores(vF2) val vF4 = base64Decode(vF3) val vF5 = charShift(vF4, 3) - val vF6 = reverse(vF5) + val vF6 = vF5.reversed() val vAtob = base64Decode(vF6) - - JsonParser.parseString(vAtob).asJsonObject + parseJson(vAtob) } catch (e: Exception) { println("Decryption error: ${e.message}") - JsonObject() + null } } @@ -139,6 +145,9 @@ open class Voe : ExtractorApi() { return input.map { (it.code - shift).toChar() }.joinToString("") } - private fun reverse(input: String): String = input.reversed() - + @Serializable + private data class VoeDecrypted( + @JsonProperty("source") @SerialName("source") val source: String? = null, + @JsonProperty("direct_access_url") @SerialName("direct_access_url") val directAccessUrl: String? = null, + ) } From b1ce67dbf385d510549063a13bd4e35a6e07bb20 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 07:20:02 -0600 Subject: [PATCH 065/105] ControllerActivity: migrate to kotlinx serialization (#2958) --- .../cloudstream3/ui/ControllerActivity.kt | 149 ++++++------------ 1 file changed, 47 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index 2aadfb13c..f00fd8031 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -12,6 +12,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.ListView import androidx.appcompat.app.AlertDialog +import com.fasterxml.jackson.annotation.JsonProperty import com.google.android.gms.cast.MediaLoadOptions import com.google.android.gms.cast.MediaQueueItem import com.google.android.gms.cast.MediaSeekOptions @@ -34,35 +35,24 @@ import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment import com.lagradost.cloudstream3.utils.AppContextUtils.sortSubs +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import org.json.JSONObject -/*class SkipOpController(val view: ImageView) : UIController() { - init { - view.setImageResource(R.drawable.exo_controls_fastforward) - view.setOnClickListener { - remoteMediaClient?.let { - val options = MediaSeekOptions.Builder() - .setPosition(it.approximateStreamPosition + 85000) - it.seek(options.build()) - } - } - } -}*/ - private fun RemoteMediaClient.getItemIndex(): Int? { return try { val index = this.mediaQueue.itemIds.indexOf(this.currentItem?.itemId ?: 0) if (index < 0) null else index - } catch (e: Exception) { + } catch (_: Exception) { null } } @@ -89,48 +79,41 @@ class SkipNextEpisodeController(val view: ImageView) : UIController() { } } +@Serializable data class MetadataHolder( - val apiName: String, - val isMovie: Boolean, - val title: String?, - val poster: String?, - val currentEpisodeIndex: Int, - val episodes: List, - val currentLinks: List, - val currentSubtitles: List + @JsonProperty("apiName") @SerialName("apiName") val apiName: String, + @JsonProperty("isMovie") @SerialName("isMovie") val isMovie: Boolean, + @JsonProperty("title") @SerialName("title") val title: String?, + @JsonProperty("poster") @SerialName("poster") val poster: String?, + @JsonProperty("currentEpisodeIndex") @SerialName("currentEpisodeIndex") val currentEpisodeIndex: Int, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List, + @JsonProperty("currentLinks") @SerialName("currentLinks") val currentLinks: List, + @JsonProperty("currentSubtitles") @SerialName("currentSubtitles") val currentSubtitles: List, ) -class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : - UIController() { +class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() { init { view.setImageResource(R.drawable.ic_baseline_playlist_play_24) view.setOnClickListener { - // lateinit var dialog: AlertDialog val holder = getCurrentMetaData() - if (holder != null) { val items = holder.currentLinks if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) { - val subTracks = - remoteMediaClient?.mediaInfo?.mediaTracks?.filter { it.type == MediaTrack.TYPE_TEXT } - ?: ArrayList() + val subTracks = remoteMediaClient?.mediaInfo?.mediaTracks?.filter { it.type == MediaTrack.TYPE_TEXT } + ?: ArrayList() - val bottomSheetDialogBuilder = - AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack) + val bottomSheetDialogBuilder = AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack) bottomSheetDialogBuilder.setView(R.layout.sort_bottom_sheet) + val bottomSheetDialog = bottomSheetDialogBuilder.create() bottomSheetDialog.show() - // bottomSheetDialog.setContentView(R.layout.sort_bottom_sheet) - val providerList = - bottomSheetDialog.findViewById(R.id.sort_providers)!! - val subtitleList = - bottomSheetDialog.findViewById(R.id.sort_subtitles)!! + + val providerList = bottomSheetDialog.findViewById(R.id.sort_providers)!! + val subtitleList = bottomSheetDialog.findViewById(R.id.sort_subtitles)!! if (subTracks.isEmpty()) { - bottomSheetDialog.findViewById(R.id.sort_subtitles_holder)?.visibility = - GONE + bottomSheetDialog.findViewById(R.id.sort_subtitles_holder)?.visibility = GONE } else { - val arrayAdapter = - ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) + val arrayAdapter = ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) arrayAdapter.add(view.context.getString(R.string.no_subtitles)) arrayAdapter.addAll(subTracks.mapNotNull { it.name }) @@ -138,10 +121,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi subtitleList.adapter = arrayAdapter val currentTracks = remoteMediaClient?.mediaStatus?.activeTrackIds - - val subtitleIndex = - if (currentTracks == null) 0 else subTracks.map { it.id } - .indexOfFirst { currentTracks.contains(it) } + 1 + val subtitleIndex = if (currentTracks == null) 0 else subTracks.map { it.id } + .indexOfFirst { currentTracks.contains(it) } + 1 subtitleList.setSelection(subtitleIndex) subtitleList.setItemChecked(subtitleIndex, true) @@ -153,9 +134,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi ChromecastSubtitlesFragment.getCurrentSavedStyle().apply { val font = TextTrackStyle() font.setFontFamily(fontFamily ?: "Google Sans") - fontGenericFamily?.let { - font.fontGenericFamily = it - } + fontGenericFamily?.let { font.fontGenericFamily = it } font.windowColor = windowColor font.backgroundColor = backgroundColor @@ -172,7 +151,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi if (!it.status.isSuccess) { Log.e( "CHROMECAST", "Failed with status code:" + - it.status.statusCode + " > " + it.status.statusMessage + it.status.statusCode + " > " + it.status.statusMessage ) } } @@ -181,17 +160,15 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi } } - //https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation + // https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl ?: remoteMediaClient?.currentItem?.media?.contentId) - val sortingMethods = - items.map { "${it.name} ${Qualities.getStringByInt(it.quality)}" } - .toTypedArray() + val sortingMethods = items.map { "${it.name} ${Qualities.getStringByInt(it.quality)}" } + .toTypedArray() val sotringIndex = items.indexOfFirst { it.url == contentUrl } - val arrayAdapter = - ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) + val arrayAdapter = ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) arrayAdapter.addAll(sortingMethods.toMutableList()) providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE @@ -201,10 +178,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi providerList.setOnItemClickListener { _, _, which, _ -> val epData = holder.episodes[holder.currentEpisodeIndex] - fun loadMirror(index: Int) { if (holder.currentLinks.size <= index) return - val mediaItem = getMediaInfo( epData, holder, @@ -214,25 +189,21 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi ) val startAt = remoteMediaClient?.approximateStreamPosition ?: 0 - - //remoteMediaClient.load(mediaItem, true, startAt) try { // THIS IS VERY IMPORTANT BECAUSE WE NEVER WANT TO AUTOLOAD THE NEXT EPISODE val currentIdIndex = remoteMediaClient?.getItemIndex() - val nextId = remoteMediaClient?.mediaQueue?.itemIds?.get( currentIdIndex?.plus(1) ?: 0 ) + if (currentIdIndex == null && nextId != null) { awaitLinks( remoteMediaClient?.queueInsertAndPlayItem( MediaQueueItem.Builder(mediaItem).build(), nextId, startAt, - JSONObject() + JSONObject(), ) - ) { - loadMirror(index + 1) - } + ) { loadMirror(index + 1) } } else { val mediaLoadOptions = MediaLoadOptions.Builder() @@ -244,11 +215,9 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi mediaItem, mediaLoadOptions ) - ) { - loadMirror(index + 1) - } + ) { loadMirror(index + 1) } } - } catch (e: Exception) { + } catch (_: Exception) { val mediaLoadOptions = MediaLoadOptions.Builder() .setPlayPosition(startAt) @@ -259,8 +228,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi } } } - loadMirror(which) + loadMirror(which) bottomSheetDialog.dismissSafe(activity) } } @@ -270,23 +239,19 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi private fun getCurrentMetaData(): MetadataHolder? { return try { - val data = remoteMediaClient?.mediaInfo?.customData?.toString() - data?.toKotlinObject() - } catch (e: Exception) { + val data = remoteMediaClient?.mediaInfo?.customData?.toString() ?: return null + parseJson(data) + } catch (_: Exception) { null } } var isLoadingMore = false - - override fun onMediaStatusUpdated() { super.onMediaStatusUpdated() val meta = getCurrentMetaData() + view.visibility = if ((meta?.currentLinks?.size ?: 0) > 1) VISIBLE else INVISIBLE - view.visibility = if ((meta?.currentLinks?.size - ?: 0) > 1 - ) VISIBLE else INVISIBLE try { if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) { val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return @@ -303,7 +268,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi currentPosition, currentDuration, epData, - meta.episodes.getOrNull(index + 1) + meta.episodes.getOrNull(index + 1), ) } catch (t: Throwable) { logError(t) @@ -314,9 +279,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi ioSafe { val currentLinks = mutableSetOf() val currentSubs = mutableSetOf() - val generator = RepoLinkGenerator(listOf(epData)) - val isSuccessful = safeApiCall { generator.generateLinks( clearCache = false, @@ -329,7 +292,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi currentSubs.add(it) }, offset = 0, - isCasting = true + isCasting = true, ) } @@ -340,32 +303,18 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi val jsonCopy = meta.copy( currentLinks = sortedLinks, currentSubtitles = sortedSubs, - currentEpisodeIndex = index + currentEpisodeIndex = index, ) - val done = - JSONObject(jsonCopy.toJson()) - + val done = JSONObject(jsonCopy.toJson()) val mediaInfo = getMediaInfo( epData, jsonCopy, 0, done, - sortedSubs + sortedSubs, ) - /*fun loadIndex(index: Int) { - println("LOAD INDEX::::: $index") - if (meta.currentLinks.size <= index) return - val info = getMediaInfo( - epData, - meta, - index, - done) - awaitLinks(remoteMediaClient?.load(info, true, 0)) { - loadIndex(index + 1) - } - }*/ activity.runOnUiThread { awaitLinks( remoteMediaClient?.queueAppendItem( @@ -374,7 +323,6 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi ) ) { println("FAILED TO LOAD NEXT ITEM") - // loadIndex(1) } isLoadingMore = false } @@ -397,10 +345,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi class SkipTimeController(val view: ImageView, forwards: Boolean) : UIController() { init { - //val settingsManager = PreferenceManager.getDefaultSharedPreferences() - //val time = settingsManager?.getInt("chromecast_tap_time", 30) ?: 30 val time = 30 - //view.setImageResource(if (forwards) R.drawable.netflix_skip_forward else R.drawable.netflix_skip_back) view.setImageResource(if (forwards) R.drawable.go_forward_30 else R.drawable.go_back_30) view.setOnClickListener { remoteMediaClient?.let { From b245a2d9d75049f41796d919fe9bf1c0179d140a Mon Sep 17 00:00:00 2001 From: Phisher98 <153359846+phisher98@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:58:26 +0530 Subject: [PATCH 066/105] Fix VidHidePro M3U8 type detection in JWPlayerHelper (#3064) --- .../extractors/helper/JWPlayerHelper.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt index b026fa237..2b388d4e0 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/JWPlayerHelper.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.newExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.INFER_TYPE import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.collections.orEmpty @@ -52,11 +54,12 @@ object JwPlayerHelper { }.toList() var extractedLinks = sourceMatches.flatMap { link -> - if (link.file.contains(".m3u8")) { + val cleanUrl = link.file.replace("\\/", "/") + if (cleanUrl.contains(".m3u8") || cleanUrl.contains(".txt")) { try { M3u8Helper.generateM3u8( source = sourceName, - streamUrl = link.file, + streamUrl = cleanUrl, referer = mainUrl, headers = headers, ) @@ -69,7 +72,7 @@ object JwPlayerHelper { newExtractorLink( source = sourceName, name = sourceName, - url = fixUrl(link.file, mainUrl), + url = fixUrl(cleanUrl, mainUrl), ) { this.referer = url this.headers = headers @@ -98,11 +101,13 @@ object JwPlayerHelper { */ if (extractedLinks.isEmpty()) { extractedLinks = m3u8Regex.findAll(script).toList().map { match -> - val link = match.groupValues[1] + val cleanUrl = match.groupValues[1].replace("\\/", "/") + val isM3u8 = cleanUrl.contains(".m3u8") || cleanUrl.contains(".txt") newExtractorLink( source = sourceName, name = sourceName, - url = fixUrl(link, mainUrl), + url = fixUrl(cleanUrl, mainUrl), + type = if (isM3u8) ExtractorLinkType.M3U8 else INFER_TYPE, ) { this.referer = url this.headers = headers From ecf5650bbdb89e5c9331e5a574792f94ff2765c3 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:46:09 +0000 Subject: [PATCH 067/105] fix build --- app/src/main/res/values-b+lt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index bccc4cf59..ff92ac093 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -249,7 +249,7 @@ Pašalinti iš žiūrimų Garso takelis Užpildymas - min + %d min Namai Atsisiuntimų eilė Balso atpažinimas nepasiekiamas From 076e7a51168b0d615ba0d7fde955517b917cedec Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:00:32 -0600 Subject: [PATCH 068/105] [skip ci] test: add an IgnoreOnWeb annotation (#3034) --- .../kotlin/com/lagradost/cloudstream3/utils/Ignore.kt | 6 ++++++ .../com/lagradost/cloudstream3/utils/JsInterpreterTest.kt | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.kt diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.kt new file mode 100644 index 000000000..f815258a3 --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.kt @@ -0,0 +1,6 @@ +package com.lagradost.cloudstream3.utils + +@OptIn(ExperimentalMultiplatform::class) // OptionalExpectation is an experimental annotation for now +@OptionalExpectation +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +expect annotation class IgnoreOnWeb() diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt index ded236f4e..588b8d268 100644 --- a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/utils/JsInterpreterTest.kt @@ -2133,6 +2133,7 @@ class JsInterpreterTest { } @Test + @IgnoreOnWeb fun suspendEvalJsWithTimeoutCancelsInfiniteLoop() = runTest { /** * activeScope() provides a real CoroutineScope with a real Job, so withTimeout @@ -2326,6 +2327,7 @@ class JsInterpreterTest { } @Test + @IgnoreOnWeb fun jsContextEvalCancelledWhenEnclosingCoroutineCancelledViaWithTimeout() = runTest { val done = Channel() var elapsed = Duration.ZERO @@ -2360,6 +2362,7 @@ class JsInterpreterTest { } @Test + @IgnoreOnWeb fun jsContextJsTryCatchCannotSwallowEnclosingCancellation() = runTest { val done = Channel() var elapsed = Duration.ZERO From d778befe19751e0de3f815d1afb78d158c08eee5 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:26:21 -0600 Subject: [PATCH 069/105] [skip ci] Add KMP locale API for SubtitleHelper (#2833) --- library/api/jvm/library.api | 3 +- .../utils/SubtitleHelperPlatform.android.kt | 24 ++++++++++++++ .../cloudstream3/utils/SubtitleHelper.kt | 27 ++-------------- .../utils/SubtitleHelperPlatform.kt | 13 ++++++++ .../utils/SubtitleHelperPlatform.jvm.kt | 21 +++++++++++++ .../utils/SubtitleHelperPlatform.web.kt | 31 +++++++++++++++++++ 6 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.android.kt create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.kt create mode 100644 library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.jvm.kt create mode 100644 library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.web.kt diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index d0d80346a..49638f132 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -7524,8 +7524,9 @@ public final class com/lagradost/cloudstream3/utils/SubtitleHelper$LanguageMetad public fun toString ()Ljava/lang/String; } -public final class com/lagradost/cloudstream3/utils/SubtitleHelperKt { +public final class com/lagradost/cloudstream3/utils/SubtitleHelperPlatform_jvmKt { public static final fun getCurrentLocale ()Ljava/lang/String; + public static final fun localizedLanguageName (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; } public abstract class com/lagradost/cloudstream3/utils/serializers/NonEmptySerializer : kotlinx/serialization/json/JsonTransformingSerializer { diff --git a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.android.kt b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.android.kt new file mode 100644 index 000000000..1a87b512d --- /dev/null +++ b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.android.kt @@ -0,0 +1,24 @@ +package com.lagradost.cloudstream3.utils + +import java.util.Locale + +// TODO: add androidMain actual using LocaleListCompat.getAdjustedDefault() to respect +// per-app language preferences set via AppCompatDelegate.setApplicationLocales() + +actual fun getCurrentLocale(): String = + Locale.getDefault().toLanguageTag() + +actual fun localizedLanguageName(ietfTag: String, localizedTo: String): String? { + val localeOfLangCode = Locale.forLanguageTag(ietfTag) + val localeOfLocalizeTo = Locale.forLanguageTag(localizedTo) + val displayName = localeOfLangCode.getDisplayName(localeOfLocalizeTo) + + // Locale.getDisplayName() falls back to the raw tag or "language (country)" form + // when it doesn't know how to render the name. + val langCodeWithCountry = "${localeOfLangCode.language} (" + val failed = + displayName.equals(ietfTag, ignoreCase = true) || + displayName.contains(langCodeWithCountry, ignoreCase = true) + + return if (failed) null else displayName +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt index a6362efd0..497e129a0 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt @@ -1,13 +1,5 @@ package com.lagradost.cloudstream3.utils -import java.util.Locale - -// If you find a way to use SettingsGeneral getCurrentLocale() -// instead of this function do it. -fun getCurrentLocale(): String { - return Locale.getDefault().toLanguageTag() -} - @Suppress( "unused", "MemberVisibilityCanBePrivate" @@ -47,23 +39,10 @@ object SubtitleHelper { val ISO_639_3: String, // ISO 639-6 missing as it's intended to differentiate specific dialects and variants val openSubtitles: String, // inconsistent codes that do not conform ISO 639 ) { - fun localizedName(localizedTo: String? = null): String { - // Use system locale to localize language name - val localeOfLangCode = Locale.forLanguageTag(this.IETF_tag) - val localeOfLocalizeTo = Locale.forLanguageTag(localizedTo ?: getCurrentLocale()) - val sysLocalizedName = localeOfLangCode.getDisplayName(localeOfLocalizeTo) - - val langCodeWithCountry = "${localeOfLangCode.language} (" // ${localeOfLangCode.country})" - val failedToLocalize = - sysLocalizedName.equals(this.IETF_tag, ignoreCase = true) || - sysLocalizedName.contains(langCodeWithCountry, ignoreCase = true) - - return if (failedToLocalize) + fun localizedName(localizedTo: String? = null): String = + localizedLanguageName(this.IETF_tag, localizedTo ?: getCurrentLocale()) // fallback to native language name - this.nativeName - else - sysLocalizedName - } + ?: this.nativeName fun nameNextToFlagEmoji(localizedTo: String? = null): String { // fallback to [A][A] -> [?] question mak flag diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.kt new file mode 100644 index 000000000..38a254da8 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.kt @@ -0,0 +1,13 @@ +package com.lagradost.cloudstream3.utils + +/** + * Returns the current locale as an IETF BCP 47 language tag. + */ +expect fun getCurrentLocale(): String + +/** + * Returns the display name of [ietfTag] localized into [localizedTo]. + * Returns null if the platform couldn't produce a meaningful name + * (i.e. it just echoed back the tag or contained a bare language code with parentheses). + */ +expect fun localizedLanguageName(ietfTag: String, localizedTo: String): String? \ No newline at end of file diff --git a/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.jvm.kt b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.jvm.kt new file mode 100644 index 000000000..a43358ccb --- /dev/null +++ b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.jvm.kt @@ -0,0 +1,21 @@ +package com.lagradost.cloudstream3.utils + +import java.util.Locale + +actual fun getCurrentLocale(): String = + Locale.getDefault().toLanguageTag() + +actual fun localizedLanguageName(ietfTag: String, localizedTo: String): String? { + val localeOfLangCode = Locale.forLanguageTag(ietfTag) + val localeOfLocalizeTo = Locale.forLanguageTag(localizedTo) + val displayName = localeOfLangCode.getDisplayName(localeOfLocalizeTo) + + // Locale.getDisplayName() falls back to the raw tag or "language (country)" form + // when it doesn't know how to render the name. + val langCodeWithCountry = "${localeOfLangCode.language} (" + val failed = + displayName.equals(ietfTag, ignoreCase = true) || + displayName.contains(langCodeWithCountry, ignoreCase = true) + + return if (failed) null else displayName +} \ No newline at end of file diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.web.kt new file mode 100644 index 000000000..33e293f31 --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelperPlatform.web.kt @@ -0,0 +1,31 @@ +package com.lagradost.cloudstream3.utils + +@OptIn(ExperimentalWasmJsInterop::class) +external class IntlDisplayNames(locale: String, options: JsAny) : JsAny { + fun of(code: String): String? +} + +@OptIn(ExperimentalWasmJsInterop::class) +private fun navigatorLanguage(): String = + js("navigator.language") + +actual fun getCurrentLocale(): String = + try { navigatorLanguage() } catch (_: Throwable) { "en" } + +@OptIn(ExperimentalWasmJsInterop::class) +private fun buildOptions(): JsAny = + js("({ type: 'language' })") + +@OptIn(ExperimentalWasmJsInterop::class) +private fun makeDisplayNames(locale: String, options: JsAny): IntlDisplayNames = + js("new Intl.DisplayNames([locale], options)") + +@OptIn(ExperimentalWasmJsInterop::class) +actual fun localizedLanguageName(ietfTag: String, localizedTo: String): String? = + try { + val dn = makeDisplayNames(localizedTo, buildOptions()) + val name = dn.of(ietfTag) + if (name.isNullOrBlank() || name.equals(ietfTag, ignoreCase = true)) null else name + } catch (_: Throwable) { + null + } From 15fea0c7caf50c457a745d06e2e74695d66308fa Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:37:33 -0600 Subject: [PATCH 070/105] Bump compilesdk to 37 (#2670) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f20c19832..f45bd6778 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -59,7 +59,7 @@ zipline = "1.27.0" jvmTarget = "1.8" jdkToolchain = "17" minSdk = "23" -compileSdk = "36" +compileSdk = "37" targetSdk = "36" versionCode = "68" From feddcccf0d4186fc6b6fe6520181c02f709b33af Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 14 Jul 2026 05:17:47 -0700 Subject: [PATCH 071/105] [skip ci] Update Kotlin to 2.4.0 (#2868) --- app/build.gradle.kts | 2 -- .../com/lagradost/cloudstream3/ui/home/HomeFragment.kt | 1 - gradle/libs.versions.toml | 2 +- library/api/jvm/library.api | 1 - library/build.gradle.kts | 8 ++------ .../com/lagradost/cloudstream3/utils/ExtractorApi.kt | 3 --- 6 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 75a9698c8..0feb866be 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -323,11 +323,9 @@ tasks.withType { compilerOptions { jvmTarget.set(javaTarget) jvmDefault.set(JvmDefaultMode.ENABLE) - freeCompilerArgs.add("-Xannotation-default-target=param-property") optIn.addAll( "com.lagradost.cloudstream3.InternalAPI", "com.lagradost.cloudstream3.Prerelease", - "kotlin.uuid.ExperimentalUuidApi", ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index b68ef5962..ab0715c6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -665,7 +665,6 @@ class HomeFragment : BaseFragment( fromUI = true ) showToast(R.string.action_reload, Toast.LENGTH_SHORT) - true } homePreviewSearchButton.setOnClickListener { _ -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f45bd6778..fdfaee7b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ junit = "4.13.2" junitKtx = "1.3.0" junitVersion = "1.3.0" juniversalchardet = "2.5.0" -kotlinGradlePlugin = "2.3.20" +kotlinGradlePlugin = "2.4.0" kotlinxAtomicfu = "0.33.0" kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutines = "1.11.0" diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 49638f132..794d7d520 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -1185,7 +1185,6 @@ public final class com/lagradost/cloudstream3/Score { public static final field MAX I public static final field MAX_ZEROS I public static final field MIN I - public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I public final fun toByte (I)B diff --git a/library/build.gradle.kts b/library/build.gradle.kts index aa460164e..18579eb0c 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -41,10 +41,7 @@ kotlin { jvm() compilerOptions { - freeCompilerArgs.addAll( - "-Xexpect-actual-classes", - "-Xannotation-default-target=param-property" - ) + freeCompilerArgs.add("-Xexpect-actual-classes") } sourceSets { @@ -91,8 +88,7 @@ kotlin { @OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class) // https://kotlinlang.org/docs/gradle-binary-compatibility-validation.html abiValidation { - enabled.set(true) - this.filters { + filters { exclude { annotatedWith.add("com.lagradost.cloudstream3.Prerelease") annotatedWith.add("com.lagradost.cloudstream3.InternalAPI") diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 554170df5..163059a97 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -1,5 +1,3 @@ -@file:OptIn(ExperimentalUuidApi::class) - package com.lagradost.cloudstream3.utils import com.fasterxml.jackson.annotation.JsonIgnore @@ -332,7 +330,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlin.coroutines.cancellation.CancellationException -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid import kotlin.uuid.toJavaUuid import kotlin.uuid.toKotlinUuid From 804b3c279c2752501d0a5d8711c68dffb77f8164 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 14 Jul 2026 06:04:51 -0700 Subject: [PATCH 072/105] extractors: migrate to kotlinx serialization (#2963) --- library/api/jvm/library.api | 836 +++++++++++++++++- .../cloudstream3/extractors/Acefile.kt | 13 +- .../cloudstream3/extractors/Blogger.kt | 14 +- .../cloudstream3/extractors/ByseSX.kt | 98 +- .../lagradost/cloudstream3/extractors/Cda.kt | 21 +- .../cloudstream3/extractors/Firestream.kt | 22 +- .../cloudstream3/extractors/Flyfile.kt | 18 +- .../cloudstream3/extractors/GUpload.kt | 28 +- .../cloudstream3/extractors/Gdriveplayer.kt | 22 +- .../cloudstream3/extractors/Gofile.kt | 32 +- .../extractors/HDMomPlayerExtractor.kt | 49 +- .../extractors/HDPlayerSystemExtractor.kt | 52 +- .../cloudstream3/extractors/Jeniusplay.kt | 17 +- .../cloudstream3/extractors/Linkbox.kt | 23 +- .../extractors/MailRuExtractor.kt | 48 +- .../extractors/OdnoklassnikiExtractor.kt | 55 +- .../extractors/PeaceMakerstExtractor.kt | 89 +- .../cloudstream3/extractors/PlayLtXyz.kt | 53 +- .../cloudstream3/extractors/Rabbitstream.kt | 54 +- .../extractors/SobreatsesuypExtractor.kt | 48 +- .../cloudstream3/extractors/StreamEmbed.kt | 26 +- .../cloudstream3/extractors/StreamSB.kt | 49 +- .../cloudstream3/extractors/Streamlare.kt | 50 +- .../cloudstream3/extractors/Streamplay.kt | 42 +- .../cloudstream3/extractors/TRsTXExtractor.kt | 62 +- .../cloudstream3/extractors/Tantifilm.kt | 42 +- .../extractors/TauVideoExtractor.kt | 36 +- .../cloudstream3/extractors/Uservideo.kt | 30 +- .../cloudstream3/extractors/Vicloud.kt | 26 +- .../cloudstream3/extractors/Vidara.kt | 34 +- .../extractors/VideoSeyredExtractor.kt | 64 +- .../cloudstream3/extractors/Vidoza.kt | 44 +- .../cloudstream3/extractors/Vinovo.kt | 33 +- .../cloudstream3/extractors/XStreamCdn.kt | 138 +-- .../cloudstream3/extractors/YourUpload.kt | 19 +- 35 files changed, 1570 insertions(+), 717 deletions(-) diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 794d7d520..1b39b3906 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -1735,6 +1735,7 @@ public class com/lagradost/cloudstream3/extractors/Acefile : com/lagradost/cloud } public final class com/lagradost/cloudstream3/extractors/Acefile$Source { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Acefile$Source$Companion; public fun ()V public fun (Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1747,6 +1748,21 @@ public final class com/lagradost/cloudstream3/extractors/Acefile$Source { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Acefile$Source$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Acefile$Source$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Acefile$Source; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Acefile$Source;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Acefile$Source$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/AesHelper { public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/AesHelper; public final fun decryptAES (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; @@ -1815,16 +1831,12 @@ public final class com/lagradost/cloudstream3/extractors/ByseBuho : com/lagrados public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/ByseQekaho : com/lagradost/cloudstream3/extractors/ByseSX { public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public class com/lagradost/cloudstream3/extractors/ByseSX : com/lagradost/cloudstream3/utils/ExtractorApi { @@ -1833,24 +1845,18 @@ public class com/lagradost/cloudstream3/extractors/ByseSX : com/lagradost/clouds public fun getName ()Ljava/lang/String; public fun getRequiresReferer ()Z public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/ByseVepoin : com/lagradost/cloudstream3/extractors/ByseSX { public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/Bysezejataos : com/lagradost/cloudstream3/extractors/ByseSX { public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/Cavanhabg : com/lagradost/cloudstream3/extractors/VidHidePro { @@ -1870,6 +1876,7 @@ public class com/lagradost/cloudstream3/extractors/Cda : com/lagradost/cloudstre } public final class com/lagradost/cloudstream3/extractors/Cda$PlayerData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData$Companion; public fun (Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;)V public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData; public final fun copy (Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;)Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData; @@ -1880,7 +1887,23 @@ public final class com/lagradost/cloudstream3/extractors/Cda$PlayerData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Cda$PlayerData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Cda$PlayerData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Cda$PlayerData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Cda$VideoPlayerData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData$Companion; public fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -1900,6 +1923,21 @@ public final class com/lagradost/cloudstream3/extractors/Cda$VideoPlayerData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Cda$VideoPlayerData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Cda$VideoPlayerData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Cda$VideoPlayerData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Cdnplayer : com/lagradost/cloudstream3/extractors/XStreamCdn { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2086,16 +2124,15 @@ public final class com/lagradost/cloudstream3/extractors/Dailymotion$SubtitlesWr public final class com/lagradost/cloudstream3/extractors/DatabaseGdrive : com/lagradost/cloudstream3/extractors/Gdriveplayer { public fun ()V public fun getMainUrl ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/DatabaseGdrive2 : com/lagradost/cloudstream3/extractors/Gdriveplayer { public fun ()V public fun getMainUrl ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/DecryptKeys { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/DecryptKeys$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -2110,6 +2147,21 @@ public final class com/lagradost/cloudstream3/extractors/DecryptKeys { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/DecryptKeys$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/DecryptKeys$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/DecryptKeys; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/DecryptKeys;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/DecryptKeys$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/DesuArcg : com/lagradost/cloudstream3/extractors/JWPlayer { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2135,6 +2187,7 @@ public final class com/lagradost/cloudstream3/extractors/DesuOdvip : com/lagrado } public final class com/lagradost/cloudstream3/extractors/DetailsRoot { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/DetailsRoot$Companion; public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)V public final fun component1 ()J public final fun component2 ()Ljava/lang/String; @@ -2159,6 +2212,21 @@ public final class com/lagradost/cloudstream3/extractors/DetailsRoot { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/DetailsRoot$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/DetailsRoot$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/DetailsRoot; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/DetailsRoot;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/DetailsRoot$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Dhcplay : com/lagradost/cloudstream3/extractors/CineMMRedirect { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2371,15 +2439,12 @@ public final class com/lagradost/cloudstream3/extractors/FeHD : com/lagradost/cl public fun getDomainUrl ()Ljava/lang/String; public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setDomainUrl (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/Fembed9hd : com/lagradost/cloudstream3/extractors/XStreamCdn { public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/FileMoon : com/lagradost/cloudstream3/extractors/FilemoonV2 { @@ -2525,6 +2590,7 @@ public class com/lagradost/cloudstream3/extractors/Gdriveplayer : com/lagradost/ } public final class com/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -2539,6 +2605,21 @@ public final class com/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Gdriveplayer$Tracks$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Gdriveplayerapi : com/lagradost/cloudstream3/extractors/Gdriveplayer { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2609,6 +2690,7 @@ public class com/lagradost/cloudstream3/extractors/Gofile : com/lagradost/clouds } public final class com/lagradost/cloudstream3/extractors/Gofile$AccountData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData$Companion; public fun ()V public fun (Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2621,7 +2703,23 @@ public final class com/lagradost/cloudstream3/extractors/Gofile$AccountData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Gofile$AccountData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$AccountData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Gofile$AccountResponse { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse$Companion; public fun ()V public fun (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;)V public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Gofile$AccountData;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2634,7 +2732,23 @@ public final class com/lagradost/cloudstream3/extractors/Gofile$AccountResponse public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Gofile$AccountResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Gofile$AccountResponse;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$AccountResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Gofile$GofileData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData$Companion; public fun ()V public fun (Ljava/util/Map;)V public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2647,7 +2761,23 @@ public final class com/lagradost/cloudstream3/extractors/Gofile$GofileData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Gofile$GofileData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$GofileData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Gofile$GofileFile { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2666,7 +2796,23 @@ public final class com/lagradost/cloudstream3/extractors/Gofile$GofileFile { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Gofile$GofileFile$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Gofile$GofileFile;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$GofileFile$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Gofile$GofileResponse { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse$Companion; public fun ()V public fun (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;)V public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Gofile$GofileData;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2679,6 +2825,21 @@ public final class com/lagradost/cloudstream3/extractors/Gofile$GofileResponse { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Gofile$GofileResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Gofile$GofileResponse;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Gofile$GofileResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/GoodstreamExtractor : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2710,6 +2871,7 @@ public class com/lagradost/cloudstream3/extractors/HDMomPlayer : com/lagradost/c } public final class com/lagradost/cloudstream3/extractors/HDMomPlayer$Track { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -2728,6 +2890,21 @@ public final class com/lagradost/cloudstream3/extractors/HDMomPlayer$Track { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/HDMomPlayer$Track$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/HDMomPlayer$Track;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/HDMomPlayer$Track$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/HDPlayerSystem : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2737,6 +2914,7 @@ public class com/lagradost/cloudstream3/extractors/HDPlayerSystem : com/lagrados } public final class com/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -2754,6 +2932,21 @@ public final class com/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemRe public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/HDPlayerSystem$SystemResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/HDStreamAble : com/lagradost/cloudstream3/extractors/PeaceMakerst { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2853,6 +3046,7 @@ public class com/lagradost/cloudstream3/extractors/Jeniusplay : com/lagradost/cl } public final class com/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource$Companion; public fun (ZLjava/lang/String;Ljava/lang/String;)V public final fun component1 ()Z public final fun component2 ()Ljava/lang/String; @@ -2867,6 +3061,21 @@ public final class com/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSour public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Jeniusplay$ResponseSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Jodwish : com/lagradost/cloudstream3/extractors/StreamWishExtractor { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -2923,6 +3132,7 @@ public class com/lagradost/cloudstream3/extractors/Linkbox : com/lagradost/cloud } public final class com/lagradost/cloudstream3/extractors/Linkbox$Data { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Linkbox$Data$Companion; public fun ()V public fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/lang/String;)V public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2937,7 +3147,23 @@ public final class com/lagradost/cloudstream3/extractors/Linkbox$Data { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Linkbox$Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Linkbox$Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Data; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Linkbox$ItemInfo { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo$Companion; public fun ()V public fun (Ljava/util/ArrayList;)V public synthetic fun (Ljava/util/ArrayList;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2950,7 +3176,23 @@ public final class com/lagradost/cloudstream3/extractors/Linkbox$ItemInfo { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Linkbox$ItemInfo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Linkbox$ItemInfo;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$ItemInfo$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Linkbox$Resolutions { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2965,7 +3207,23 @@ public final class com/lagradost/cloudstream3/extractors/Linkbox$Resolutions { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Linkbox$Resolutions$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Linkbox$Resolutions;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$Resolutions$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Linkbox$Responses { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses$Companion; public fun ()V public fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;)V public synthetic fun (Lcom/lagradost/cloudstream3/extractors/Linkbox$Data;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2978,6 +3236,21 @@ public final class com/lagradost/cloudstream3/extractors/Linkbox$Responses { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Linkbox$Responses$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Linkbox$Responses;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Linkbox$Responses$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/LuluStream : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3032,6 +3305,7 @@ public class com/lagradost/cloudstream3/extractors/MailRu : com/lagradost/clouds } public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData$Companion; public fun (Ljava/lang/String;Ljava/util/List;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/util/List; @@ -3044,7 +3318,23 @@ public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/MailRu$MailRuData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -3057,6 +3347,21 @@ public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/MailRu$MailRuVideoData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/Maxstream : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3272,6 +3577,7 @@ public class com/lagradost/cloudstream3/extractors/Odnoklassniki : com/lagradost } public final class com/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -3284,6 +3590,21 @@ public final class com/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Odnoklassniki$OkRuVideo$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/OkRuHTTP : com/lagradost/cloudstream3/extractors/Odnoklassniki { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3321,6 +3642,7 @@ public class com/lagradost/cloudstream3/extractors/PeaceMakerst : com/lagradost/ } public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse$Companion; public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/util/List; @@ -3329,7 +3651,7 @@ public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceRespo public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse; public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse; public fun equals (Ljava/lang/Object;)Z - public final fun getSIndex ()Ljava/lang/String; + public final fun getSourceIndex ()Ljava/lang/String; public final fun getSourceList ()Ljava/util/Map; public final fun getVideoImage ()Ljava/lang/String; public final fun getVideoSources ()Ljava/util/List; @@ -3337,7 +3659,23 @@ public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceRespo public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$PeaceResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse$Companion; public fun (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;)V public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; public final fun copy (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse; @@ -3348,7 +3686,23 @@ public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiRe public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2ApiResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -3361,7 +3715,23 @@ public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media$Companion; public fun (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;)V public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link; public final fun copy (Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Link;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; @@ -3372,7 +3742,23 @@ public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$Teve2Media$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -3387,6 +3773,21 @@ public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSourc public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PeaceMakerst$VideoSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Peytonepre : com/lagradost/cloudstream3/extractors/VidHidePro { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3434,6 +3835,7 @@ public final class com/lagradost/cloudstream3/extractors/PlayRu : com/lagradost/ } public final class com/lagradost/cloudstream3/extractors/Playback { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Playback$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lcom/lagradost/cloudstream3/extractors/DecryptKeys;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -3458,7 +3860,23 @@ public final class com/lagradost/cloudstream3/extractors/Playback { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Playback$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Playback$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Playback; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Playback;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Playback$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PlaybackDecrypt { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt$Companion; public fun (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt; @@ -3469,19 +3887,35 @@ public final class com/lagradost/cloudstream3/extractors/PlaybackDecrypt { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PlaybackDecrypt$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PlaybackDecrypt;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PlaybackDecrypt$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PlaybackDecryptSource { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;)V + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Integer;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()J - public final fun component6 ()Ljava/lang/Object; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Object;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; + public final fun component6 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Integer;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; public fun equals (Ljava/lang/Object;)Z public final fun getBitrateKbps ()J - public final fun getHeight ()Ljava/lang/Object; + public final fun getHeight ()Ljava/lang/Integer; public final fun getLabel ()Ljava/lang/String; public final fun getMimeType ()Ljava/lang/String; public final fun getQuality ()Ljava/lang/String; @@ -3490,7 +3924,23 @@ public final class com/lagradost/cloudstream3/extractors/PlaybackDecryptSource { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PlaybackDecryptSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PlaybackDecryptSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PlaybackDecryptSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/PlaybackRoot { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/PlaybackRoot$Companion; public fun (Lcom/lagradost/cloudstream3/extractors/Playback;)V public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/Playback; public final fun copy (Lcom/lagradost/cloudstream3/extractors/Playback;)Lcom/lagradost/cloudstream3/extractors/PlaybackRoot; @@ -3501,6 +3951,21 @@ public final class com/lagradost/cloudstream3/extractors/PlaybackRoot { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/PlaybackRoot$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/PlaybackRoot$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/PlaybackRoot; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/PlaybackRoot;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/PlaybackRoot$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/PlayerVoxzer : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3535,6 +4000,7 @@ public class com/lagradost/cloudstream3/extractors/Rabbitstream : com/lagradost/ } public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Sources { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3551,7 +4017,23 @@ public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Sources { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Rabbitstream$Sources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Sources;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Sources$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;)V public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3568,7 +4050,23 @@ public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEnc public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesEncrypted$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses$Companion; public fun ()V public fun (Ljava/util/List;Ljava/util/List;)V public synthetic fun (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3583,7 +4081,23 @@ public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesRes public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$SourcesResponses$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Tracks { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3600,6 +4114,21 @@ public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Tracks { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Rabbitstream$Tracks$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Rabbitstream$Tracks;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Rabbitstream$Tracks$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/RapidVid : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3774,6 +4303,7 @@ public class com/lagradost/cloudstream3/extractors/Slmaxed : com/lagradost/cloud } public final class com/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3794,7 +4324,23 @@ public final class com/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Slmaxed$JsonResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Slmaxed$Result { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3811,6 +4357,21 @@ public final class com/lagradost/cloudstream3/extractors/Slmaxed$Result { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Slmaxed$Result$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Slmaxed$Result;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Slmaxed$Result$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Smoothpre : com/lagradost/cloudstream3/extractors/VidHidePro { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3828,6 +4389,7 @@ public class com/lagradost/cloudstream3/extractors/Sobreatsesuyp : com/lagradost } public final class com/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3842,6 +4404,21 @@ public final class com/lagradost/cloudstream3/extractors/Sobreatsesuyp$Sobreatse public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Sobreatsesuyp$SobreatsesuypVideoData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Ssbstream : com/lagradost/cloudstream3/extractors/StreamSB { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -3854,8 +4431,6 @@ public class com/lagradost/cloudstream3/extractors/StreamEmbed : com/lagradost/c public fun getName ()Ljava/lang/String; public fun getRequiresReferer ()Z public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/StreamHLS : com/lagradost/cloudstream3/extractors/StreamWishExtractor { @@ -3881,6 +4456,7 @@ public class com/lagradost/cloudstream3/extractors/StreamSB : com/lagradost/clou } public final class com/lagradost/cloudstream3/extractors/StreamSB$Main { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/StreamSB$Main$Companion; public fun (Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData;I)V public final fun component1 ()Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData; public final fun component2 ()I @@ -3893,7 +4469,23 @@ public final class com/lagradost/cloudstream3/extractors/StreamSB$Main { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/StreamSB$Main$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/StreamSB$Main$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/StreamSB$Main; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/StreamSB$Main;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB$Main$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/StreamSB$StreamData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -3919,7 +4511,23 @@ public final class com/lagradost/cloudstream3/extractors/StreamSB$StreamData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/StreamSB$StreamData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/StreamSB$StreamData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB$StreamData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/StreamSB$Subs { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3934,6 +4542,21 @@ public final class com/lagradost/cloudstream3/extractors/StreamSB$Subs { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/StreamSB$Subs$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/StreamSB$Subs;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/StreamSB$Subs$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/StreamSB1 : com/lagradost/cloudstream3/extractors/StreamSB { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -4102,6 +4725,7 @@ public class com/lagradost/cloudstream3/extractors/Streamplay : com/lagradost/cl } public final class com/lagradost/cloudstream3/extractors/Streamplay$Source { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Streamplay$Source$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -4116,6 +4740,21 @@ public final class com/lagradost/cloudstream3/extractors/Streamplay$Source { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Streamplay$Source$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Streamplay$Source$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Streamplay$Source; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Streamplay$Source;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Streamplay$Source$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Streamsss : com/lagradost/cloudstream3/extractors/StreamSB { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -4170,6 +4809,7 @@ public class com/lagradost/cloudstream3/extractors/TRsTX : com/lagradost/cloudst } public final class com/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -4184,17 +4824,31 @@ public final class com/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/TRsTX$TrstxVideoData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/Tantifilm : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; public fun getRequiresReferer ()Z public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -4209,7 +4863,23 @@ public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData$Companion; public fun (ZLjava/util/List;Ljava/util/List;Z)V public final fun component1 ()Z public final fun component2 ()Ljava/util/List; @@ -4222,10 +4892,25 @@ public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJson public final fun getData ()Ljava/util/List; public final fun getSuccess ()Z public fun hashCode ()I - public final fun is_vr ()Z + public final fun isVr ()Z public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Tantifilm$TantifilmJsonData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/TauVideo : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -4235,6 +4920,7 @@ public class com/lagradost/cloudstream3/extractors/TauVideo : com/lagradost/clou } public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoData { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -4247,7 +4933,23 @@ public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoData { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoData;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls$Companion; public fun (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls; @@ -4258,6 +4960,21 @@ public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/TauVideo$TauVideoUrls$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Techinmind : com/lagradost/cloudstream3/extractors/GDMirrorbot { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -4382,6 +5099,7 @@ public class com/lagradost/cloudstream3/extractors/Uservideo : com/lagradost/clo } public final class com/lagradost/cloudstream3/extractors/Uservideo$Sources { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources$Companion; public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -4398,6 +5116,21 @@ public final class com/lagradost/cloudstream3/extractors/Uservideo$Sources { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/Uservideo$Sources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/Uservideo$Sources;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/Uservideo$Sources$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/extractors/Vicloud : com/lagradost/cloudstream3/utils/ExtractorApi { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -4550,6 +5283,7 @@ public class com/lagradost/cloudstream3/extractors/VideoSeyred : com/lagradost/c } public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSSource { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -4564,7 +5298,23 @@ public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSSource { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/VideoSeyred$VSSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -4584,7 +5334,23 @@ public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VSTrack$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource { + public static final field Companion Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; @@ -4601,6 +5367,21 @@ public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyred public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/extractors/VideoSeyred$VideoSeyredSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class com/lagradost/cloudstream3/extractors/Videzz : com/lagradost/cloudstream3/extractors/Vidoza { public fun ()V public fun getMainUrl ()Ljava/lang/String; @@ -4688,8 +5469,6 @@ public final class com/lagradost/cloudstream3/extractors/VinovoSi : com/lagrados public fun ()V public fun getMainUrl ()Ljava/lang/String; public fun getName ()Ljava/lang/String; - public fun setMainUrl (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V } public class com/lagradost/cloudstream3/extractors/VinovoTo : com/lagradost/cloudstream3/utils/ExtractorApi { @@ -4786,7 +5565,6 @@ public class com/lagradost/cloudstream3/extractors/XStreamCdn : com/lagradost/cl public fun getName ()Ljava/lang/String; public fun getRequiresReferer ()Z public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun setDomainUrl (Ljava/lang/String;)V } public final class com/lagradost/cloudstream3/extractors/Xenolyzb : com/lagradost/cloudstream3/extractors/VidHidePro { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Acefile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Acefile.kt index a86c5f01a..5b214c5dd 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Acefile.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Acefile.kt @@ -1,8 +1,11 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Acefile : ExtractorApi() { override val name = "Acefile" @@ -13,7 +16,7 @@ open class Acefile : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val id = "/(?:d|download|player|f|file)/(\\w+)".toRegex().find(url)?.groupValues?.get(1) val script = getAndUnpack(app.get("$mainUrl/player/${id ?: return}").text) @@ -27,14 +30,14 @@ open class Acefile : ExtractorApi() { newExtractorLink( this.name, this.name, - video ?: return + video ?: return, ) ) } + @Serializable data class Source( - val data: String? = null, + @JsonProperty("data") @SerialName("data") val data: String? = null, ) - -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Blogger.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Blogger.kt index a9f4a3eb9..e00ffcb57 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Blogger.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Blogger.kt @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Blogger : ExtractorApi() { override val name = "Blogger" @@ -22,10 +24,10 @@ open class Blogger : ExtractorApi() { newExtractorLink( name, name, - it.play_url, + it.playUrl, ) { this.referer = "https://www.youtube.com/" - this.quality = when (it.format_id) { + this.quality = when (it.formatId) { 18 -> 360 22 -> 720 else -> Qualities.Unknown.value @@ -36,11 +38,13 @@ open class Blogger : ExtractorApi() { } } } + return sources } + @Serializable private data class ResponseSource( - @JsonProperty("play_url") val play_url: String, - @JsonProperty("format_id") val format_id: Int + @JsonProperty("play_url") @SerialName("play_url") val playUrl: String, + @JsonProperty("format_id") @SerialName("format_id") val formatId: Int, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt index 5248a8861..4781474d6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByseSX.kt @@ -13,30 +13,32 @@ import dev.whyoleg.cryptography.DelicateCryptographyApi import dev.whyoleg.cryptography.algorithms.AES import io.ktor.http.Url import io.ktor.http.decodeURLPart +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class Bysezejataos : ByseSX() { - override var name = "Bysezejataos" - override var mainUrl = "https://bysezejataos.com" + override val name = "Bysezejataos" + override val mainUrl = "https://bysezejataos.com" } class ByseBuho : ByseSX() { - override var name = "ByseBuho" - override var mainUrl = "https://bysebuho.com" + override val name = "ByseBuho" + override val mainUrl = "https://bysebuho.com" } class ByseVepoin : ByseSX() { - override var name = "ByseVepoin" - override var mainUrl = "https://bysevepoin.com" + override val name = "ByseVepoin" + override val mainUrl = "https://bysevepoin.com" } class ByseQekaho : ByseSX() { - override var name = "ByseQekaho" - override var mainUrl = "https://byseqekaho.com" + override val name = "ByseQekaho" + override val mainUrl = "https://byseqekaho.com" } open class ByseSX : ExtractorApi() { - override var name = "Byse" - override var mainUrl = "https://byse.sx" + override val name = "Byse" + override val mainUrl = "https://byse.sx" override val requiresReferer = true private val aesGcm = CryptographyProvider.Default.get(AES.GCM) @@ -112,7 +114,7 @@ open class ByseSX : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val refererUrl = getBaseUrl(url) val playbackRoot = getPlayback(url) ?: return @@ -123,64 +125,58 @@ open class ByseSX : ExtractorApi() { name, streamUrl, mainUrl, - headers = headers + headers = headers, ).forEach(callback) } } +@Serializable data class DetailsRoot( - val id: Long, - val code: String, - val title: String, - @JsonProperty("poster_url") - val posterUrl: String, - val description: String, - @JsonProperty("created_at") - val createdAt: String, - @JsonProperty("owner_private") - val ownerPrivate: Boolean, - @JsonProperty("embed_frame_url") - val embedFrameUrl: String, + @JsonProperty("id") @SerialName("id") val id: Long, + @JsonProperty("code") @SerialName("code") val code: String, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("poster_url") @SerialName("poster_url") val posterUrl: String, + @JsonProperty("description") @SerialName("description") val description: String, + @JsonProperty("created_at") @SerialName("created_at") val createdAt: String, + @JsonProperty("owner_private") @SerialName("owner_private") val ownerPrivate: Boolean, + @JsonProperty("embed_frame_url") @SerialName("embed_frame_url") val embedFrameUrl: String, ) +@Serializable data class PlaybackRoot( - val playback: Playback, + @JsonProperty("playback") @SerialName("playback") val playback: Playback, ) +@Serializable data class Playback( - val algorithm: String, - val iv: String, - val payload: String, - @JsonProperty("key_parts") - val keyParts: List, - @JsonProperty("expires_at") - val expiresAt: String, - @JsonProperty("decrypt_keys") - val decryptKeys: DecryptKeys, - val iv2: String, - val payload2: String, + @JsonProperty("algorithm") @SerialName("algorithm") val algorithm: String, + @JsonProperty("iv") @SerialName("iv") val iv: String, + @JsonProperty("payload") @SerialName("payload") val payload: String, + @JsonProperty("key_parts") @SerialName("key_parts") val keyParts: List, + @JsonProperty("expires_at") @SerialName("expires_at") val expiresAt: String, + @JsonProperty("decrypt_keys") @SerialName("decrypt_keys") val decryptKeys: DecryptKeys, + @JsonProperty("iv2") @SerialName("iv2") val iv2: String, + @JsonProperty("payload2") @SerialName("payload2") val payload2: String, ) +@Serializable data class DecryptKeys( - @JsonProperty("edge_1") - val edge1: String, - @JsonProperty("edge_2") - val edge2: String, - @JsonProperty("legacy_fallback") - val legacyFallback: String, + @JsonProperty("edge_1") @SerialName("edge_1") val edge1: String, + @JsonProperty("edge_2") @SerialName("edge_2") val edge2: String, + @JsonProperty("legacy_fallback") @SerialName("legacy_fallback") val legacyFallback: String, ) +@Serializable data class PlaybackDecrypt( - val sources: List, + @JsonProperty("sources") @SerialName("sources") val sources: List, ) +@Serializable data class PlaybackDecryptSource( - val quality: String, - val label: String, - @JsonProperty("mime_type") - val mimeType: String, - val url: String, - @JsonProperty("bitrate_kbps") - val bitrateKbps: Long, - val height: Any?, + @JsonProperty("quality") @SerialName("quality") val quality: String, + @JsonProperty("label") @SerialName("label") val label: String, + @JsonProperty("mime_type") @SerialName("mime_type") val mimeType: String, + @JsonProperty("url") @SerialName("url") val url: String, + @JsonProperty("bitrate_kbps") @SerialName("bitrate_kbps") val bitrateKbps: Long, + @JsonProperty("height") @SerialName("height") val height: Int?, ) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt index 5c9f58efc..3b5189f0e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson @@ -8,6 +9,8 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.StringUtils.decodeUrl import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Cda : ExtractorApi() { override var mainUrl = "https://ebd.cda.pl" @@ -22,7 +25,7 @@ open class Cda : ExtractorApi() { "https://ebd.cda.pl/647x500/$mediaId", headers = mapOf( "Referer" to "https://ebd.cda.pl/647x500/$mediaId", "User-Agent" to USER_AGENT, - "Cookie" to "cda.player=html5" + "Cookie" to "cda.player=html5", ) ).document val dataRaw = doc.selectFirst("[player_data]")?.attr("player_data") ?: return null @@ -86,15 +89,17 @@ open class Cda : ExtractorApi() { else -> a } + @Serializable data class VideoPlayerData( - val file: String, - val qualities: Map = mapOf(), - val quality: String?, - val ts: Int?, - val hash2: String? + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("qualities") @SerialName("qualities") val qualities: Map = mapOf(), + @JsonProperty("quality") @SerialName("quality") val quality: String?, + @JsonProperty("ts") @SerialName("ts") val ts: Int?, + @JsonProperty("hash2") @SerialName("hash2") val hash2: String?, ) + @Serializable data class PlayerData( - val video: VideoPlayerData + @JsonProperty("video") @SerialName("video") val video: VideoPlayerData, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt index c2fbaeee2..f1d33e853 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app @@ -10,31 +11,29 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable class Firestream : ExtractorApi() { - override val name: String = "Firestream" - override val mainUrl: String = "https://firestream.to" - override val requiresReferer: Boolean = false + override val name = "Firestream" + override val mainUrl = "https://firestream.to" + override val requiresReferer = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val id = url.removeSuffix("/").substringAfterLast("/") val url = getExtractorUrl(id) val doc = app.get(url).document val token = doc.selectFirst("script[id=token-blob]")!!.data() - - val videoResponse = - app.post("$mainUrl/api/videos/$id/resolve", json = mapOf("blob" to token)) - .parsed() + val videoResponse = app.post("$mainUrl/api/videos/$id/resolve", json = mapOf("blob" to token)) + .parsed() callback.invoke( newExtractorLink( source = name, name = name, - url = videoResponse.signedVideoUrl + url = videoResponse.signedVideoUrl, ) ) } @@ -45,7 +44,6 @@ class Firestream : ExtractorApi() { @Serializable private data class VideoResponse( - @SerialName("signedVideoUrl") - val signedVideoUrl: String, + @JsonProperty("signedVideoUrl") @SerialName("signedVideoUrl") val signedVideoUrl: String, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt index 50042bd19..b58827652 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Flyfile.kt @@ -11,16 +11,16 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable open class Flyfile : ExtractorApi() { - override val name: String = "FlyFile" - override val mainUrl: String = "https://flyfile.app" + override val name = "FlyFile" + override val mainUrl = "https://flyfile.app" + override val requiresReferer = false open val apiUrl: String = "https://api.flyfile.app" - override val requiresReferer: Boolean = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val videoId = url.substringAfterLast("/") val videoInfo = app.get("$apiUrl/api/streaming/assign/$videoId") @@ -32,16 +32,14 @@ open class Flyfile : ExtractorApi() { source = name, name = name, url = streamUrl, - type = ExtractorLinkType.M3U8 + type = ExtractorLinkType.M3U8, ) ) } @Serializable private data class StreamInfo( - @SerialName("url") - val url: String, - @SerialName("token") - val token: String + @SerialName("url") val url: String, + @SerialName("token") val token: String, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt index 1203b2167..8fb0ae046 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GUpload.kt @@ -4,11 +4,14 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode -import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement open class GUpload: ExtractorApi() { override val name: String = "GUpload" @@ -19,12 +22,12 @@ open class GUpload: ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val response = app.get(url, referer = referer).text val playerConfigString = response.substringAfter("const config = ").substringBefore(";") - val playerConfig = AppUtils.parseJson(playerConfigString) + val playerConfig = parseJson(playerConfigString) callback.invoke( newExtractorLink( @@ -39,14 +42,15 @@ open class GUpload: ExtractorApi() { ) } + @Serializable private data class VideoInfo( - @JsonProperty("videoUrl") val videoUrl: String, - @JsonProperty("posterUrl") val posterUrl: String? = null, - @JsonProperty("videoId") val videoId: String? = null, - @JsonProperty("primaryColor") val primaryColor: String? = null, - @JsonProperty("audioTracks") val audioTracks: List = emptyList(), - @JsonProperty("subtitleTracks") val subtitleTracks: List = emptyList(), - @JsonProperty("vastFallbackList") val vastFallbackList: List = emptyList(), - @JsonProperty("videoOwnerId") val videoOwnerId: Long = 0, + @JsonProperty("videoUrl") @SerialName("videoUrl") val videoUrl: String, + @JsonProperty("posterUrl") @SerialName("posterUrl") val posterUrl: String? = null, + @JsonProperty("videoId") @SerialName("videoId") val videoId: String? = null, + @JsonProperty("primaryColor") @SerialName("primaryColor") val primaryColor: String? = null, + @JsonProperty("audioTracks") @SerialName("audioTracks") val audioTracks: List = emptyList(), + @JsonProperty("subtitleTracks") @SerialName("subtitleTracks") val subtitleTracks: List = emptyList(), + @JsonProperty("vastFallbackList") @SerialName("vastFallbackList") val vastFallbackList: List = emptyList(), + @JsonProperty("videoOwnerId") @SerialName("videoOwnerId") val videoOwnerId: Long = 0, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt index f484d766c..10abcce31 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt @@ -5,14 +5,16 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import org.jsoup.nodes.Element class DatabaseGdrive2 : Gdriveplayer() { - override var mainUrl = "https://databasegdriveplayer.co" + override val mainUrl = "https://databasegdriveplayer.co" } class DatabaseGdrive : Gdriveplayer() { - override var mainUrl = "https://series.databasegdriveplayer.co" + override val mainUrl = "https://series.databasegdriveplayer.co" } class Gdriveplayerapi : Gdriveplayer() { @@ -73,10 +75,9 @@ open class Gdriveplayer : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val document = app.get(url).document - val eval = unpackJs(document)?.replace("\\", "") ?: return val data = Regex("data='(\\S+?)'").first(eval) ?: return val password = Regex("null,['|\"](\\w+)['|\"]").first(eval) @@ -84,9 +85,9 @@ open class Gdriveplayer : ExtractorApi() { ?.joinToString("") { it.toInt().toChar().toString() }.let { Regex("var pass = \"(\\S+?)\"").first(it ?: return)?.encodeToByteArray() } - ?: throw ErrorLoadingException("can't find password") - val decryptedData = cryptoAESHandler(data, password, false, false)?.let { getAndUnpack(it) }?.replace("\\", "") + ?: throw ErrorLoadingException("can't find password") + val decryptedData = cryptoAESHandler(data, password, false, false)?.let { getAndUnpack(it) }?.replace("\\", "") val sourceData = decryptedData?.substringAfter("sources:[")?.substringBefore("],") val subData = decryptedData?.substringAfter("tracks:[")?.substringBefore("],") @@ -111,16 +112,17 @@ open class Gdriveplayer : ExtractorApi() { subtitleCallback.invoke( newSubtitleFile( sub.label, - httpsify(sub.file) + httpsify(sub.file), ) ) } } } + @Serializable data class Tracks( - @JsonProperty("file") val file: String, - @JsonProperty("kind") val kind: String, - @JsonProperty("label") val label: String, + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("kind") @SerialName("kind") val kind: String, + @JsonProperty("label") @SerialName("label") val label: String, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gofile.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gofile.kt index 2b42e42c7..9edc54a41 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gofile.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gofile.kt @@ -8,6 +8,8 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlin.math.round open class Gofile : ExtractorApi() { @@ -20,20 +22,18 @@ open class Gofile : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val id = Regex("/(?:\\?c=|d/)([\\da-zA-Z-]+)").find(url)?.groupValues?.get(1) ?: return - val token = app.post( "$mainApi/accounts", ).parsedSafe()?.data?.token ?: return val globalRes = app.get("$mainUrl/dist/js/config.js").text val wt = Regex("""appdata\.wt\s*=\s*[\"']([^\"']+)[\"']""").find(globalRes)?.groupValues?.get(1) ?: return - val headers = mapOf( "Authorization" to "Bearer $token", - "X-Website-Token" to wt + "X-Website-Token" to wt, ) val parsedResponse = app.get( @@ -42,7 +42,6 @@ open class Gofile : ExtractorApi() { ).parsedSafe() val childrenMap = parsedResponse?.data?.children ?: return - for ((_, file) in childrenMap) { if (file.link.isNullOrEmpty() || file.type != "file") continue val fileName = file.name ?: "" @@ -54,7 +53,7 @@ open class Gofile : ExtractorApi() { "Gofile", "[Gofile] $fileName [$formattedSize]", file.link, - ExtractorLinkType.VIDEO + ExtractorLinkType.VIDEO, ) { this.quality = getQuality(fileName) this.headers = mapOf("Cookie" to "accountToken=$token") @@ -84,26 +83,31 @@ open class Gofile : ExtractorApi() { } } + @Serializable data class AccountResponse( - @JsonProperty("data") val data: AccountData? = null + @JsonProperty("data") @SerialName("data") val data: AccountData? = null, ) + @Serializable data class AccountData( - @JsonProperty("token") val token: String? = null + @JsonProperty("token") @SerialName("token") val token: String? = null, ) + @Serializable data class GofileResponse( - @JsonProperty("data") val data: GofileData? = null + @JsonProperty("data") @SerialName("data") val data: GofileData? = null, ) + @Serializable data class GofileData( - @JsonProperty("children") val children: Map? = null + @JsonProperty("children") @SerialName("children") val children: Map? = null, ) + @Serializable data class GofileFile( - @JsonProperty("type") val type: String? = null, - @JsonProperty("name") val name: String? = null, - @JsonProperty("link") val link: String? = null, - @JsonProperty("size") val size: Long? = 0L + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("link") @SerialName("link") val link: String? = null, + @JsonProperty("size") @SerialName("size") val size: Long? = 0L, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt index 1ccd3e4d5..f7fa2bef1 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt @@ -8,39 +8,41 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.extractors.helper.AesHelper import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class HDMomPlayer : ExtractorApi() { - override val name = "HDMomPlayer" - override val mainUrl = "https://hdmomplayer.com" + override val name = "HDMomPlayer" + override val mainUrl = "https://hdmomplayer.com" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { - val m3uLink:String? - val extRef = referer ?: "" - val iSource = app.get(url, referer=extRef).text - + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val m3uLink: String? + val extRef = referer ?: "" + val iSource = app.get(url, referer = extRef).text val bePlayer = Regex("""bePlayer\('([^']+)',\s*'(\{[^\}]+\})'\);""").find(iSource)?.groupValues if (bePlayer != null) { val bePlayerPass = bePlayer.get(1) val bePlayerData = bePlayer.get(2) - val encrypted = AesHelper.cryptoAESHandler(bePlayerData, bePlayerPass.encodeToByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt") - + val encrypted = AesHelper.cryptoAESHandler(bePlayerData, bePlayerPass.encodeToByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt") m3uLink = Regex("""video_location\":\"([^\"]+)""").find(encrypted)?.groupValues?.get(1) } else { m3uLink = Regex("""file:\"([^\"]+)""").find(iSource)?.groupValues?.get(1) - val trackStr = Regex("""tracks:\[([^\]]+)""").find(iSource)?.groupValues?.get(1) if (trackStr != null) { - val tracks:List = parseJson>("[${trackStr}]") - + val tracks = parseJson>("[${trackStr}]") for (track in tracks) { if (track.file == null || track.label == null) continue if (track.label.contains("Forced")) continue - subtitleCallback.invoke( newSubtitleFile( lang = track.label, - url = fixUrl(mainUrl + track.file) + url = fixUrl(mainUrl + track.file), ) ) } @@ -49,10 +51,10 @@ open class HDMomPlayer : ExtractorApi() { callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = m3uLink ?: throw ErrorLoadingException("m3u link not found"), - type = ExtractorLinkType.M3U8 + source = this.name, + name = this.name, + url = m3uLink ?: throw ErrorLoadingException("m3u link not found"), + type = ExtractorLinkType.M3U8, ) { this.referer = url this.quality = Qualities.Unknown.value @@ -60,11 +62,12 @@ open class HDMomPlayer : ExtractorApi() { ) } + @Serializable data class Track( - @JsonProperty("file") val file: String?, - @JsonProperty("label") val label: String?, - @JsonProperty("kind") val kind: String?, - @JsonProperty("language") val language: String?, - @JsonProperty("default") val default: String? + @JsonProperty("file") @SerialName("file") val file: String?, + @JsonProperty("label") @SerialName("label") val label: String?, + @JsonProperty("kind") @SerialName("kind") val kind: String?, + @JsonProperty("language") @SerialName("language") val language: String?, + @JsonProperty("default") @SerialName("default") val default: String?, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDPlayerSystemExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDPlayerSystemExtractor.kt index 9cff2049f..a991528da 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDPlayerSystemExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDPlayerSystemExtractor.kt @@ -2,56 +2,60 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class HDPlayerSystem : ExtractorApi() { - override val name = "HDPlayerSystem" - override val mainUrl = "https://hdplayersystem.live" + override val name = "HDPlayerSystem" + override val mainUrl = "https://hdplayersystem.live" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { - val extRef = referer ?: "" - val vidId = if (url.contains("video/")) { + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val extRef = referer ?: "" + val vidId = if (url.contains("video/")) { url.substringAfter("video/") } else { url.substringAfter("?data=") } - val postUrl = "${mainUrl}/player/index.php?data=${vidId}&do=getVideo" - + val postUrl = "$mainUrl/player/index.php?data=$vidId&do=getVideo" val response = app.post( postUrl, data = mapOf( "hash" to vidId, - "r" to extRef + "r" to extRef, ), referer = extRef, headers = mapOf( - "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", - "X-Requested-With" to "XMLHttpRequest" + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With" to "XMLHttpRequest", ) ) val videoResponse = response.parsedSafe() ?: throw ErrorLoadingException("failed to parse response") - val m3uLink = videoResponse.securedLink - + val m3uLink = videoResponse.securedLink callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = m3uLink - ) { - this.referer = extRef - } + source = this.name, + name = this.name, + url = m3uLink, + ) { this.referer = extRef } ) } + @Serializable data class SystemResponse( - @JsonProperty("hls") val hls: String, - @JsonProperty("videoImage") val videoImage: String? = null, - @JsonProperty("videoSource") val videoSource: String, - @JsonProperty("securedLink") val securedLink: String + @JsonProperty("hls") @SerialName("hls") val hls: String, + @JsonProperty("videoImage") @SerialName("videoImage") val videoImage: String? = null, + @JsonProperty("videoSource") @SerialName("videoSource") val videoSource: String, + @JsonProperty("securedLink") @SerialName("securedLink") val securedLink: String, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Jeniusplay.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Jeniusplay.kt index 896228b51..9f66867f6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Jeniusplay.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Jeniusplay.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.getAndUnpack import com.lagradost.cloudstream3.utils.getPacked +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Jeniusplay : ExtractorApi() { override val name = "Jeniusplay" @@ -19,16 +21,15 @@ open class Jeniusplay : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val document = app.get(url, referer = "$mainUrl/").document val hash = url.split("/").last().substringAfter("data=") - val m3uLink = app.post( url = "$mainUrl/player/index.php?data=$hash&do=getVideo", data = mapOf("hash" to hash, "r" to "$referer"), referer = url, - headers = mapOf("X-Requested-With" to "XMLHttpRequest") + headers = mapOf("X-Requested-With" to "XMLHttpRequest"), ).parsed().videoSource M3u8Helper.generateM3u8( @@ -36,7 +37,6 @@ open class Jeniusplay : ExtractorApi() { m3uLink, url, ).forEach(callback) - document.select("script").map { script -> if (getPacked(script.data()) != null) { val unpacked = getAndUnpack(script.data()) @@ -45,9 +45,10 @@ open class Jeniusplay : ExtractorApi() { } } + @Serializable data class ResponseSource( - @JsonProperty("hls") val hls: Boolean, - @JsonProperty("videoSource") val videoSource: String, - @JsonProperty("securedLink") val securedLink: String?, + @JsonProperty("hls") @SerialName("hls") val hls: Boolean, + @JsonProperty("videoSource") @SerialName("videoSource") val videoSource: String, + @JsonProperty("securedLink") @SerialName("securedLink") val securedLink: String?, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Linkbox.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Linkbox.kt index bfa94326a..91b34ac2c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Linkbox.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Linkbox.kt @@ -7,6 +7,8 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Linkbox : ExtractorApi() { override val name = "Linkbox" @@ -17,7 +19,7 @@ open class Linkbox : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val token = Regex("""(?:/f/|/file/|\?id=)(\w+)""").find(url)?.groupValues?.get(1) val id = app.get("$mainUrl/api/file/share_out_list/?sortField=utime&sortAsc=0&pageNo=1&pageSize=50&shareToken=$token").parsedSafe()?.data?.itemId @@ -36,22 +38,25 @@ open class Linkbox : ExtractorApi() { } } + @Serializable data class Resolutions( - @JsonProperty("url") val url: String? = null, - @JsonProperty("resolution") val resolution: String? = null, + @JsonProperty("url") @SerialName("url") val url: String? = null, + @JsonProperty("resolution") @SerialName("resolution") val resolution: String? = null, ) + @Serializable data class ItemInfo( - @JsonProperty("resolutionList") val resolutionList: ArrayList? = arrayListOf(), + @JsonProperty("resolutionList") @SerialName("resolutionList") val resolutionList: ArrayList? = arrayListOf(), ) + @Serializable data class Data( - @JsonProperty("itemInfo") val itemInfo: ItemInfo? = null, - @JsonProperty("itemId") val itemId: String? = null, + @JsonProperty("itemInfo") @SerialName("itemInfo") val itemInfo: ItemInfo? = null, + @JsonProperty("itemId") @SerialName("itemId") val itemId: String? = null, ) + @Serializable data class Responses( - @JsonProperty("data") val data: Data? = null, + @JsonProperty("data") @SerialName("data") val data: Data? = null, ) - -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/MailRuExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/MailRuExtractor.kt index f1fd1288c..899c6ced5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/MailRuExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/MailRuExtractor.kt @@ -2,51 +2,57 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class MailRu : ExtractorApi() { - override val name = "MailRu" - override val mainUrl = "https://my.mail.ru" + override val name = "MailRu" + override val mainUrl = "https://my.mail.ru" override val requiresReferer = false - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { val extRef = referer ?: "" - - val vidId = url.substringAfter("video/embed/").trim() - val videoReq = app.get("${mainUrl}/+/video/meta/${vidId}", referer=url) - val videoKey = videoReq.cookies["video_key"].toString() - - val videoData = AppUtils.tryParseJson(videoReq.text) ?: throw ErrorLoadingException("Video not found") + val vidId = url.substringAfter("video/embed/").trim() + val videoReq = app.get("$mainUrl/+/video/meta/$vidId", referer = url) + val videoKey = videoReq.cookies["video_key"].toString() + val videoData = AppUtils.tryParseJson(videoReq.text) ?: + throw ErrorLoadingException("Video not found") for (video in videoData.videos) { - val videoUrl = if (video.url.startsWith("//")) "https:${video.url}" else video.url - callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = videoUrl, - type = ExtractorLinkType.M3U8 + source = this.name, + name = this.name, + url = videoUrl, + type = ExtractorLinkType.M3U8, ) { this.referer = url - this.headers = mapOf("Cookie" to "video_key=${videoKey}") + this.headers = mapOf("Cookie" to "video_key=$videoKey") this.quality = getQualityFromName(video.key) } ) } } + @Serializable data class MailRuData( - @JsonProperty("provider") val provider: String, - @JsonProperty("videos") val videos: List + @JsonProperty("provider") @SerialName("provider") val provider: String, + @JsonProperty("videos") @SerialName("videos") val videos: List, ) + @Serializable data class MailRuVideoData( - @JsonProperty("url") val url: String, - @JsonProperty("key") val key: String + @JsonProperty("url") @SerialName("url") val url: String, + @JsonProperty("key") @SerialName("key") val key: String, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OdnoklassnikiExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OdnoklassnikiExtractor.kt index 69c3b7759..fd845e1a0 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OdnoklassnikiExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OdnoklassnikiExtractor.kt @@ -7,19 +7,26 @@ import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Odnoklassniki : ExtractorApi() { - override val name = "Odnoklassniki" - override val mainUrl = "https://odnoklassniki.ru" + override val name = "Odnoklassniki" + override val mainUrl = "https://odnoklassniki.ru" override val requiresReferer = false - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { val headers = mapOf( "Accept" to "*/*", "Connection" to "keep-alive", @@ -29,34 +36,33 @@ open class Odnoklassniki : ExtractorApi() { "Origin" to mainUrl, "User-Agent" to USER_AGENT, ) - val embedUrl = url.replace("/video/","/videoembed/") - val videoReq = app.get(embedUrl, headers=headers).text.replace("\\"", "\"").replace("\\\\", "\\") + + val embedUrl = url.replace("/video/", "/videoembed/") + val videoReq = app.get(embedUrl, headers = headers).text.replace("\\"", "\"").replace("\\\\", "\\") .replace(Regex("\\\\u([0-9A-Fa-f]{4})")) { matchResult -> matchResult.groupValues[1].toInt(16).toChar().toString() } + val videosStr = Regex(""""videos":(\[[^]]*])""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("Video not found") - val videos = AppUtils.tryParseJson>(videosStr) ?: throw ErrorLoadingException("Video not found") - + val videos = tryParseJson>(videosStr) ?: throw ErrorLoadingException("Video not found") for (video in videos) { - - val videoUrl = if (video.url.startsWith("//")) "https:${video.url}" else video.url - - val quality = video.name.uppercase() + val videoUrl = if (video.url.startsWith("//")) "https:${video.url}" else video.url + val quality = video.name.uppercase() .replace("MOBILE", "144p") .replace("LOWEST", "240p") - .replace("LOW", "360p") - .replace("SD", "480p") - .replace("HD", "720p") - .replace("FULL", "1080p") - .replace("QUAD", "1440p") - .replace("ULTRA", "4k") + .replace("LOW", "360p") + .replace("SD", "480p") + .replace("HD", "720p") + .replace("FULL", "1080p") + .replace("QUAD", "1440p") + .replace("ULTRA", "4k") callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = videoUrl, - type = INFER_TYPE + source = this.name, + name = this.name, + url = videoUrl, + type = INFER_TYPE, ) { this.referer = "$mainUrl/" this.quality = getQualityFromName(quality) @@ -66,8 +72,9 @@ open class Odnoklassniki : ExtractorApi() { } } + @Serializable data class OkRuVideo( - @JsonProperty("name") val name: String, - @JsonProperty("url") val url: String, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("url") @SerialName("url") val url: String, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PeaceMakerstExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PeaceMakerstExtractor.kt index f96387d75..7dd12d690 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PeaceMakerstExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PeaceMakerstExtractor.kt @@ -2,57 +2,63 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class PeaceMakerst : ExtractorApi() { - override val name = "PeaceMakerst" - override val mainUrl = "https://peacemakerst.com" + override val name = "PeaceMakerst" + override val mainUrl = "https://peacemakerst.com" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { - val m3uLink:String? - val extRef = referer ?: "" - val postUrl = "${url}?do=getVideo" - + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val m3uLink: String? + val extRef = referer ?: "" + val postUrl = "$url?do=getVideo" val response = app.post( postUrl, data = mapOf( "hash" to url.substringAfter("video/"), - "r" to extRef, - "s" to "" + "r" to extRef, + "s" to "", ), referer = extRef, headers = mapOf( - "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", - "X-Requested-With" to "XMLHttpRequest" - ) + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With" to "XMLHttpRequest", + ), ) - if (response.text.contains("teve2.com.tr\\/embed\\/")) { - val teve2Id = response.text.substringAfter("teve2.com.tr\\/embed\\/").substringBefore("\"") - val teve2Response = app.get( - "https://www.teve2.com.tr/action/media/${teve2Id}", - referer = "https://www.teve2.com.tr/embed/${teve2Id}" - ).parsedSafe() ?: throw ErrorLoadingException("teve2 response is null") - m3uLink = teve2Response.media.link.serviceUrl + "//" + teve2Response.media.link.securePath + if (response.text.contains("teve2.com.tr\\/embed\\/")) { + val teve2Id = response.text.substringAfter("teve2.com.tr\\/embed\\/").substringBefore("\"") + val teve2Response = app.get( + "https://www.teve2.com.tr/action/media/$teve2Id", + referer = "https://www.teve2.com.tr/embed/$teve2Id", + ).parsedSafe() ?: throw ErrorLoadingException("teve2 response is null") + m3uLink = teve2Response.media.link.serviceUrl + "//" + teve2Response.media.link.securePath } else { val videoResponse = response.parsedSafe() ?: throw ErrorLoadingException("peace response is null") - val videoSources = videoResponse.videoSources - if (videoSources.isNotEmpty()) { - m3uLink = videoSources.lastOrNull()?.file + val videoSources = videoResponse.videoSources + m3uLink = if (videoSources.isNotEmpty()) { + videoSources.lastOrNull()?.file } else { - m3uLink = null + null } } callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = m3uLink ?: throw ErrorLoadingException("m3u link not found"), + source = this.name, + name = this.name, + url = m3uLink ?: throw ErrorLoadingException("m3u link not found"), ) { this.referer = extRef this.quality = Qualities.Unknown.value @@ -60,29 +66,34 @@ open class PeaceMakerst : ExtractorApi() { ) } + @Serializable data class PeaceResponse( - @JsonProperty("videoImage") val videoImage: String?, - @JsonProperty("videoSources") val videoSources: List, - @JsonProperty("sIndex") val sIndex: String, - @JsonProperty("sourceList") val sourceList: Map + @JsonProperty("videoImage") @SerialName("videoImage") val videoImage: String?, + @JsonProperty("videoSources") @SerialName("videoSources") val videoSources: List, + @JsonProperty("sIndex") @SerialName("sIndex") val sourceIndex: String, + @JsonProperty("sourceList") @SerialName("sourceList") val sourceList: Map, ) + @Serializable data class VideoSource( - @JsonProperty("file") val file: String, - @JsonProperty("label") val label: String, - @JsonProperty("type") val type: String + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("label") @SerialName("label") val label: String, + @JsonProperty("type") @SerialName("type") val type: String, ) + @Serializable data class Teve2ApiResponse( - @JsonProperty("Media") val media: Teve2Media + @JsonProperty("Media") @SerialName("Media") val media: Teve2Media, ) + @Serializable data class Teve2Media( - @JsonProperty("Link") val link: Teve2Link + @JsonProperty("Link") @SerialName("Link") val link: Teve2Link, ) + @Serializable data class Teve2Link( - @JsonProperty("ServiceUrl") val serviceUrl: String, - @JsonProperty("SecurePath") val securePath: String + @JsonProperty("ServiceUrl") @SerialName("ServiceUrl") val serviceUrl: String, + @JsonProperty("SecurePath") @SerialName("SecurePath") val securePath: String, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt index c59022abb..a5e71b619 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt @@ -1,33 +1,28 @@ package com.lagradost.cloudstream3.extractors -import com.lagradost.api.Log import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.api.Log import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -open class PlayLtXyz: ExtractorApi() { - override val name: String = "PlayLt" - override val mainUrl: String = "https://play.playlt.xyz" +open class PlayLtXyz : ExtractorApi() { + override val name = "PlayLt" + override val mainUrl = "https://play.playlt.xyz" override val requiresReferer = true - private data class ResponseData( - @JsonProperty("data") val data: String? = null - ) - override suspend fun getUrl(url: String, referer: String?): List { val extractedLinksList = mutableListOf() - //Log.i(this.name, "Result => (url) $url") var idUser = "" var idFile = "" var bodyText = "" val doc = app.get(url, referer = referer).document - //Log.i(this.name, "Result => (url, script) $url / ${doc.select("script")}") bodyText = doc.select("script").firstOrNull { val text = it.toString() text.contains("var idUser") }?.toString() ?: "" - //Log.i(this.name, "Result => (bodyText) $bodyText") if (bodyText.isNotBlank()) { idUser = "(?<=var idUser = \")(.*)(?=\";)".toRegex().find(bodyText) ?.groupValues?.get(0) ?: "" @@ -35,29 +30,27 @@ open class PlayLtXyz: ExtractorApi() { idFile = "(?<=var idfile = \")(.*)(?=\";)".toRegex().find(bodyText) ?.groupValues?.get(0) ?: "" } - //Log.i(this.name, "Result => (idUser, idFile) $idUser / $idFile") + if (idUser.isNotBlank() && idFile.isNotBlank()) { - //val sess = HttpSession() val ajaxHead = mapOf( Pair("Origin", mainUrl), Pair("Referer", mainUrl), Pair("Sec-Fetch-Site", "same-site"), Pair("Sec-Fetch-Mode", "cors"), - Pair("Sec-Fetch-Dest", "empty") - ) - val ajaxData = mapOf( - Pair("referrer", referer ?: mainUrl), - Pair("typeend", "html") + Pair("Sec-Fetch-Dest", "empty"), ) - //idUser = 608f7c85cf0743547f1f1b4e - val posturl = "https://api-plhq.playlt.xyz/apiv5/$idUser/$idFile" - val data = app.post(posturl, headers = ajaxHead, data = ajaxData) - //Log.i(this.name, "Result => (posturl) $posturl") + val ajaxData = mapOf( + Pair("referrer", referer ?: mainUrl), + Pair("typeend", "html"), + ) + + val postUrl = "https://api-plhq.playlt.xyz/apiv5/$idUser/$idFile" + val data = app.post(postUrl, headers = ajaxHead, data = ajaxData) if (data.isSuccessful) { - val itemstr = data.text - Log.i(this.name, "Result => (data) $itemstr") - tryParseJson(itemstr)?.let { item -> + val itemStr = data.text + Log.i(this.name, "Result => (data) $itemStr") + tryParseJson(itemStr)?.let { item -> val linkUrl = item.data ?: "" if (linkUrl.isNotBlank()) { extractedLinksList.add( @@ -65,7 +58,7 @@ open class PlayLtXyz: ExtractorApi() { source = name, name = name, url = linkUrl, - type = ExtractorLinkType.M3U8 + type = ExtractorLinkType.M3U8, ) { this.referer = url this.quality = Qualities.Unknown.value @@ -75,6 +68,12 @@ open class PlayLtXyz: ExtractorApi() { } } } + return extractedLinksList } -} \ No newline at end of file + + @Serializable + private data class ResponseData( + @JsonProperty("data") @SerialName("data") val data: String? = null, + ) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt index fdd559258..46b83f10a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -7,8 +7,8 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64DecodeArray import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.newSubtitleFile -import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper @@ -16,6 +16,8 @@ import dev.whyoleg.cryptography.CryptographyProvider import dev.whyoleg.cryptography.DelicateCryptographyApi import dev.whyoleg.cryptography.algorithms.AES import dev.whyoleg.cryptography.algorithms.MD5 +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class Megacloud : Rabbitstream() { override val name = "Megacloud" @@ -36,6 +38,7 @@ class Megacloud : Rabbitstream() { extractedKey += sourcesArray[i].toString() sourcesArray[i] = ' ' } + currentIndex += index[1] } @@ -55,7 +58,7 @@ class Megacloud : Rabbitstream() { val matchKey2 = matchingKey(match.groupValues[2]) try { listOf(matchKey1.toInt(16), matchKey2.toInt(16)) - } catch (e: NumberFormatException) { + } catch (_: NumberFormatException) { emptyList() } }.filter { it.isNotEmpty() } @@ -86,26 +89,25 @@ open class Rabbitstream : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val id = url.substringAfterLast("/").substringBefore("?") - val response = app.get( "$mainUrl/$embed/getSources?id=$id", referer = mainUrl, - headers = mapOf("X-Requested-With" to "XMLHttpRequest") + headers = mapOf("X-Requested-With" to "XMLHttpRequest"), ) val encryptedMap = response.parsedSafe() val sources = encryptedMap?.sources val decryptedSources = if (sources == null || encryptedMap.encrypted == false) { - response.parsedSafe() + response.parsedSafe() } else { val (key, encData) = extractRealKey(sources) - val decrypted = decryptMapped>(encData, key) + val decrypted = tryParseJson>(decrypt(encData, key)) SourcesResponses( sources = decrypted, - tracks = encryptedMap.tracks + tracks = encryptedMap.tracks, ) } @@ -121,7 +123,7 @@ open class Rabbitstream : ExtractorApi() { subtitleCallback.invoke( newSubtitleFile( track?.label ?: return@map, - track.file ?: return@map + track.file ?: return@map, ) ) } @@ -133,16 +135,11 @@ open class Rabbitstream : ExtractorApi() { return extractedKey to sources } - private inline suspend fun decryptMapped(input: String, key: String): T? { - val decrypt = decrypt(input, key) - return AppUtils.tryParseJson(decrypt) - } - private suspend fun decrypt(input: String, key: String): String { return decryptSourceUrl( generateKey( salt = base64DecodeArray(input).copyOfRange(8, 16), - secret = key.encodeToByteArray() + secret = key.encodeToByteArray(), ), input ) } @@ -154,6 +151,7 @@ open class Rabbitstream : ExtractorApi() { key = md5(key + secret + salt) currentKey += key } + return currentKey } @@ -172,26 +170,30 @@ open class Rabbitstream : ExtractorApi() { return decryptedData.decodeToString() } + @Serializable data class Tracks( - @JsonProperty("file") val file: String? = null, - @JsonProperty("label") val label: String? = null, - @JsonProperty("kind") val kind: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, + @JsonProperty("kind") @SerialName("kind") val kind: String? = null, ) + @Serializable data class Sources( - @JsonProperty("file") val file: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("label") val label: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, ) + @Serializable data class SourcesResponses( - @JsonProperty("sources") val sources: List? = emptyList(), - @JsonProperty("tracks") val tracks: List? = emptyList(), + @JsonProperty("sources") @SerialName("sources") val sources: List? = emptyList(), + @JsonProperty("tracks") @SerialName("tracks") val tracks: List? = emptyList(), ) + @Serializable data class SourcesEncrypted( - @JsonProperty("sources") val sources: String? = null, - @JsonProperty("encrypted") val encrypted: Boolean? = null, - @JsonProperty("tracks") val tracks: List? = emptyList(), + @JsonProperty("sources") @SerialName("sources") val sources: String? = null, + @JsonProperty("encrypted") @SerialName("encrypted") val encrypted: Boolean? = null, + @JsonProperty("tracks") @SerialName("tracks") val tracks: List? = emptyList(), ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/SobreatsesuypExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/SobreatsesuypExtractor.kt index be95940e0..0c5d7c974 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/SobreatsesuypExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/SobreatsesuypExtractor.kt @@ -2,51 +2,53 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement open class Sobreatsesuyp : ExtractorApi() { - override val name = "Sobreatsesuyp" - override val mainUrl = "https://sobreatsesuyp.com" + override val name = "Sobreatsesuyp" + override val mainUrl = "https://sobreatsesuyp.com" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { val extRef = referer ?: "" - val videoReq = app.get(url, referer = extRef).text - - val file = Regex("""file\":\"([^\"]+)""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found") - val postLink = "${mainUrl}/" + file.replace("\\", "") - val rawList = app.post(postLink, referer = extRef).parsedSafe>() ?: throw ErrorLoadingException("Post link not found") - + val file = Regex("""file\":\"([^\"]+)""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found") + val postLink = "$mainUrl/" + file.replace("\\", "") + val rawList = app.post(postLink, referer = extRef).parsedSafe>() ?: throw ErrorLoadingException("Post link not found") val postJson: List = rawList.drop(1).map { item -> val mapItem = item as Map<*, *> SobreatsesuypVideoData( title = mapItem["title"] as? String, - file = mapItem["file"] as? String + file = mapItem["file"] as? String, ) } for (item in postJson) { if (item.file == null || item.title == null) continue - - val videoData = app.post("${mainUrl}/playlist/${item.file.substring(1)}.txt", referer = extRef).text - + val videoData = app.post("$mainUrl/playlist/${item.file.substring(1)}.txt", referer = extRef).text callback.invoke( newExtractorLink( - source = this.name, - name = "${this.name} - ${item.title}", - url = videoData, - ) { - this.referer = extRef - } + source = this.name, + name = "${this.name} - ${item.title}", + url = videoData, + ) { this.referer = extRef } ) } } + @Serializable data class SobreatsesuypVideoData( - @JsonProperty("title") val title: String? = null, - @JsonProperty("file") val file: String? = null + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamEmbed.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamEmbed.kt index c89f4d226..8c5eb5d6c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamEmbed.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamEmbed.kt @@ -7,22 +7,23 @@ import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class StreamEmbed : ExtractorApi() { - override var name = "StreamEmbed" - override var mainUrl = "https://watch.gxplayer.xyz" + override val name = "StreamEmbed" + override val mainUrl = "https://watch.gxplayer.xyz" override val requiresReferer = true override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val jsonString = app.get(url, referer = mainUrl).text .substringAfter("var video = ").substringBefore(";") val video = parseJson
(jsonString) - M3u8Helper.generateM3u8( this.name, "$mainUrl/m3u8/${video.uid}/${video.md5}/master.txt?s=1&id=${video.id}&cache=${video.status}", @@ -30,14 +31,15 @@ open class StreamEmbed : ExtractorApi() { ).forEach(callback) } + @Serializable private data class Details( - @JsonProperty("id") val id: String, - @JsonProperty("uid") val uid: String, - @JsonProperty("slug") val slug: String, - @JsonProperty("title") val title: String, - @JsonProperty("quality") val quality: String, - @JsonProperty("type") val type: String, - @JsonProperty("status") val status: String, - @JsonProperty("md5") val md5: String, + @JsonProperty("id") @SerialName("id") val id: String, + @JsonProperty("uid") @SerialName("uid") val uid: String, + @JsonProperty("slug") @SerialName("slug") val slug: String, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("quality") @SerialName("quality") val quality: String, + @JsonProperty("type") @SerialName("type") val type: String, + @JsonProperty("status") @SerialName("status") val status: String, + @JsonProperty("md5") @SerialName("md5") val md5: String, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamSB.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamSB.kt index 67cf1f8da..a7584d708 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamSB.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/StreamSB.kt @@ -7,6 +7,8 @@ import com.lagradost.cloudstream3.newSubtitleFile import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlin.random.Random class Sblona : StreamSB() { @@ -141,17 +143,15 @@ open class StreamSB : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { - val regexID = + val regexId = Regex("(embed-[a-zA-Z\\d]{0,8}[a-zA-Z\\d_-]+|/e/[a-zA-Z\\d]{0,8}[a-zA-Z\\d_-]+)") - val id = regexID.findAll(url).map { + val id = regexId.findAll(url).map { it.value.replace(Regex("(embed-|/e/)"), "") }.first() val master = "$mainUrl/375664356a494546326c4b797c7c6e756577776778623171737/${encodeId(id)}" - val headers = mapOf( - "watchsb" to "sbstream", - ) + val headers = mapOf("watchsb" to "sbstream") val mapped = app.get( master.lowercase(), headers = headers, @@ -161,9 +161,8 @@ open class StreamSB : ExtractorApi() { name, mapped?.streamData?.file ?: return, url, - headers = headers + headers = headers, ).forEach(callback) - mapped.streamData.subs?.map {sub -> subtitleCallback.invoke( newSubtitleFile( @@ -189,25 +188,27 @@ open class StreamSB : ExtractorApi() { } } - data class Subs ( - @JsonProperty("file") val file: String? = null, - @JsonProperty("label") val label: String? = null, + @Serializable + data class Subs( + @JsonProperty("file") @SerialName("file") val file: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, ) - data class StreamData ( - @JsonProperty("file") val file: String, - @JsonProperty("cdn_img") val cdnImg: String, - @JsonProperty("hash") val hash: String, - @JsonProperty("subs") val subs: ArrayList? = arrayListOf(), - @JsonProperty("length") val length: String, - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonProperty("backup") val backup: String, + @Serializable + data class StreamData( + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("cdn_img") @SerialName("cdn_img") val cdnImg: String, + @JsonProperty("hash") @SerialName("hash") val hash: String, + @JsonProperty("subs") @SerialName("subs") val subs: ArrayList? = arrayListOf(), + @JsonProperty("length") @SerialName("length") val length: String, + @JsonProperty("id") @SerialName("id") val id: String, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("backup") @SerialName("backup") val backup: String, ) - data class Main ( - @JsonProperty("stream_data") val streamData: StreamData, - @JsonProperty("status_code") val statusCode: Int, + @Serializable + data class Main( + @JsonProperty("stream_data") @SerialName("stream_data") val streamData: StreamData, + @JsonProperty("status_code") @SerialName("status_code") val statusCode: Int, ) - } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamlare.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamlare.kt index da2dd62be..d17e59c82 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamlare.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamlare.kt @@ -9,10 +9,11 @@ import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.newExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.nicehttp.RequestBodyTypes +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody - class Streamlare : Slmaxed() { override val mainUrl = "https://streamlare.com/" } @@ -24,27 +25,11 @@ open class Slmaxed : ExtractorApi() { // https://slmaxed.com/e/oLvgezw3LjPzbp8E -> oLvgezw3LjPzbp8E val embedRegex = Regex("""/e/([^/]*)""") - - - data class JsonResponse( - @JsonProperty val status: String? = null, - @JsonProperty val message: String? = null, - @JsonProperty val type: String? = null, - @JsonProperty val token: String? = null, - @JsonProperty val result: Map? = null - ) - - data class Result( - @JsonProperty val label: String? = null, - @JsonProperty val file: String? = null, - @JsonProperty val type: String? = null - ) - override suspend fun getUrl(url: String, referer: String?): List? { val id = embedRegex.find(url)!!.groupValues[1] val json = app.post( "${mainUrl}api/video/stream/get", - requestBody = """{"id":"$id"}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) + requestBody = """{"id":"$id"}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()), ).parsed() return json.result?.mapNotNull { it.value.let { result -> @@ -52,18 +37,29 @@ open class Slmaxed : ExtractorApi() { this.name, this.name, result.file ?: return@mapNotNull null, - type = if (result.type?.contains( - "hls", - ignoreCase = true - ) == true - ) ExtractorLinkType.M3U8 else INFER_TYPE + type = if (result.type?.contains("hls", ignoreCase = true) == true) ExtractorLinkType.M3U8 else INFER_TYPE, ) { this.referer = url - this.quality = - result.label?.replace("p", "", ignoreCase = true)?.trim()?.toIntOrNull() - ?: Qualities.Unknown.value + this.quality = result.label?.replace("p", "", ignoreCase = true)?.trim()?.toIntOrNull() + ?: Qualities.Unknown.value } } } } -} \ No newline at end of file + + @Serializable + data class JsonResponse( + @JsonProperty("status") @SerialName("status") val status: String? = null, + @JsonProperty("message") @SerialName("message") val message: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("token") @SerialName("token") val token: String? = null, + @JsonProperty("result") @SerialName("result") val result: Map? = null, + ) + + @Serializable + data class Result( + @JsonProperty("label") @SerialName("label") val label: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + ) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt index 9886300aa..1bcf9966b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamplay.kt @@ -8,6 +8,8 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import io.ktor.http.Url +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Streamplay : ExtractorApi() { override val name = "Streamplay" @@ -18,37 +20,36 @@ open class Streamplay : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val request = app.get(url, referer = referer) val redirectUrl = request.url val mainServer = Url(redirectUrl).let { "${it.protocol.name}://${it.host}" } val key = redirectUrl.substringAfter("embed-").substringBefore(".html") - val token = - request.document.select("script").find { it.data().contains("sitekey:") }?.data() - ?.substringAfterLast("sitekey: '")?.substringBefore("',")?.let { captchaKey -> - getCaptchaToken( - redirectUrl, - captchaKey, - referer = "$mainServer/" - ) - } ?: throw ErrorLoadingException("can't bypass captcha") + val token = request.document.select("script").find { it.data().contains("sitekey:") }?.data() + ?.substringAfterLast("sitekey: '")?.substringBefore("',")?.let { captchaKey -> + getCaptchaToken( + redirectUrl, + captchaKey, + referer = "$mainServer/", + ) + } ?: throw ErrorLoadingException("can't bypass captcha") + app.post( "$mainServer/player-$key-488x286.html", data = mapOf( "op" to "embed", - "token" to token + "token" to token, ), referer = redirectUrl, headers = mapOf( "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", - "Content-Type" to "application/x-www-form-urlencoded" - ) + "Content-Type" to "application/x-www-form-urlencoded", + ), ).document.select("script").find { script -> script.data().contains("eval(function(p,a,c,k,e,d)") }?.let { val data = getAndUnpack(it.data()).substringAfter("sources=[").substringBefore(",desc") - .replace("file", "\"file\"") - .replace("label", "\"label\"") + .replace("file", "\"file\"").replace("label", "\"label\"") tryParseJson>("[$data}]")?.map { res -> callback.invoke( newExtractorLink( @@ -57,24 +58,21 @@ open class Streamplay : ExtractorApi() { res.file ?: return@map null, ) { this.referer = "$mainServer/" + this.headers = mapOf("Range" to "bytes=0-") this.quality = when (res.label) { "HD" -> Qualities.P720.value "SD" -> Qualities.P480.value else -> Qualities.Unknown.value } - this.headers = mapOf( - "Range" to "bytes=0-" - ) } ) } } - } + @Serializable data class Source( - @JsonProperty("file") val file: String? = null, - @JsonProperty("label") val label: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, ) - } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt index 1348f74d5..6014105d8 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt @@ -2,68 +2,68 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement open class TRsTX : ExtractorApi() { - override val name = "TRsTX" - override val mainUrl = "https://trstx.org" + override val name = "TRsTX" + override val mainUrl = "https://trstx.org" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { val extRef = referer ?: "" - - val videoReq = app.get(url, referer=extRef).text - - val file = Regex("""file\":\"([^\"]+)""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found") - val postLink = "${mainUrl}/" + file.replace("\\", "") - val rawList = app.post(postLink, referer=extRef).parsedSafe>() ?: throw ErrorLoadingException("Post link not found") - + val videoReq = app.get(url, referer = extRef).text + val file = Regex("""file\":\"([^\"]+)""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found") + val postLink = "$mainUrl/" + file.replace("\\", "") + val rawList = app.post(postLink, referer = extRef).parsedSafe>() ?: throw ErrorLoadingException("Post link not found") val postJson: List = rawList.drop(1).map { item -> val mapItem = item as Map<*, *> TrstxVideoData( title = mapItem["title"] as? String, - file = mapItem["file"] as? String + file = mapItem["file"] as? String, ) } val vidLinks = mutableSetOf() - val vidMap = mutableListOf>() + val vidMap = mutableListOf>() for (item in postJson) { if (item.file == null || item.title == null) continue - - val fileUrl = "${mainUrl}/playlist/" + item.file.substring(1) + ".txt" - val videoData = app.post(fileUrl, referer=extRef).text - - if (videoData in vidLinks) { continue } + val fileUrl = "$mainUrl/playlist/" + item.file.substring(1) + ".txt" + val videoData = app.post(fileUrl, referer = extRef).text + if (videoData in vidLinks) continue vidLinks.add(videoData) - vidMap.add(mapOf( - "title" to item.title, - "videoData" to videoData + "title" to item.title, + "videoData" to videoData, )) } for (mapEntry in vidMap) { - val title = mapEntry["title"] ?: continue + val title = mapEntry["title"] ?: continue val m3uLink = mapEntry["videoData"] ?: continue - callback.invoke( newExtractorLink( - source = this.name, - name = "${this.name} - ${title}", - url = m3uLink, - ) { - this.referer = extRef - } + source = this.name, + name = "${this.name} - $title", + url = m3uLink, + ) { this.referer = extRef } ) } } + @Serializable data class TrstxVideoData( - @JsonProperty("title") val title: String? = null, - @JsonProperty("file") val file: String? = null + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Tantifilm.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Tantifilm.kt index 4e330d1a7..e8022b323 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Tantifilm.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Tantifilm.kt @@ -6,38 +6,42 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Tantifilm : ExtractorApi() { - override var name = "Tantifilm" - override var mainUrl = "https://cercafilm.net" + override val name = "Tantifilm" + override val mainUrl = "https://cercafilm.net" override val requiresReferer = false - data class TantifilmJsonData ( - @JsonProperty("success") val success : Boolean, - @JsonProperty("data") val data : List, - @JsonProperty("captions")val captions : List, - @JsonProperty("is_vr") val is_vr : Boolean - ) - - data class TantifilmData ( - @JsonProperty("file") val file : String, - @JsonProperty("label") val label : String, - @JsonProperty("type") val type : String - ) - override suspend fun getUrl(url: String, referer: String?): List? { val link = "$mainUrl/api/source/${url.substringAfterLast("/")}" val response = app.post(link).text.replace("""\""","") - val jsonvideodata = parseJson(response) - return jsonvideodata.data.map { + val jsonVideoData = parseJson(response) + return jsonVideoData.data.map { newExtractorLink( this.name, this.name, - it.file+".${it.type}" + it.file + ".${it.type}", ) { this.referer = mainUrl this.quality = it.label.filter{ it.isDigit() }.toInt() } } } -} \ No newline at end of file + + @Serializable + data class TantifilmJsonData( + @JsonProperty("success") @SerialName("success") val success: Boolean, + @JsonProperty("data") @SerialName("data") val data: List, + @JsonProperty("captions") @SerialName("captions") val captions: List, + @JsonProperty("is_vr") @SerialName("is_vr") val isVr: Boolean, + ) + + @Serializable + data class TantifilmData( + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("label") @SerialName("label") val label: String, + @JsonProperty("type") @SerialName("type") val type: String, + ) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TauVideoExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TauVideoExtractor.kt index 1c9cce2a8..9133ca99c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TauVideoExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TauVideoExtractor.kt @@ -2,29 +2,35 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class TauVideo : ExtractorApi() { - override val name = "TauVideo" - override val mainUrl = "https://tau-video.xyz" + override val name = "TauVideo" + override val mainUrl = "https://tau-video.xyz" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { - val extRef = referer ?: "" + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val extRef = referer ?: "" val videoKey = url.split("/").last() - val videoUrl = "${mainUrl}/api/video/${videoKey}" - + val videoUrl = "$mainUrl/api/video/$videoKey" val api = app.get(videoUrl).parsedSafe() ?: throw ErrorLoadingException("TauVideo") for (video in api.urls) { callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = video.url, + source = this.name, + name = this.name, + url = video.url, ) { this.referer = extRef this.quality = getQualityFromName(video.label) @@ -33,12 +39,14 @@ open class TauVideo : ExtractorApi() { } } + @Serializable data class TauVideoUrls( - @JsonProperty("urls") val urls: List + @JsonProperty("urls") @SerialName("urls") val urls: List, ) + @Serializable data class TauVideoData( - @JsonProperty("url") val url: String, - @JsonProperty("label") val label: String, + @JsonProperty("url") @SerialName("url") val url: String, + @JsonProperty("label") @SerialName("label") val label: String, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Uservideo.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Uservideo.kt index 307825998..97922127d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Uservideo.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Uservideo.kt @@ -3,51 +3,51 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Uservideo : ExtractorApi() { - override val name: String = "Uservideo" - override val mainUrl: String = "https://uservideo.xyz" + override val name = "Uservideo" + override val mainUrl = "https://uservideo.xyz" override val requiresReferer = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val script = app.get(url).document.selectFirst("script:containsData(hosts =)")?.data() val host = script?.substringAfter("hosts = [\"")?.substringBefore("\"];") val servers = script?.substringAfter("servers = \"")?.substringBefore("\";") - - val sources = app.get("$host/s/$servers").text.substringAfter("\"sources\":[").substringBefore("],").let { - AppUtils.tryParseJson>("[$it]") - } val quality = Regex("(\\d{3,4})[Pp]").find(url)?.groupValues?.getOrNull(1)?.toIntOrNull() + val sources = app.get("$host/s/$servers").text.substringAfter("\"sources\":[").substringBefore("],").let { + tryParseJson>("[$it]") + } sources?.map { source -> callback.invoke( newExtractorLink( name, name, - source.src ?: return@map null + source.src ?: return@map null, ) { this.referer = url this.quality = quality ?: Qualities.Unknown.value } ) } - } + @Serializable data class Sources( - @JsonProperty("src") val src: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("label") val label: String? = null, + @JsonProperty("src") @SerialName("src") val src: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, ) - -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vicloud.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vicloud.kt index 6b82ee454..b3082cea6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vicloud.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vicloud.kt @@ -8,25 +8,25 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class Vicloud : ExtractorApi() { - override val name: String = "Vicloud" - override val mainUrl: String = "https://vicloud.sbs" + override val name = "Vicloud" + override val mainUrl = "https://vicloud.sbs" override val requiresReferer = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val id = Regex("\"apiQuery\":\"(.*?)\"").find(app.get(url).text)?.groupValues?.getOrNull(1) app.get( "$mainUrl/api/?$id=&_=$unixTimeMS", - headers = mapOf( - "X-Requested-With" to "XMLHttpRequest" - ), - referer = url + headers = mapOf("X-Requested-With" to "XMLHttpRequest"), + referer = url, ).parsedSafe()?.sources?.map { source -> callback.invoke( newExtractorLink( @@ -39,16 +39,16 @@ open class Vicloud : ExtractorApi() { } ) } - } + @Serializable private data class Sources( - @JsonProperty("file") val file: String? = null, - @JsonProperty("label") val label: String? = null, + @JsonProperty("file") @SerialName("file") val file: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, ) + @Serializable private data class Responses( - @JsonProperty("sources") val sources: List? = arrayListOf(), + @JsonProperty("sources") @SerialName("sources") val sources: List? = arrayListOf(), ) - -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt index d74a1ccd6..04c60675e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt @@ -44,27 +44,29 @@ class VidaraSo : Vidara() { } open class Vidara : ExtractorApi() { - override val name: String = "Vidara" + override val name = "Vidara" override val mainUrl = "https://vidara.to" - override val requiresReferer: Boolean = false + override val requiresReferer = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val fileCode = url.substringAfterLast("/") - val fileInfo = - app.post("$mainUrl/api/stream", json = mapOf("filecode" to fileCode, "device" to "web")) - .parsed() - + val fileInfo = app.post( + "$mainUrl/api/stream", json = mapOf( + "filecode" to fileCode, + "device" to "web" + ) + ).parsed() callback.invoke( newExtractorLink( source = name, name = name, url = fileInfo.streamingUrl, - type = ExtractorLinkType.M3U8 + type = ExtractorLinkType.M3U8, ) ) @@ -77,19 +79,15 @@ open class Vidara : ExtractorApi() { @Serializable private data class StreamUpFileInfo( - val title: String, - val thumbnail: String, - @SerialName("streaming_url") - @JsonProperty("streaming_url") - val streamingUrl: String, - val subtitles: List? + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("thumbnail") @SerialName("thumbnail") val thumbnail: String, + @JsonProperty("streaming_url") @SerialName("streaming_url") val streamingUrl: String, + @JsonProperty("subtitles") @SerialName("subtitles") val subtitles: List?, ) @Serializable private data class StreamUpSubtitle( - @SerialName("file_path") - @JsonProperty("file_path") - val filePath: String, - val language: String, + @JsonProperty("file_path") @SerialName("file_path") val filePath: String, + @JsonProperty("language") @SerialName("language") val language: String, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VideoSeyredExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VideoSeyredExtractor.kt index bc94ae0cc..0c8e41ab7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VideoSeyredExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VideoSeyredExtractor.kt @@ -7,27 +7,32 @@ import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable open class VideoSeyred : ExtractorApi() { - override val name = "VideoSeyred" - override val mainUrl = "https://videoseyred.in" + override val name = "VideoSeyred" + override val mainUrl = "https://videoseyred.in" override val requiresReferer = true - override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { - val extRef = referer ?: "" - val videoId = url.substringAfter("embed/").substringBefore("?") - val videoUrl = "${mainUrl}/playlist/${videoId}.json" - - val responseRaw = app.get(videoUrl) - val responseList: List = tryParseJson>(responseRaw.text) ?: throw ErrorLoadingException("VideoSeyred") - val response = responseList[0] - + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + val extRef = referer ?: "" + val videoId = url.substringAfter("embed/").substringBefore("?") + val videoUrl = "$mainUrl/playlist/$videoId.json" + val responseRaw = app.get(videoUrl) + val responseList = tryParseJson>(responseRaw.text) ?: throw ErrorLoadingException("VideoSeyred") + val response = responseList[0] for (track in response.tracks) { if (track.label != null && track.kind == "captions") { subtitleCallback.invoke( newSubtitleFile( lang = track.label, - url = fixUrl(track.file) + url = fixUrl(track.file), ) ) } @@ -36,35 +41,38 @@ open class VideoSeyred : ExtractorApi() { for (source in response.sources) { callback.invoke( newExtractorLink( - source = this.name, - name = this.name, - url = source.file, + source = this.name, + name = this.name, + url = source.file, ) { - this.referer = "${mainUrl}/" + this.referer = "$mainUrl/" this.quality = Qualities.Unknown.value } ) } } + @Serializable data class VideoSeyredSource( - @JsonProperty("image") val image: String, - @JsonProperty("title") val title: String, - @JsonProperty("sources") val sources: List, - @JsonProperty("tracks") val tracks: List + @JsonProperty("image") @SerialName("image") val image: String, + @JsonProperty("title") @SerialName("title") val title: String, + @JsonProperty("sources") @SerialName("sources") val sources: List, + @JsonProperty("tracks") @SerialName("tracks") val tracks: List, ) + @Serializable data class VSSource( - @JsonProperty("file") val file: String, - @JsonProperty("type") val type: String, - @JsonProperty("default") val default: String + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("type") @SerialName("type") val type: String, + @JsonProperty("default") @SerialName("default") val default: String, ) + @Serializable data class VSTrack( - @JsonProperty("file") val file: String, - @JsonProperty("kind") val kind: String, - @JsonProperty("language") val language: String? = null, - @JsonProperty("label") val label: String? = null, - @JsonProperty("default") val default: String? = null + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("kind") @SerialName("kind") val kind: String, + @JsonProperty("language") @SerialName("language") val language: String? = null, + @JsonProperty("label") @SerialName("label") val label: String? = null, + @JsonProperty("default") @SerialName("default") val default: String? = null, ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidoza.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidoza.kt index df663f97f..5cb1706cd 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidoza.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidoza.kt @@ -4,26 +4,28 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.api.Log import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -class Videzz: Vidoza() { - override val mainUrl: String = "https://videzz.net" +class Videzz : Vidoza() { + override val mainUrl = "https://videzz.net" } -open class Vidoza: ExtractorApi() { - override val name: String = "Vidoza" - override val mainUrl: String = "https://vidoza.net" - override val requiresReferer: Boolean = false +open class Vidoza : ExtractorApi() { + override val name = "Vidoza" + override val mainUrl = "https://vidoza.net" + override val requiresReferer = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val response = app.get(url).document val script = response.selectFirst("script:containsData(sourcesCode)")?.data() @@ -32,31 +34,27 @@ open class Vidoza: ExtractorApi() { // e.g. sourcesCode: [{ src: "https://str38.vidoza.net/vod/v2/.../v.mp4", type: "video/mp4", label:"SD", res:"720"}], var sourcesArray = script.substringAfter("sourcesCode:").substringBefore("\n") arrayOf("src", "type", "label", "res").forEach { - // add missing quotation marks, e.g. src: "https..." -> "src": "https..." - sourcesArray = sourcesArray - .replace(Regex(""""?$it"?:"""), """"$it":""") + // Add missing quotation marks, e.g. src: "https..." -> "src": "https..." + sourcesArray = sourcesArray.replace(Regex(""""?$it"?:"""), """"$it":""") } - val videoData = AppUtils.parseJson(sourcesArray) + val videoData = parseJson>(sourcesArray) for (stream in videoData) { callback.invoke( newExtractorLink( source = name, name = name, - url = stream.source - ) { - quality = getQualityFromName(stream.resolution) - } + url = stream.source, + ) { quality = getQualityFromName(stream.resolution) } ) } } - private class VinovoDataList: ArrayList() - + @Serializable private data class VinovoVideoData( - @JsonProperty("src") val source: String, - @JsonProperty("type") val type: String?, - @JsonProperty("label") val label: String?, - @JsonProperty("res") val resolution: String?, + @JsonProperty("src") @SerialName("src") val source: String, + @JsonProperty("type") @SerialName("type") val type: String?, + @JsonProperty("label") @SerialName("label") val label: String?, + @JsonProperty("res") @SerialName("res") val resolution: String?, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vinovo.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vinovo.kt index e905543a8..8fc42d65a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vinovo.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vinovo.kt @@ -1,32 +1,33 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.APIHolder +import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class VinovoSi : VinovoTo() { - override var name = "VinovoSi" - override var mainUrl = "https://vinovo.si" + override val name = "VinovoSi" + override val mainUrl = "https://vinovo.si" } open class VinovoTo : ExtractorApi() { - override val mainUrl: String = "https://vinovo.to" - override val name: String = "VinovoTo" - override val requiresReferer: Boolean = false + override val mainUrl = "https://vinovo.to" + override val name = "VinovoTo" + override val requiresReferer = false override suspend fun getUrl( url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val fixedUrl = url.replace("/d/", "/e/") - val resp = app.get(fixedUrl, referer = referer) val doc = resp.document @@ -35,21 +36,20 @@ open class VinovoTo : ExtractorApi() { val fileCode = doc.selectFirst("meta[name=\"file_code\"]")?.attr("content") ?: return val captchaToken = doc.selectFirst("meta[name=recaptcha]")?.attr("content") ?: return - val captchaSolution = - APIHolder.getCaptchaToken(fixedUrl, captchaToken, "$mainUrl/") ?: return + val captchaSolution = getCaptchaToken(fixedUrl, captchaToken, "$mainUrl/") ?: return val streamInfo = app.post( url = "$mainUrl/api/file/url/$fileCode", data = mapOf("token" to videoToken, "recaptcha" to captchaSolution), headers = mapOf( "Origin" to mainUrl, - "X-Requested-With" to "XMLHttpRequest" + "X-Requested-With" to "XMLHttpRequest", ), cookies = resp.cookies, - referer = fixedUrl + referer = fixedUrl, ).parsed() - val fileUrl = "$videoBaseUrl/stream/${streamInfo.token}" + val fileUrl = "$videoBaseUrl/stream/${streamInfo.token}" callback.invoke( newExtractorLink(source = name, name = name, url = fileUrl) { val dataTitle = doc.selectFirst("video")?.attr("data-title").orEmpty() @@ -59,8 +59,9 @@ open class VinovoTo : ExtractorApi() { ) } + @Serializable private data class VinovoFileResp( - @JsonProperty("status") val status: String, - @JsonProperty("token") val token: String, + @JsonProperty("status") @SerialName("status") val status: String, + @JsonProperty("token") @SerialName("token") val token: String, ) -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/XStreamCdn.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/XStreamCdn.kt index 6e3a8126d..4392e64ed 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/XStreamCdn.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/XStreamCdn.kt @@ -9,96 +9,74 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable class StreamM4u : XStreamCdn() { - override val name: String = "StreamM4u" - override val mainUrl: String = "https://streamm4u.club" + override val name = "StreamM4u" + override val mainUrl = "https://streamm4u.club" } class Fembed9hd : XStreamCdn() { - override var mainUrl = "https://fembed9hd.com" - override var name = "Fembed9hd" + override val name = "Fembed9hd" + override val mainUrl = "https://fembed9hd.com" } -class Cdnplayer: XStreamCdn() { - override val name: String = "Cdnplayer" - override val mainUrl: String = "https://cdnplayer.online" +class Cdnplayer : XStreamCdn() { + override val name = "Cdnplayer" + override val mainUrl = "https://cdnplayer.online" } -class Kotakajair: XStreamCdn() { - override val name: String = "Kotakajair" - override val mainUrl: String = "https://kotakajair.xyz" +class Kotakajair : XStreamCdn() { + override val name = "Kotakajair" + override val mainUrl = "https://kotakajair.xyz" } -class FEnet: XStreamCdn() { - override val name: String = "FEnet" - override val mainUrl: String = "https://fembed.net" +class FEnet : XStreamCdn() { + override val name = "FEnet" + override val mainUrl = "https://fembed.net" } -class Rasacintaku: XStreamCdn() { - override val mainUrl: String = "https://rasa-cintaku-semakin-berantai.xyz" +class Rasacintaku : XStreamCdn() { + override val mainUrl = "https://rasa-cintaku-semakin-berantai.xyz" } -class LayarKaca: XStreamCdn() { - override val name: String = "LayarKaca-xxi" - override val mainUrl: String = "https://layarkacaxxi.icu" +class LayarKaca : XStreamCdn() { + override val name = "LayarKaca-xxi" + override val mainUrl = "https://layarkacaxxi.icu" } -class DBfilm: XStreamCdn() { - override val name: String = "DBfilm" - override val mainUrl: String = "https://dbfilm.bar" +class DBfilm : XStreamCdn() { + override val name = "DBfilm" + override val mainUrl = "https://dbfilm.bar" } -class Luxubu : XStreamCdn(){ - override val name: String = "FE" - override val mainUrl: String = "https://www.luxubu.review" +class Luxubu : XStreamCdn() { + override val name = "FE" + override val mainUrl = "https://www.luxubu.review" } -class FEmbed: XStreamCdn() { - override val name: String = "FEmbed" - override val mainUrl: String = "https://www.fembed.com" +class FEmbed : XStreamCdn() { + override val name = "FEmbed" + override val mainUrl = "https://www.fembed.com" } -class Fplayer: XStreamCdn() { - override val name: String = "Fplayer" - override val mainUrl: String = "https://fplayer.info" +class Fplayer : XStreamCdn() { + override val name = "Fplayer" + override val mainUrl = "https://fplayer.info" } -class FeHD: XStreamCdn() { - override val name: String = "FeHD" - override val mainUrl: String = "https://fembed-hd.com" - override var domainUrl: String = "fembed-hd.com" +class FeHD : XStreamCdn() { + override val name = "FeHD" + override val mainUrl = "https://fembed-hd.com" + override val domainUrl = "fembed-hd.com" } open class XStreamCdn : ExtractorApi() { - override val name: String = "XStreamCdn" - override val mainUrl: String = "https://embedsito.com" + override val name = "XStreamCdn" + override val mainUrl = "https://embedsito.com" override val requiresReferer = false - open var domainUrl: String = "embedsito.com" - - private data class ResponseData( - @JsonProperty("file") val file: String, - @JsonProperty("label") val label: String, - //val type: String // Mp4 - ) - - private data class Player( - @JsonProperty("poster_file") val poster_file: String? = null, - ) - - private data class ResponseJson( - @JsonProperty("success") val success: Boolean, - @JsonProperty("player") val player: Player? = null, - @JsonProperty("data") val data: List?, - @JsonProperty("captions") val captions: List?, - ) - - private data class Captions( - @JsonProperty("id") val id: String, - @JsonProperty("hash") val hash: String, - @JsonProperty("language") val language: String, - @JsonProperty("extension") val extension: String - ) + open val domainUrl = "embedsito.com" override fun getExtractorUrl(id: String): String { return "$domainUrl/api/source/$id" @@ -108,16 +86,17 @@ open class XStreamCdn : ExtractorApi() { url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ) { val headers = mapOf( "Referer" to url, "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", ) + val id = url.trimEnd('/').split("/").last() - val newUrl = "https://${domainUrl}/api/source/${id}" + val newUrl = "https://$domainUrl/api/source/$id" app.post(newUrl, headers = headers).let { res -> - val sources = tryParseJson(res.text) + val sources = tryParseJson(res.text) sources?.let { if (it.success && it.data != null) { it.data.map { source -> @@ -135,7 +114,7 @@ open class XStreamCdn : ExtractorApi() { } } - val userData = sources?.player?.poster_file?.split("/")?.get(2) + val userData = sources?.player?.posterFile?.split("/")?.get(2) sources?.captions?.map { subtitleCallback.invoke( newSubtitleFile( @@ -146,4 +125,31 @@ open class XStreamCdn : ExtractorApi() { } } } -} \ No newline at end of file + + @Serializable + private data class ResponseData( + @JsonProperty("file") @SerialName("file") val file: String, + @JsonProperty("label") @SerialName("label") val label: String, + ) + + @Serializable + private data class Player( + @JsonProperty("poster_file") @SerialName("poster_file") val posterFile: String? = null, + ) + + @Serializable + private data class ResponseJson( + @JsonProperty("success") @SerialName("success") val success: Boolean, + @JsonProperty("player") @SerialName("player") val player: Player? = null, + @JsonProperty("data") @SerialName("data") val data: List?, + @JsonProperty("captions") @SerialName("captions") val captions: List?, + ) + + @Serializable + private data class Captions( + @JsonProperty("id") @SerialName("id") val id: String, + @JsonProperty("hash") @SerialName("hash") val hash: String, + @JsonProperty("language") @SerialName("language") val language: String, + @JsonProperty("extension") @SerialName("extension") val extension: String, + ) +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/YourUpload.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/YourUpload.kt index 9f2d6bde0..88eb58333 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/YourUpload.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/YourUpload.kt @@ -7,8 +7,10 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -open class YourUpload: ExtractorApi() { +open class YourUpload : ExtractorApi() { override val name = "Yourupload" override val mainUrl = "https://www.yourupload.com" override val requiresReferer = false @@ -19,13 +21,11 @@ open class YourUpload: ExtractorApi() { val quality = Regex("\\d{3,4}p").find(this.select("title").text())?.groupValues?.get(0) this.select("script").map { script -> if (script.data().contains("var jwplayerOptions = {")) { - val data = - script.data().substringAfter("var jwplayerOptions = {").substringBefore(",\n") + val data = script.data().substringAfter("var jwplayerOptions = {").substringBefore(",\n") val link = tryParseJson( - "{${ - data.replace("file", "\"file\"").replace("'", "\"") - }}" + "{${data.replace("file", "\"file\"").replace("'", "\"")}}" ) + sources.add( newExtractorLink( source = name, @@ -39,11 +39,12 @@ open class YourUpload: ExtractorApi() { } } } + return sources } + @Serializable private data class ResponseSource( - @JsonProperty("file") val file: String, + @JsonProperty("file") @SerialName("file") val file: String, ) - -} \ No newline at end of file +} From c66b0eadd62f5a781ae1c07c6780eb4028cbfd5b Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:29:29 +0000 Subject: [PATCH 073/105] Fix failed cast in ContextHelper --- app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index a9cd9c01e..466ef5a00 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -113,7 +113,7 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { get() = _context?.get() private set(value) { _context = WeakReference(value) - setContext(WeakReference(value)) + setContext(value) } fun getKeyClass(path: String, valueType: Class): T? { From 36502213342b98ab297f1ab7c68736a4b1b5bb86 Mon Sep 17 00:00:00 2001 From: Kraptor123 <89366989+Kraptor123@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:05:15 +0300 Subject: [PATCH 074/105] [skip ci] ui: Hide pinned NSFW-only extensions from non-NSFW categories (#2923) --- .../com/lagradost/cloudstream3/ui/home/HomeFragment.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index ab0715c6d..96c5f9f56 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -451,7 +451,14 @@ class HomeFragment : BaseFragment( arrayAdapter.clear() val sortedApis = validAPIs .filter { - it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any( + val isPinned = pinnedphashset.contains(it.name) + + // Hide pinned NSFW when NSFW not selected. NSFW is distracting when not chosen. + if (isPinned && !preSelectedTypes.contains(TvType.NSFW)) { + if (it.supportedTypes.all { type -> type == TvType.NSFW }) return@filter false + } + + it.hasMainPage && (isPinned || it.supportedTypes.any( preSelectedTypes::contains )) } From c09c06820dc3b47d237893c22202f5687434ca0c Mon Sep 17 00:00:00 2001 From: Leandre Chamberland-Dozois Date: Tue, 14 Jul 2026 11:19:39 -0400 Subject: [PATCH 075/105] fix(download): remove deleted/cancelled items from the Downloads list in real time (#1227) (#2598) --- .../ui/download/DownloadViewModel.kt | 27 ++++++++++++++++++- .../ui/download/button/BaseFetchButton.kt | 12 +++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index 0d35d5670..19c006b28 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.api.Log +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.mvvm.Resource @@ -36,6 +37,7 @@ import com.lagradost.cloudstream3.utils.ResourceLiveData import com.lagradost.cloudstream3.utils.downloader.DownloadObjects import com.lagradost.cloudstream3.utils.downloader.DownloadQueueManager import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.deleteFilesAndUpdateSettings +import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.downloadDeleteEvent import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.getDownloadFileInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -67,6 +69,17 @@ class DownloadViewModel : ViewModel() { private val _selectedItemIds = ConsistentLiveData?>(null) val selectedItemIds: LiveData?> = _selectedItemIds + init { + // Keep the Downloads list in sync when a download is deleted/cancelled from + // anywhere in the app (result page button, queue, notification, etc.). See + // onDownloadDeleted for the rationale (issue #1227). + downloadDeleteEvent += ::onDownloadDeleted + } + + override fun onCleared() { + downloadDeleteEvent -= ::onDownloadDeleted + super.onCleared() + } fun cancelSelection() { updateSelectedItems { null } @@ -389,6 +402,18 @@ class DownloadViewModel : ViewModel() { postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } + /** + * Refreshes the Downloads screen in real time when a download is deleted/cancelled. + */ + private fun onDownloadDeleted(id: Int) { + // Keep multi-select state consistent: forget the removed id if it was selected. + updateSelectedItems { it?.minus(id) } + + val context = CloudStreamApp.context ?: return + updateHeaderList(context) + postChildren(_childCards.success?.filterNot { it.data.id == id }) + } + private fun updateStorageStats(visual: List) { try { val stat = StatFs(Environment.getExternalStorageDirectory().path) @@ -584,4 +609,4 @@ class DownloadViewModel : ViewModel() { val names: List, val parentName: String? ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt index 382a770cd..450dc9a61 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt @@ -164,9 +164,11 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : } } - /*fun downloadDeleteEvent(data: Int) { - - }*/ + fun downloadDeleteEvent(data: Int) { + if (data == persistentId) { + resetView() + } + } /*fun downloadEvent(data: Pair) { val (id, action) = data @@ -185,7 +187,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : override fun onAttachedToWindow() { VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent - // VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent + VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent // VideoDownloadManager.downloadEvent += ::downloadEvent VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent @@ -200,7 +202,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : override fun onDetachedFromWindow() { VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent - // VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent + VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent // VideoDownloadManager.downloadEvent -= ::downloadEvent VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent From fb8bcb4ac70e4277ce29c540c6a2ea386de5268b Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:23:52 +0000 Subject: [PATCH 076/105] more robust date parsing (#3068) --- .../com/lagradost/cloudstream3/MainAPI.kt | 12 +++++++++-- .../lagradost/cloudstream3/EpisodeDateTest.kt | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 3e8774db2..ca024b463 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -2576,13 +2576,21 @@ constructor( @OptIn(FormatStringsInDatetimeFormats::class) fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") { - if (date == null) return + if (date.isNullOrBlank()) return this.date = runCatching { // First try standard ISO 8601 (e.g. "2026-01-01T12:30:00.000Z", "2026-05-17T14:35+02:00") runCatching { Instant.parse(date).toEpochMilliseconds() } .getOrElse { val fmt = DateTimeComponents.Format { byUnicodePattern(format) } - val components = DateTimeComponents.parse(date, fmt) + + // Try parsing the full date first then only parse the beginning of the string + // May lose time, but better than failing. + val components = runCatching { + DateTimeComponents.parse(date, fmt) + }.recoverCatching { + DateTimeComponents.parse(date.trimStart().take(format.length), fmt) + }.getOrThrow() + /** * Try multiple conversions in order of precision for non-ISO-8601 formats, * since the date string may or may not include time and/or timezone offset: diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/EpisodeDateTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/EpisodeDateTest.kt index 4dc56978e..422b4cd93 100644 --- a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/EpisodeDateTest.kt +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/EpisodeDateTest.kt @@ -31,6 +31,26 @@ class EpisodeDateTest { assertEquals(expected, ep.date) } + @Test + fun addDateDefaultFormatParsesExtraTime() { + val ep = episode() + ep.addDate("2026-05-17 12:30:45") + val expected = LocalDate(2026, 5, 17) + .atStartOfDayIn(TimeZone.currentSystemDefault()) + .toEpochMilliseconds() + assertEquals(expected, ep.date) + } + + @Test + fun addDateDefaultFormatParsesExtraJunk() { + val ep = episode() + ep.addDate(" 2026-05-17 random data") + val expected = LocalDate(2026, 5, 17) + .atStartOfDayIn(TimeZone.currentSystemDefault()) + .toEpochMilliseconds() + assertEquals(expected, ep.date) + } + @Test fun addDateNullDoesNotSetDate() { val ep = episode() From fef17079a8263e9afd20124ddbd13334f5c4835c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:05:44 -0700 Subject: [PATCH 077/105] [skip ci] Coroutines KMP API (#2836) --- library/api/jvm/library.api | 8 ++ .../cloudstream3/mvvm/ArchComponentExt.kt | 87 ++++++------------- .../cloudstream3/utils/Coroutines.kt | 7 +- .../cloudstream3/mvvm/ArchComponentExtTest.kt | 31 +++++++ .../mvvm/ArchComponentExt.jvmCommon.kt | 37 ++++++++ .../utils/Coroutines.jvmCommon.kt | 4 + 6 files changed, 112 insertions(+), 62 deletions(-) create mode 100644 library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt create mode 100644 library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 1b39b3906..0b0996f94 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -7235,6 +7235,10 @@ public final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt { public static final fun throwAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; } +public final class com/lagradost/cloudstream3/mvvm/ArchComponentExt_jvmCommonKt { + public static final fun platformThrowAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; +} + public final class com/lagradost/cloudstream3/mvvm/DebugException : java/lang/Exception { public fun (Ljava/lang/String;)V } @@ -7438,6 +7442,10 @@ public final class com/lagradost/cloudstream3/utils/Coroutines { public final fun threadSafeListOf ([Ljava/lang/Object;)Ljava/util/List; } +public final class com/lagradost/cloudstream3/utils/Coroutines_jvmCommonKt { + public static final fun getWorkerDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; +} + public final class com/lagradost/cloudstream3/utils/Coroutines_jvmKt { public static final fun runOnMainThreadNative (Lkotlin/jvm/functions/Function0;)V } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index 4cd8eefa6..73de880db 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -4,15 +4,11 @@ import androidx.annotation.AnyThread import com.lagradost.api.Log import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.utils.AppDebug +import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.WorkerThread import kotlinx.coroutines.* -import java.io.InterruptedIOException -import java.net.SocketTimeoutException -import java.net.UnknownHostException -import javax.net.ssl.SSLHandshakeException import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -import kotlin.reflect.full.NoSuchPropertyException const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!" const val DEBUG_PRINT = "DEBUG PRINT" @@ -61,7 +57,7 @@ sealed class Resource { companion object { fun fromResult(result: Result) : Resource { val value = result.getOrNull() - return if(value != null) { + return if (value != null) { Success(value) } else { throwAbleToResource(result.exceptionOrNull() ?: Exception("this should not be possible")) @@ -72,7 +68,6 @@ sealed class Resource { fun logError(throwable: Throwable) { Log.d("ApiError", "-------------------------------------------------------------------") - Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage) Log.d("ApiError", "safeApiCall: " + throwable.message) throwable.printStackTrace() Log.d("ApiError", "-------------------------------------------------------------------") @@ -129,16 +124,21 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } fun Throwable.getAllMessages(): String { - return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") + return (this.message ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") } fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { - val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else "" - return prefix + this.stackTrace.joinToString( - separator = "\n" - ) { - "${it.fileName} ${it.lineNumber}" - } + val prefix = if (showMessage) this.message?.let { "\n$it" } ?: "" else "" + return prefix + this.stackTraceToString() + .lines() + .mapNotNull { line -> + val trimmed = line.trim() + if (!trimmed.startsWith("at ")) return@mapNotNull null + // pull "File.kt:42" out of "at com.pkg.Class.method(File.kt:42)" + Regex("""\(([^)]+)\)$""").find(trimmed)?.groupValues?.get(1) + ?.replace(":", " ") + } + .joinToString("\n") } fun safeFail(throwable: Throwable): Resource { @@ -162,50 +162,26 @@ fun CoroutineScope.launchSafe( return this.launch(context, start, obj) } +expect fun platformThrowAbleToResource(throwable: Throwable): Resource + fun throwAbleToResource( throwable: Throwable ): Resource { return when (throwable) { - is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { - Resource.Failure( - false, - "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? - ) - } - is NullPointerException -> { - for (line in throwable.stackTrace) { - if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) { - return Resource.Failure( - false, - "NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection" - ) - } + val traceLine = throwable.stackTraceToString() + .lines() + .firstOrNull { it.contains("provider.kt", ignoreCase = true) } + ?.let { Regex("""\(([^)]+)\)$""").find(it)?.groupValues?.get(1) } + if (traceLine != null) { + return Resource.Failure( + false, + "NullPointerException at $traceLine\nSite might have updated or added Cloudflare/DDOS protection" + ) } safeFail(throwable) } - is SocketTimeoutException, is InterruptedIOException -> { - Resource.Failure( - true, - "Connection Timeout\nPlease try again later." - ) - } -// is HttpException -> { -// Resource.Failure( -// false, -// throwable.statusCode, -// null, -// throwable.message ?: "HttpException" -// ) -// } - is UnknownHostException -> { - Resource.Failure( - true, - "Cannot connect to server, try again later.\n${throwable.message}" - ) - } - is ErrorLoadingException -> { Resource.Failure( true, @@ -217,20 +193,13 @@ fun throwAbleToResource( Resource.Failure(false, "This operation is not implemented.") } - is SSLHandshakeException -> { - Resource.Failure( - true, - (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." - ) - } - is CancellationException -> { throwable.cause?.let { throwAbleToResource(it) } ?: safeFail(throwable) } - else -> safeFail(throwable) + else -> platformThrowAbleToResource(throwable) } } @@ -238,9 +207,9 @@ fun throwAbleToResource( suspend fun safeApiCall( @WorkerThread apiCall: suspend () -> T, ): Resource { - return withContext(Dispatchers.IO) { + return apiCall.ioWork { work -> try { - Resource.Success(apiCall.invoke()) + Resource.Success(work.invoke()) } catch (throwable: Throwable) { logError(throwable) throwAbleToResource(throwable) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index 4536eb88b..4a269c525 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.* @AnyThread expect fun runOnMainThreadNative(@MainThread work: (() -> Unit)) +expect val workerDispatcher: CoroutineDispatcher internal expect annotation class WorkerThread() object Coroutines { @@ -24,7 +25,7 @@ object Coroutines { @AnyThread fun T.ioSafe(@WorkerThread work: suspend (CoroutineScope.(T) -> Unit)): Job { val value = this - return CoroutineScope(Dispatchers.IO).launchSafe { + return CoroutineScope(workerDispatcher).launchSafe { work(value) } } @@ -32,7 +33,7 @@ object Coroutines { @AnyThread suspend fun V.ioWorkSafe(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T? { val value = this - return withContext(Dispatchers.IO) { + return withContext(workerDispatcher) { try { work(value) } catch (e: Exception) { @@ -45,7 +46,7 @@ object Coroutines { @AnyThread suspend fun V.ioWork(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T { val value = this - return withContext(Dispatchers.IO) { + return withContext(workerDispatcher) { work(value) } } diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt new file mode 100644 index 000000000..970fe728d --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt @@ -0,0 +1,31 @@ +package com.lagradost.cloudstream3.mvvm + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ArchComponentExtTest { + + @Test + fun getStackTracePrettyStripsFullyQualifiedClassNames() { + val throwable = try { + throw IllegalStateException("Exception") + } catch (t: Throwable) { + t + } + + val pretty = throwable.getStackTracePretty() + + // Should not contain the fully qualified package/class prefix + assertFalse( + pretty.contains("com.lagradost.cloudstream3.mvvm.ArchComponentExtTest"), + "Expected no fully qualified class names, got:\n$pretty" + ) + + // Should still reference the source file itself + assertTrue( + pretty.contains("ArchComponentExtTest.kt"), + "Expected pretty stack trace to reference a .kt file, got:\n$pretty" + ) + } +} diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt new file mode 100644 index 000000000..f2ff5e3ac --- /dev/null +++ b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt @@ -0,0 +1,37 @@ +package com.lagradost.cloudstream3.mvvm + +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException +import kotlin.reflect.full.NoSuchPropertyException + +actual fun platformThrowAbleToResource(throwable: Throwable): Resource { + return when (throwable) { + is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { + Resource.Failure( + false, + "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? + ) + } + is SocketTimeoutException, is InterruptedIOException -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } + is UnknownHostException -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + is SSLHandshakeException -> { + Resource.Failure( + true, + (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." + ) + } + else -> safeFail(throwable) + } +} diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt index 2ae886f59..03a5be6c2 100644 --- a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt +++ b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt @@ -1,3 +1,7 @@ package com.lagradost.cloudstream3.utils +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +actual val workerDispatcher: CoroutineDispatcher = Dispatchers.IO internal actual typealias WorkerThread = androidx.annotation.WorkerThread From 91e533686eddbecc1d0a895f25ee5d592a6528b3 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:31:41 -0700 Subject: [PATCH 078/105] [skip ci] Rewrite TmdbProvider to remove external lib dependency (#2800) --- app/build.gradle.kts | 2 + gradle/libs.versions.toml | 2 - library/api/jvm/library.api | 694 ++++++++++++++++++ library/build.gradle.kts | 1 - .../metaproviders/TmdbProvider.kt | 661 ++++++++++------- 5 files changed, 1076 insertions(+), 284 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0feb866be..2ad8451a3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -276,6 +276,8 @@ dependencies { implementation(libs.jackson.module.kotlin) // JSON Parser implementation(libs.zipline) + // Temp/deprecated; will be removed once extensions have time to migrate from using it + implementation("com.google.code.gson:gson:2.11.0") // Deprecated; will be removed once extensions have time to migrate from using it implementation("me.xdrop:fuzzywuzzy:1.4.0") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fdfaee7b2..898529d9a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,7 +49,6 @@ qrcodeKotlin = "4.5.0" rhino = { strictly = "1.8.1" } # Requires minSdk 26 or later beginning at version 1.9.0 safefile = "0.0.8" shimmer = "0.5.0" -tmdbJava = "2.13.0" torrentserver = "7861970" tvprovider = "1.1.0" video = "1.0.0" @@ -129,7 +128,6 @@ qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrco rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" } shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } -tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJava" } torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 0b0996f94..0d9b8f76e 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -6613,6 +6613,7 @@ public final class com/lagradost/cloudstream3/metaproviders/SyncRedirector { } public final class com/lagradost/cloudstream3/metaproviders/TmdbLink { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbLink$Companion; public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -6632,6 +6633,21 @@ public final class com/lagradost/cloudstream3/metaproviders/TmdbLink { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbLink$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbLink$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbLink; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbLink;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbLink$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagradost/cloudstream3/MainAPI { public fun ()V public fun fetchContentRating (Ljava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -6650,6 +6666,684 @@ public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagrado public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; + public fun equals (Ljava/lang/Object;)Z + public final fun getCharacter ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getProfilePath ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; + public fun equals (Ljava/lang/Object;)Z + public final fun getCountry ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public fun equals (Ljava/lang/Object;)Z + public final fun getCast ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/Integer; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/Double; + public final fun component9 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; + public fun equals (Ljava/lang/Object;)Z + public final fun getAirDate ()Ljava/lang/String; + public final fun getEpisodeNumber ()Ljava/lang/Integer; + public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun getId ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getSeasonNumber ()Ljava/lang/Integer; + public final fun getStillPath ()Ljava/lang/String; + public final fun getVoteAverage ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public fun equals (Ljava/lang/Object;)Z + public final fun getImdbId ()Ljava/lang/String; + public final fun getTvdbId ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun component12 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun component13 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component15 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component16 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Double; + public final fun component8 ()Ljava/util/List; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getCredits ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun getDisplayTitle ()Ljava/lang/String; + public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun getGenres ()Ljava/util/List; + public final fun getId ()Ljava/lang/Integer; + public final fun getImdbId ()Ljava/lang/String; + public final fun getOriginalTitle ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getPosterPath ()Ljava/lang/String; + public final fun getRecommendations ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getReleaseDate ()Ljava/lang/String; + public final fun getReleaseDates ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public final fun getRuntime ()Ljava/lang/Integer; + public final fun getSimilar ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getTitle ()Ljava/lang/String; + public final fun getVideos ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun getVoteAverage ()Ljava/lang/Double; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public final fun getTotalPages ()Ljava/lang/Integer; + public final fun getTotalResults ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; + public fun equals (Ljava/lang/Object;)Z + public final fun getCertification ()Ljava/lang/String; + public final fun getType ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getCountry ()Ljava/lang/String; + public final fun getReleaseDates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Double; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getDisplayTitle ()Ljava/lang/String; + public final fun getFirstAirDate ()Ljava/lang/String; + public final fun getId ()Ljava/lang/Integer; + public final fun getMediaType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getOriginalName ()Ljava/lang/String; + public final fun getOriginalTitle ()Ljava/lang/String; + public final fun getPosterPath ()Ljava/lang/String; + public final fun getReleaseDate ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getVoteAverage ()Ljava/lang/Double; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public final fun isTv ()Z + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Integer;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/Integer;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail;Ljava/lang/Integer;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisodes ()Ljava/util/List; + public final fun getSeasonNumber ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisodeCount ()Ljava/lang/Integer; + public final fun getSeasonNumber ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun component12 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun component13 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component15 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component16 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Double; + public final fun component8 ()Ljava/util/List; + public final fun component9 ()Ljava/util/List; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getContentRatings ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public final fun getCredits ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun getDisplayTitle ()Ljava/lang/String; + public final fun getEpisodeRunTime ()Ljava/util/List; + public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun getFirstAirDate ()Ljava/lang/String; + public final fun getGenres ()Ljava/util/List; + public final fun getId ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public final fun getOriginalName ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getPosterPath ()Ljava/lang/String; + public final fun getRecommendations ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getSeasons ()Ljava/util/List; + public final fun getSimilar ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getVideos ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun getVoteAverage ()Ljava/lang/Double; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; + public fun equals (Ljava/lang/Object;)Z + public final fun getKey ()Ljava/lang/String; + public final fun getSite ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagradost/cloudstream3/MainAPI { public fun ()V public fun getHasMainPage ()Z diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 18579eb0c..92969baa6 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -64,7 +64,6 @@ kotlin { implementation(libs.ktor.http) implementation(libs.nicehttp) // HTTP Library implementation(libs.rhino) // Run JavaScript - implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit implementation(libs.bundles.cryptography) // Cryptography } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index 89f935da3..88dda631a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -1,7 +1,9 @@ package com.lagradost.cloudstream3.metaproviders +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.Actor +import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.Episode import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.HomePageList @@ -20,6 +22,8 @@ import com.lagradost.cloudstream3.SearchResponseList import com.lagradost.cloudstream3.TvSeriesLoadResponse import com.lagradost.cloudstream3.TvSeriesSearchResponse import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.addDate +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.newEpisode import com.lagradost.cloudstream3.newHomePageResponse import com.lagradost.cloudstream3.newMovieLoadResponse @@ -28,35 +32,22 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.runAllAsync import com.lagradost.cloudstream3.toNewSearchResponseList +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.uwetrottmann.tmdb2.Tmdb -import com.uwetrottmann.tmdb2.entities.AppendToResponse -import com.uwetrottmann.tmdb2.entities.BaseMovie -import com.uwetrottmann.tmdb2.entities.BaseTvShow -import com.uwetrottmann.tmdb2.entities.CastMember -import com.uwetrottmann.tmdb2.entities.ContentRating -import com.uwetrottmann.tmdb2.entities.Movie -import com.uwetrottmann.tmdb2.entities.ReleaseDate -import com.uwetrottmann.tmdb2.entities.ReleaseDatesResult -import com.uwetrottmann.tmdb2.entities.TvSeason -import com.uwetrottmann.tmdb2.entities.TvShow -import com.uwetrottmann.tmdb2.entities.Videos -import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem -import com.uwetrottmann.tmdb2.enumerations.VideoType -import retrofit2.awaitResponse -import retrofit2.Response -import java.util.Calendar +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * episode and season starting from 1 * they are null if movie - * */ + */ +@Serializable data class TmdbLink( - @JsonProperty("imdbID") val imdbID: String?, - @JsonProperty("tmdbID") val tmdbID: Int?, - @JsonProperty("episode") val episode: Int?, - @JsonProperty("season") val season: Int?, - @JsonProperty("movieName") val movieName: String? = null, + @JsonProperty("imdbID") @SerialName("imdbID") val imdbID: String?, + @JsonProperty("tmdbID") @SerialName("tmdbID") val tmdbID: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("movieName") @SerialName("movieName") val movieName: String? = null, ) open class TmdbProvider : MainAPI() { @@ -67,19 +58,184 @@ open class TmdbProvider : MainAPI() { open val useMetaLoadResponse = false open val apiName = "TMDB" - // As some sites doesn't support s0 + // As some sites don't support s0 open val disableSeasonZero = true override val hasMainPage = true override val providerType = ProviderType.MetaProvider - // Fuck it, public private api key because github actions won't co-operate. - // Please no stealy. - private val tmdb = Tmdb("e6333b32409e02a4a6eba6fb7ff866bb") + private val tmdbApiKey = "e6333b32409e02a4a6eba6fb7ff866bb" + private val tmdbApiUrl = "https://api.themoviedb.org/3" + + @Serializable + data class TmdbIds( + @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, + @JsonProperty("tvdb_id") @SerialName("tvdb_id") val tvdbId: Int? = null, + ) + + @Serializable + data class TmdbGenre( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + ) + + @Serializable + data class TmdbCastMember( + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("character") @SerialName("character") val character: String? = null, + @JsonProperty("profile_path") @SerialName("profile_path") val profilePath: String? = null, + ) + + @Serializable + data class TmdbCredits( + @JsonProperty("cast") @SerialName("cast") val cast: List? = null, + ) + + @Serializable + data class TmdbVideo( + @JsonProperty("key") @SerialName("key") val key: String? = null, + @JsonProperty("site") @SerialName("site") val site: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + ) + + @Serializable + data class TmdbVideos( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + // Shared between movie and tv search results + @Serializable + data class TmdbSearchResult( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, // movies + @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, // tv + @JsonProperty("original_name") @SerialName("original_name") val originalName: String? = null, + @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String? = null, + @JsonProperty("first_air_date") @SerialName("first_air_date") val firstAirDate: String? = null, + @JsonProperty("media_type") @SerialName("media_type") val mediaType: String? = null, // for multi-search + ) { + @get:JsonIgnore val isTv get() = name != null || mediaType == "tv" + @get:JsonIgnore val displayTitle get() = title ?: originalTitle ?: name ?: originalName ?: "" + @get:JsonIgnore val year get() = (releaseDate ?: firstAirDate)?.take(4)?.toIntOrNull() + } + + @Serializable + data class TmdbPageResult( + @JsonProperty("results") @SerialName("results") val results: List? = null, + @JsonProperty("total_pages") @SerialName("total_pages") val totalPages: Int? = null, + @JsonProperty("total_results") @SerialName("total_results") val totalResults: Int? = null, + ) + + @Serializable + data class TmdbMultiResult( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + @Serializable + data class TmdbEpisode( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("episode_number") @SerialName("episode_number") val episodeNumber: Int? = null, + @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, + @JsonProperty("still_path") @SerialName("still_path") val stillPath: String? = null, + @JsonProperty("air_date") @SerialName("air_date") val airDate: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, + ) + + @Serializable + data class TmdbSeasonDetail( + @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, + ) + + @Serializable + data class TmdbSeasonSummary( + @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, + @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Int? = null, + ) + + @Serializable + data class TmdbContentRating( + @JsonProperty("iso_3166_1") @SerialName("iso_3166_1") val country: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: String? = null, + ) + + @Serializable + data class TmdbContentRatings( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + @Serializable + data class TmdbReleaseDateEntry( + @JsonProperty("certification") @SerialName("certification") val certification: String? = null, + @JsonProperty("type") @SerialName("type") val type: Int? = null, + ) + + @Serializable + data class TmdbReleaseDateResult( + @JsonProperty("iso_3166_1") @SerialName("iso_3166_1") val country: String? = null, + @JsonProperty("release_dates") @SerialName("release_dates") val releaseDates: List? = null, + ) + + @Serializable + data class TmdbReleaseDates( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + @Serializable + data class TmdbTvDetail( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("original_name") @SerialName("original_name") val originalName: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, + @JsonProperty("first_air_date") @SerialName("first_air_date") val firstAirDate: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("episode_run_time") @SerialName("episode_run_time") val episodeRunTime: List? = null, + @JsonProperty("seasons") @SerialName("seasons") val seasons: List? = null, + @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, + @JsonProperty("videos") @SerialName("videos") val videos: TmdbVideos? = null, + @JsonProperty("credits") @SerialName("credits") val credits: TmdbCredits? = null, + @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: TmdbPageResult? = null, + @JsonProperty("similar") @SerialName("similar") val similar: TmdbPageResult? = null, + @JsonProperty("content_ratings") @SerialName("content_ratings") val contentRatings: TmdbContentRatings? = null, + ) { + @get:JsonIgnore val displayTitle get() = name ?: originalName ?: "" + @get:JsonIgnore val year get() = firstAirDate?.take(4)?.toIntOrNull() + } + + @Serializable + data class TmdbMovieDetail( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, + @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, + @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, + @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, + @JsonProperty("videos") @SerialName("videos") val videos: TmdbVideos? = null, + @JsonProperty("credits") @SerialName("credits") val credits: TmdbCredits? = null, + @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: TmdbPageResult? = null, + @JsonProperty("similar") @SerialName("similar") val similar: TmdbPageResult? = null, + @JsonProperty("release_dates") @SerialName("release_dates") val releaseDates: TmdbReleaseDates? = null, + ) { + @get:JsonIgnore val displayTitle get() = title ?: originalTitle ?: "" + @get:JsonIgnore val year get() = releaseDate?.take(4)?.toIntOrNull() + } private fun getImageUrl(link: String?): String? { - if (link == null) return null - return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500/$link" else link + link ?: return null + return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500$link" else link } private fun getUrl(id: Int?, tvShow: Boolean): String { @@ -87,199 +243,197 @@ open class TmdbProvider : MainAPI() { else "https://www.themoviedb.org/movie/${id ?: -1}" } - private fun BaseTvShow.toSearchResponse(): TvSeriesSearchResponse { - return newTvSeriesSearchResponse( - name = this.name ?: this.original_name, + private suspend fun getApi(path: String, extraParams: Map = emptyMap()): String { + val params = buildMap { + put("api_key", tmdbApiKey) + putAll(extraParams) + } + return app.get( + url = "$tmdbApiUrl$path", + params = params, + ).text + } + + private fun TmdbSearchResult.toSearchResponse() = if (isTv) { + newTvSeriesSearchResponse( + name = displayTitle, url = getUrl(id, true), type = TvType.TvSeries, - fix = false + fix = false, ) { this.id = this@toSearchResponse.id - this.posterUrl = getImageUrl(poster_path) - this.score = Score.from10(vote_average) - this.year = first_air_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } + this.posterUrl = getImageUrl(posterPath) + this.score = Score.from10(voteAverage) + this.year = this@toSearchResponse.year } - } - - private fun BaseMovie.toSearchResponse(): MovieSearchResponse { - return newMovieSearchResponse( - name = this.title ?: this.original_title, + } else { + newMovieSearchResponse( + name = displayTitle, url = getUrl(id, false), type = TvType.Movie, - fix = false + fix = false, ) { this.id = this@toSearchResponse.id - this.posterUrl = getImageUrl(poster_path) - this.score = Score.from10(vote_average) - this.year = release_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } + this.posterUrl = getImageUrl(posterPath) + this.score = Score.from10(voteAverage) + this.year = this@toSearchResponse.year } } - private fun List?.toActors(): List>? { + private fun List?.toActors(): List>? { return this?.mapNotNull { + it ?: return@mapNotNull null Pair( - Actor(it?.name ?: return@mapNotNull null, getImageUrl(it.profile_path)), - it.character + Actor(it.name ?: return@mapNotNull null, getImageUrl(it.profilePath)), + it.character, ) } } - private suspend fun TvShow.toLoadResponse(): TvSeriesLoadResponse { - val tvSeasonsService = tmdb.tvSeasonsService() - val episodes = mutableListOf() - - val validSeasons = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } ?: emptyList() - for (season in validSeasons) { - val seasonNumber = season.season_number ?: continue - - val response: Response = tmdb.tvSeasonsService() - .season(this.id, seasonNumber, "external_ids,images,episodes") - .awaitResponse() - - val fullSeason = response.body() ?: continue - - fullSeason.episodes?.forEach { episode -> - episodes += newEpisode( - TmdbLink( - episode.external_ids?.imdb_id ?: this.external_ids?.imdb_id, - this.id, - episode.episode_number, - episode.season_number, - this.name ?: this.original_name - ).toJson() - ) { - this.name = episode.name - this.season = episode.season_number - this.episode = episode.episode_number - this.score = Score.from10(episode.vote_average) - this.description = episode.overview - this.date = episode.air_date?.time - this.posterUrl = getImageUrl(episode.still_path) - } - } - } - - return newTvSeriesLoadResponse( - this.name ?: this.original_name, - getUrl(id, true), - TvType.TvSeries, - episodes - ) { - posterUrl = getImageUrl(poster_path) - year = first_air_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } - plot = overview - addImdbId(external_ids?.imdb_id) - tags = genres?.mapNotNull { it.name } - duration = episode_run_time?.average()?.toInt() - score = Score.from10(vote_average) - addTrailer(videos.toTrailers()) - recommendations = (this@toLoadResponse.recommendations - ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } - addActors(credits?.cast?.toList().toActors()) - contentRating = fetchContentRating(id, "US") - } - } - - private fun Videos?.toTrailers(): List? { - return this?.results?.filter { it.type != VideoType.OPENING_CREDITS && it.type != VideoType.FEATURETTE } - ?.sortedBy { it.type?.ordinal ?: 10000 } + private fun TmdbVideos?.toTrailers(): List? { + val skipTypes = setOf("Opening Credits", "Featurette") + return this?.results + ?.filter { it.type !in skipTypes } + ?.sortedBy { it.type } ?.mapNotNull { when (it.site?.trim()?.lowercase()) { - "youtube" -> { // TODO FILL SITES - "https://www.youtube.com/watch?v=${it.key}" - } + "youtube" -> "https://www.youtube.com/watch?v=${it.key}" else -> null } } } - private suspend fun Movie.toLoadResponse(): MovieLoadResponse { - return newMovieLoadResponse( - this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink( - this.imdb_id, - this.id, - null, - null, - this.title ?: this.original_title, - ).toJson() - ) { - posterUrl = getImageUrl(poster_path) - year = release_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } - plot = overview - addImdbId(external_ids?.imdb_id) - tags = genres?.mapNotNull { it.name } - duration = runtime - score = Score.from10(vote_average) - addTrailer(videos.toTrailers()) + open suspend fun fetchContentRating(id: Int?, country: String): String? { + id ?: return null + // Try TV content ratings first + val tvRating = parseJson( + getApi("/tv/$id/content_ratings") + ).results?.firstOrNull { it.country == country }?.rating + if (tvRating != null) return tvRating + // Fall back to movie release dates + return parseJson( + getApi("/movie/$id/release_dates") + ).results?.firstOrNull { it.country == country } + ?.releaseDates?.firstOrNull { !it.certification.isNullOrBlank() } + ?.certification + } + + private suspend fun TmdbTvDetail.toLoadResponse(): TvSeriesLoadResponse { + val episodes = mutableListOf() + val validSeasons = seasons?.filter { !disableSeasonZero || (it.seasonNumber ?: 0) != 0 } + ?: emptyList() + + for (season in validSeasons) { + val seasonNum = season.seasonNumber ?: continue + val fullSeason = parseJson( + getApi("/tv/$id/season/$seasonNum", mapOf("append_to_response" to "external_ids")) + ) + + fullSeason.episodes?.forEach { episode -> + episodes += newEpisode( + TmdbLink( + episode.externalIds?.imdbId ?: externalIds?.imdbId, + id, + episode.episodeNumber, + episode.seasonNumber, + displayTitle, + ).toJson() + ) { + this.name = episode.name + this.season = episode.seasonNumber + this.episode = episode.episodeNumber + this.score = Score.from10(episode.voteAverage) + this.description = episode.overview + this.posterUrl = getImageUrl(episode.stillPath) + this.addDate(episode.airDate) + } + } + } + + return newTvSeriesLoadResponse( + displayTitle, + getUrl(id, true), + TvType.TvSeries, + episodes, + ) { + posterUrl = getImageUrl(posterPath) + this.year = this@toLoadResponse.year + plot = overview + addImdbId(externalIds?.imdbId) + tags = genres?.mapNotNull { it.name } + duration = episodeRunTime?.average()?.toInt() + score = Score.from10(voteAverage) + addTrailer(videos.toTrailers()) recommendations = (this@toLoadResponse.recommendations ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } addActors(credits?.cast?.toList().toActors()) + contentRating = contentRatings?.results?.firstOrNull { it.country == "US" }?.rating + ?: fetchContentRating(id, "US") + } + } - contentRating = fetchContentRating(id, "US") + private suspend fun TmdbMovieDetail.toLoadResponse(): MovieLoadResponse { + return newMovieLoadResponse( + displayTitle, + getUrl(id, false), + TvType.Movie, + TmdbLink( + imdbId ?: externalIds?.imdbId, + id, + null, + null, + displayTitle, + ).toJson() + ) { + posterUrl = getImageUrl(posterPath) + this.year = this@toLoadResponse.year + plot = overview + addImdbId(imdbId ?: externalIds?.imdbId) + tags = genres?.mapNotNull { it.name } + duration = runtime + score = Score.from10(voteAverage) + addTrailer(videos.toTrailers()) + recommendations = (this@toLoadResponse.recommendations + ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } + addActors(credits?.cast?.toList().toActors()) + contentRating = releaseDates?.results + ?.firstOrNull { it.country == "US" } + ?.releaseDates?.firstOrNull { !it.certification.isNullOrBlank() } + ?.certification } } override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { - - // SAME AS DISCOVER IT SEEMS -// val popularSeries = tmdb.tvService().popular(page, "en-US").execute().body()?.results?.map { -// it.toSearchResponse() -// } ?: listOf() -// -// val popularMovies = -// tmdb.moviesService().popular(page, "en-US", "840").execute().body()?.results?.map { -// it.toSearchResponse() -// } ?: listOf() - var discoverMovies: List = listOf() var discoverSeries: List = listOf() var topMovies: List = listOf() var topSeries: List = listOf() runAllAsync( { - discoverMovies = tmdb.discoverMovie().page(page).build().awaitResponse().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - }, { - discoverSeries = tmdb.discoverTv().page(page).build().awaitResponse().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - }, { - // https://en.wikipedia.org/wiki/ISO_3166-1 - topMovies = - tmdb.moviesService().topRated(page, "en-US", "US").awaitResponse() - .body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - }, { - topSeries = - tmdb.tvService().topRated(page, "en-US").awaitResponse().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - } + discoverMovies = parseJson( + getApi("/discover/movie", mapOf("page" to "$page")) + ).results?.map { it.toSearchResponse() as MovieSearchResponse } ?: listOf() + }, + { + discoverSeries = parseJson( + getApi("/discover/tv", mapOf("page" to "$page")) + ).results?.map { it.toSearchResponse() as TvSeriesSearchResponse } ?: listOf() + }, + { + topMovies = parseJson( + getApi("/movie/top_rated", mapOf("page" to "$page", "language" to "en-US", "region" to "US")) + ).results?.map { it.toSearchResponse() as MovieSearchResponse } ?: listOf() + }, + { + topSeries = parseJson( + getApi("/tv/top_rated", mapOf("page" to "$page", "language" to "en-US")) + ).results?.map { it.toSearchResponse() as TvSeriesSearchResponse } ?: listOf() + }, ) return newHomePageResponse( listOf( -// HomePageList("Popular Series", popularSeries), -// HomePageList("Popular Movies", popularMovies), HomePageList("Popular Movies", discoverMovies), HomePageList("Popular Series", discoverSeries), HomePageList("Top Movies", topMovies), @@ -288,47 +442,14 @@ open class TmdbProvider : MainAPI() { ) } - open fun loadFromImdb(imdb: String, seasons: List): LoadResponse? { - return null - } + open fun loadFromImdb(imdb: String, seasons: List): LoadResponse? = null + open fun loadFromTmdb(tmdbId: Int, seasons: List): LoadResponse? = null + open fun loadFromImdb(imdb: String): LoadResponse? = null + open fun loadFromTmdb(tmdbId: Int): LoadResponse? = null - open fun loadFromTmdb(tmdb: Int, seasons: List): LoadResponse? { - return null - } - - open fun loadFromImdb(imdb: String): LoadResponse? { - return null - } - - open fun loadFromTmdb(tmdb: Int): LoadResponse? { - return null - } - - open suspend fun fetchContentRating(id: Int?, country: String): String? { - id ?: return null - - val contentRatings = tmdb.tvService().content_ratings(id).awaitResponse().body()?.results - return if (!contentRatings.isNullOrEmpty()) { - contentRatings.firstOrNull { it: ContentRating -> - it.iso_3166_1 == country - }?.rating - } else { - val releaseDates = tmdb.moviesService().releaseDates(id).awaitResponse().body()?.results - val certification = releaseDates?.firstOrNull { it: ReleaseDatesResult -> - it.iso_3166_1 == country - }?.release_dates?.firstOrNull { it: ReleaseDate -> - !it.certification.isNullOrBlank() - }?.certification - - certification - } - } - - // Possible to add recommendations and such here. override suspend fun load(url: String): LoadResponse? { // https://www.themoviedb.org/movie/7445-brothers // https://www.themoviedb.org/tv/71914-the-wheel-of-time - val idRegex = Regex("""themoviedb\.org/(.*)/(\d+)""") val found = idRegex.find(url) @@ -337,86 +458,64 @@ open class TmdbProvider : MainAPI() { ?: throw ErrorLoadingException("No id found") return if (useMetaLoadResponse) { - return if (isTvSeries) { - val body = tmdb.tvService() - .tv( - id, - "en-US", - AppendToResponse( - AppendToResponseItem.EXTERNAL_IDS, - AppendToResponseItem.VIDEOS + if (isTvSeries) { + val detail = parseJson( + getApi( + "/tv/$id", + mapOf( + "language" to "en-US", + "append_to_response" to "external_ids,videos,credits,recommendations,similar,content_ratings", ) ) - .awaitResponse().body() - val response = body?.toLoadResponse() - if (response != null) { - if (response.recommendations.isNullOrEmpty()) - tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body() - ?.let { - it.results?.map { res -> res.toSearchResponse() } - }?.let { list -> - response.recommendations = list - } - - if (response.actors.isNullOrEmpty()) - tmdb.tvService().credits(id, "en-US").awaitResponse().body()?.let { - response.addActors(it.cast?.toActors()) - } - } - - response + ) + detail.toLoadResponse() } else { - val body = tmdb.moviesService() - .summary( - id, - "en-US", - AppendToResponse( - AppendToResponseItem.EXTERNAL_IDS, - AppendToResponseItem.VIDEOS + val detail = parseJson( + getApi( + "/movie/$id", + mapOf( + "language" to "en-US", + "append_to_response" to "external_ids,videos,credits,recommendations,similar,release_dates", ) ) - .awaitResponse().body() - val response = body?.toLoadResponse() - if (response != null) { - if (response.recommendations.isNullOrEmpty()) - tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body() - ?.let { - it.results?.map { res -> res.toSearchResponse() } - }?.let { list -> - response.recommendations = list - } - - if (response.actors.isNullOrEmpty()) - tmdb.moviesService().credits(id).awaitResponse().body()?.let { - response.addActors(it.cast?.toActors()) - } - } - response + ) + detail.toLoadResponse() } } else { loadFromTmdb(id)?.let { return it } if (isTvSeries) { - tmdb.tvService().externalIds(id).awaitResponse().body()?.imdb_id?.let { - val fromImdb = loadFromImdb(it) - val result = if (fromImdb == null) { - val details = tmdb.tvService().tv(id, "en-US").awaitResponse().body() - loadFromImdb(it, details?.seasons ?: listOf()) - ?: loadFromTmdb(id, details?.seasons ?: listOf()) - } else fromImdb - - result + val externalIds = parseJson(getApi("/tv/$id/external_ids")) + val imdbId = externalIds.imdbId + if (imdbId != null) { + val fromImdb = loadFromImdb(imdbId) + if (fromImdb != null) return fromImdb + } + val seasons = parseJson(getApi("/tv/$id")).seasons ?: listOf() + if (imdbId != null) { + loadFromImdb(imdbId, seasons) ?: loadFromTmdb(id, seasons) + } else { + loadFromTmdb(id, seasons) } } else { - tmdb.moviesService().externalIds(id).awaitResponse() - .body()?.imdb_id?.let { loadFromImdb(it) } + val imdbId = parseJson(getApi("/movie/$id")).imdbId + if (imdbId != null) loadFromImdb(imdbId) else null } } } override suspend fun search(query: String, page: Int): SearchResponseList? { - return tmdb.searchService().multi(query, page, "en-US", "US", includeAdult).awaitResponse() - .body()?.results?.mapNotNull { - it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse() - }?.toNewSearchResponseList() + return parseJson( + getApi( + "/search/multi", + mapOf( + "query" to query, + "page" to "$page", + "language" to "en-US", + "include_adult" to "$includeAdult", + ) + ) + ).results?.mapNotNull { + if (it.mediaType == "person") null else it.toSearchResponse() + }?.toNewSearchResponseList() } -} \ No newline at end of file +} From 6ca7507146748bb378a9155cbc5a7e70f3919650 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:34:43 -0700 Subject: [PATCH 079/105] HlsPlaylistParser: replace usage of Java APIs with kotlin equivalents (#2828) This does add kotlinx-io as a dependency, it is more light weight then okio, and when we migrate to ktor for example it will become an implicit dependency anyway so it should be fine to just add. Allows for more reliable and readable usage then doing some of this manually. --- gradle/libs.versions.toml | 2 + library/api/jvm/library.api | 52 +++------------ library/build.gradle.kts | 1 + .../cloudstream3/utils/HlsPlaylistParser.kt | 63 ++++++++++--------- 4 files changed, 44 insertions(+), 74 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 898529d9a..e38895451 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ kotlinxAtomicfu = "0.33.0" kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutines = "1.11.0" kotlinxDatetime = "0.8.0" +kotlinxIOCore = "0.9.1" kotlinxSerializationJson = "1.11.0" ksoup = "0.2.6" ktor = "3.5.0" @@ -98,6 +99,7 @@ kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collec kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } +kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIOCore" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" } ktor-http = { module = "io.ktor:ktor-http", version.ref = "ktor" } diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 0d9b8f76e..195cd49fe 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -8327,44 +8327,6 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser { public final fun parse (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; } -public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$C { - public static final field CENC_TYPE_cbc1 Ljava/lang/String; - public static final field CENC_TYPE_cbcs Ljava/lang/String; - public static final field CENC_TYPE_cenc Ljava/lang/String; - public static final field CENC_TYPE_cens Ljava/lang/String; - public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$C; - public static final field ROLE_FLAG_ALTERNATE I - public static final field ROLE_FLAG_CAPTION I - public static final field ROLE_FLAG_COMMENTARY I - public static final field ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND I - public static final field ROLE_FLAG_DESCRIBES_VIDEO I - public static final field ROLE_FLAG_DUB I - public static final field ROLE_FLAG_EASY_TO_READ I - public static final field ROLE_FLAG_EMERGENCY I - public static final field ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY I - public static final field ROLE_FLAG_MAIN I - public static final field ROLE_FLAG_SIGN I - public static final field ROLE_FLAG_SUBTITLE I - public static final field ROLE_FLAG_SUPPLEMENTARY I - public static final field ROLE_FLAG_TRANSCRIBES_DIALOG I - public static final field ROLE_FLAG_TRICK_PLAY I - public static final field SELECTION_FLAG_AUTOSELECT I - public static final field SELECTION_FLAG_DEFAULT I - public static final field SELECTION_FLAG_FORCED I - public static final field TRACK_TYPE_AUDIO I - public static final field TRACK_TYPE_CAMERA_MOTION I - public static final field TRACK_TYPE_DEFAULT I - public static final field TRACK_TYPE_IMAGE I - public static final field TRACK_TYPE_METADATA I - public static final field TRACK_TYPE_NONE I - public static final field TRACK_TYPE_TEXT I - public static final field TRACK_TYPE_UNKNOWN I - public static final field TRACK_TYPE_VIDEO I - public final fun getCLEARKEY_UUID ()Ljava/util/UUID; - public final fun getPLAYREADY_UUID ()Ljava/util/UUID; - public final fun getWIDEVINE_UUID ()Ljava/util/UUID; -} - public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData { public fun (Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;)V public final fun component1 ()Ljava/lang/String; @@ -8630,7 +8592,7 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserExce public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil { public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil; - public final fun buildPsshAtom (Ljava/util/UUID;[Ljava/util/UUID;[B)[B + public final fun buildPsshAtom (Lkotlin/uuid/Uuid;[Lkotlin/uuid/Uuid;[B)[B } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition { @@ -8651,19 +8613,19 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData { - public fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)V - public synthetic fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/UUID; + public fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)V + public synthetic fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlin/uuid/Uuid; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()[B - public final fun copy (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public final fun copy (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; public fun equals (Ljava/lang/Object;)Z public final fun getData ()[B public final fun getLicenseServerUrl ()Ljava/lang/String; public final fun getMimeType ()Ljava/lang/String; - public final fun getUuid ()Ljava/util/UUID; + public final fun getUuid ()Lkotlin/uuid/Uuid; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 92969baa6..59a36ce7f 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -59,6 +59,7 @@ kotlin { implementation(libs.kotlinx.atomicfu) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.io.core) implementation(libs.kotlinx.serialization.json) // JSON Parser implementation(libs.ksoup) // HTML Parser implementation(libs.ktor.http) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index cd5d752b3..3edd0f410 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -21,9 +21,10 @@ package com.lagradost.cloudstream3.utils import com.lagradost.cloudstream3.base64DecodeArray import io.ktor.http.Url -import java.io.IOException -import java.nio.ByteBuffer -import java.util.UUID +import kotlinx.io.Buffer +import kotlinx.io.IOException +import kotlinx.io.readByteArray +import kotlin.uuid.Uuid @Suppress("unused") object HlsPlaylistParser { @@ -186,13 +187,13 @@ object HlsPlaylistParser { data class SchemeData( /** - * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. + * The [Uuid] of the DRM scheme, or [C.UUID_NIL] if the data is universal (i.e. * applies to all schemes). */ - val uuid: UUID, + val uuid: Uuid, /** The URL of the server to which license requests should be made. May be null if unknown. */ val licenseServerUrl: String? = null, - /** The mimeType of {@link #data}. */ + /** The mimeType of [data]. */ val mimeType: String, /** The initialization data. May be null for scheme support checks only. */ val data: ByteArray @@ -454,14 +455,14 @@ object HlsPlaylistParser { // "." or "..", remove the appropriate segments of the path. if (i == segmentStart + 1 && url[segmentStart] == '.') { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". - url.delete(segmentStart, nextSegmentStart) + url.deleteRange(segmentStart, nextSegmentStart) limit -= nextSegmentStart - segmentStart i = segmentStart } else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1 val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset - url.delete(removeFrom, nextSegmentStart) + url.deleteRange(removeFrom, nextSegmentStart) limit -= nextSegmentStart - removeFrom segmentStart = prevSegmentStart i = prevSegmentStart @@ -532,31 +533,31 @@ object HlsPlaylistParser { } } - object C { + private object C { /** - * UUID for the ClearKey DRM scheme. + * [Uuid] for the ClearKey DRM scheme. * * * ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up. */ - val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) + val CLEARKEY_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) /** - * UUID for the Widevine DRM scheme. + * [Uuid] for the Widevine DRM scheme. * * * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. */ - val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) + val WIDEVINE_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) /** - * UUID for the PlayReady DRM scheme. + * [Uuid] for the PlayReady DRM scheme. * * * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not * provide PlayReady support. */ - val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) + val PLAYREADY_UUID = Uuid.fromLongs(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) /** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */ @@ -1066,7 +1067,7 @@ object HlsPlaylistParser { object PsshAtomUtil { fun buildPsshAtom( - systemId: UUID, keyIds: Array?, data: ByteArray? + systemId: Uuid, keyIds: Array?, data: ByteArray? ): ByteArray { val dataLength = data?.size ?: 0 var psshBoxLength: Int = @@ -1074,26 +1075,30 @@ object HlsPlaylistParser { if (keyIds != null) { psshBoxLength += 4 /* KID_count */ + (keyIds.size * 16) /* KIDs */ } - val psshBox: ByteBuffer = ByteBuffer.allocate(psshBoxLength) - psshBox.putInt(psshBoxLength) - psshBox.putInt(Mp4Box.TYPE_pssh) - psshBox.putInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) - psshBox.putLong(systemId.mostSignificantBits) - psshBox.putLong(systemId.leastSignificantBits) + val buffer = Buffer() + buffer.writeInt(psshBoxLength) + buffer.writeInt(Mp4Box.TYPE_pssh) + buffer.writeInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) + systemId.toLongs { mostSignificantBits, leastSignificantBits -> + buffer.writeLong(mostSignificantBits) + buffer.writeLong(leastSignificantBits) + } if (keyIds != null) { - psshBox.putInt(keyIds.size) + buffer.writeInt(keyIds.size) for (keyId in keyIds) { - psshBox.putLong(keyId.mostSignificantBits) - psshBox.putLong(keyId.leastSignificantBits) + keyId.toLongs { mostSignificantBits, leastSignificantBits -> + buffer.writeLong(mostSignificantBits) + buffer.writeLong(leastSignificantBits) + } } } if (data != null && data.size != 0) { - psshBox.putInt(data.size) - psshBox.put(data) + buffer.writeInt(data.size) + buffer.write(data) } else { - psshBox.putInt(0) + buffer.writeInt(0) } - return psshBox.array() + return buffer.readByteArray() } } From e48c97363e626ee20e74673910e7f8a771ab351a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:27:22 -0700 Subject: [PATCH 080/105] [skip ci] Add actual declarations for webMain/webTest (#3072) --- .../com/lagradost/api/ContextHelper.web.kt | 4 +++ .../webMain/kotlin/com/lagradost/api/Log.kt | 21 ++++++++++++ .../extractors/YoutubeExtractor.web.kt | 32 +++++++++++++++++++ .../cloudstream3/mvvm/ArchComponentExt.web.kt | 32 +++++++++++++++++++ .../cloudstream3/utils/Coroutines.web.kt | 14 ++++++++ .../cloudstream3/utils/Ignore.web.kt | 5 +++ 6 files changed, 108 insertions(+) create mode 100644 library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt create mode 100644 library/src/webMain/kotlin/com/lagradost/api/Log.kt create mode 100644 library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt create mode 100644 library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt create mode 100644 library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt create mode 100644 library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt diff --git a/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt b/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt new file mode 100644 index 000000000..83cae11a5 --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt @@ -0,0 +1,4 @@ +package com.lagradost.api + +actual fun getContext(): Any? = null +actual fun setContext(context: Any?) = Unit diff --git a/library/src/webMain/kotlin/com/lagradost/api/Log.kt b/library/src/webMain/kotlin/com/lagradost/api/Log.kt new file mode 100644 index 000000000..be185f0ca --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/api/Log.kt @@ -0,0 +1,21 @@ +package com.lagradost.api + +actual object Log { + actual fun d(tag: String, message: String) { + // There is no console.debug in Kotlin/JS (Yet) + // We will switch this when it is added. + console.log("[$tag] $message") + } + + actual fun i(tag: String, message: String) { + console.info("[$tag] $message") + } + + actual fun w(tag: String, message: String) { + console.warn("[$tag] $message") + } + + actual fun e(tag: String, message: String) { + console.error("[$tag] $message") + } +} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt new file mode 100644 index 000000000..07463bd56 --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt @@ -0,0 +1,32 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink + +/** + * Web/JS target shim. + * + * NewPipeExtractor is JVM-only, so the full extraction logic used on + * Android/desktop can't run here. This stub keeps the multiplatform + * source set compiling; it performs no extraction and simply reports + * that no links were found on this target. + * + * If/when a JS-compatible YouTube extraction path exists, replace the + * body of getUrl() with that implementation. + */ +actual open class YoutubeExtractor actual constructor() : ExtractorApi() { + + actual override val mainUrl = "https://www.youtube.com" + actual override val name = "YouTube" + actual override val requiresReferer = false + + actual override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + // No-op on web target: NewPipeExtractor is unavailable here. + } +} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt new file mode 100644 index 000000000..f108bf7fb --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt @@ -0,0 +1,32 @@ +package com.lagradost.cloudstream3.mvvm + +actual fun platformThrowAbleToResource(throwable: Throwable): Resource { + val message = throwable.message.orEmpty() + return when { + message.contains("timeout", ignoreCase = true) -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } + + message.contains("NetworkError", ignoreCase = true) || + message.contains("Failed to fetch", ignoreCase = true) || + message.contains("ERR_NAME_NOT_RESOLVED", ignoreCase = true) -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + + message.contains("SSL", ignoreCase = true) || + message.contains("certificate", ignoreCase = true) -> { + Resource.Failure( + true, + (throwable.message ?: "SSL error") + "\nTry a VPN or DNS." + ) + } + + else -> safeFail(throwable) + } +} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt new file mode 100644 index 000000000..3ea59f6cc --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt @@ -0,0 +1,14 @@ +package com.lagradost.cloudstream3.utils + +import androidx.annotation.AnyThread +import androidx.annotation.MainThread +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +actual val workerDispatcher: CoroutineDispatcher = Dispatchers.Default +internal actual annotation class WorkerThread() + +@AnyThread +actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { + work.invoke() +} diff --git a/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt b/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt new file mode 100644 index 000000000..0381a1944 --- /dev/null +++ b/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt @@ -0,0 +1,5 @@ +package com.lagradost.cloudstream3.utils + +import kotlin.test.Ignore + +actual typealias IgnoreOnWeb = Ignore From 115be075d4b156b6795ff60bceb96986a629bd99 Mon Sep 17 00:00:00 2001 From: danzybae Date: Mon, 20 Jul 2026 01:30:04 +0700 Subject: [PATCH 081/105] Fix posterHeaders not passed in home preview carousel (#3085) --- .../com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index e42e774b5..1020205ac 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -58,7 +58,7 @@ class HomeScrollAdapter( when (binding) { is HomeScrollViewBinding -> { - binding.homeScrollPreview.loadImage(posterUrl) + binding.homeScrollPreview.loadImage(posterUrl, item.posterHeaders) binding.homeScrollPreviewTags.apply { text = item.tags?.joinToString(" • ") ?: "" isGone = item.tags.isNullOrEmpty() @@ -79,8 +79,8 @@ class HomeScrollAdapter( binding.homeScrollPreview.setOnClickListener { view -> callback.invoke(view ?: return@setOnClickListener, position, item) } - binding.homeScrollPreview.loadImage(posterUrl) + binding.homeScrollPreview.loadImage(posterUrl, item.posterHeaders) } } } -} \ No newline at end of file +} From 2cd65aad4f7c2676beeed676d925bef314fc8880 Mon Sep 17 00:00:00 2001 From: danzybae Date: Sun, 26 Jul 2026 02:53:45 +0700 Subject: [PATCH 082/105] Fix posterHeaders not passed in TV detail menu (#3088) --- .../com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index cfbacc5d1..ce67763d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -877,7 +877,7 @@ class ResultFragmentTv : BaseFragment( resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) - resultPoster.loadImage(d.posterImage) + resultPoster.loadImage(d.posterImage, headers = d.posterHeaders) var isExpanded = false resultDescription.apply { @@ -910,7 +910,7 @@ class ResultFragmentTv : BaseFragment( R.drawable.profile_bg_teal ).random() - backgroundPoster.loadImage(d.posterBackgroundImage) { + backgroundPoster.loadImage(d.posterBackgroundImage, headers = d.posterHeaders) { error { getImageFromDrawable(context ?: return@error null, error) } } From 93d3b36575568bceeaf9e77fef1afb888d88a0dd Mon Sep 17 00:00:00 2001 From: Kraptor123 <89366989+Kraptor123@users.noreply.github.com> Date: Sun, 26 Jul 2026 01:43:11 +0300 Subject: [PATCH 083/105] feat: add settings button to provider selection in home (#3060) --- .../cloudstream3/ui/home/HomeFragment.kt | 24 ++++++++++++- .../sort_bottom_single_provider_choice.xml | 34 ++++++++++++++----- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 96c5f9f56..604695859 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -24,6 +24,8 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.chip.Chip @@ -43,6 +45,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable +import com.lagradost.cloudstream3.plugins.Plugin import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.BaseFragment @@ -424,11 +427,30 @@ class HomeFragment : BaseFragment( .inflate(R.layout.sort_bottom_single_provider_choice, parent, false) val titleText = view.findViewById(R.id.text1) val pinIcon = view.findViewById(R.id.pinicon) + val settingsIcon = view.findViewById(R.id.action_settings) + val name = getItem(position) titleText?.text = name + val providerApi = currentValidApis[position] val isPinned = - pinnedphashset.contains(currentValidApis[position].name) + pinnedphashset.contains(providerApi.name) pinIcon.visibility = if (isPinned) View.VISIBLE else View.GONE + + val pluginInstance = providerApi.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin + val isDownloadedPluginWithSettings = pluginInstance?.openSettings != null && !isLayout(TV) + + settingsIcon.visibility = if (isDownloadedPluginWithSettings) View.VISIBLE else View.GONE + if (isDownloadedPluginWithSettings) { + settingsIcon.setOnClickListener { + try { + val activityContext = it.context.getActivity() ?: it.context + pluginInstance.openSettings?.invoke(activityContext) + } catch (e: Throwable) { + logError(e) + } + } + } + return view } } diff --git a/app/src/main/res/layout/sort_bottom_single_provider_choice.xml b/app/src/main/res/layout/sort_bottom_single_provider_choice.xml index 657e2f2b8..ea778c488 100644 --- a/app/src/main/res/layout/sort_bottom_single_provider_choice.xml +++ b/app/src/main/res/layout/sort_bottom_single_provider_choice.xml @@ -10,13 +10,31 @@ style="@style/CheckLabel" android:layout_gravity="center" tools:text="hello" /> - + android:orientation="horizontal"> + + + + + \ No newline at end of file From 1e33e4227700c90c972b1a3d9071002ad6e8e8ac Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:59:31 +0000 Subject: [PATCH 084/105] fix subtitle clipping (#3099) --- .../lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt | 3 +++ app/src/main/res/layout/player_custom_layout.xml | 2 ++ app/src/main/res/layout/player_custom_layout_tv.xml | 2 ++ app/src/main/res/layout/trailer_custom_layout.xml | 2 ++ 4 files changed, 9 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index cf892424f..bf77ef247 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -115,6 +115,9 @@ class SubtitlesFragment : BaseDialogFragment( ) } + view.clipToPadding = false + view.clipChildren = false + // we default to 25sp, this is needed as RoundedBackgroundColorSpan breaks on override sizes val size = data.fixedTextSize ?: 25.0f view.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, size) diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 04a2a1f1f..c4aa4c473 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -1076,6 +1076,8 @@ diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 077929d87..88e4cd984 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -1116,6 +1116,8 @@ diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 88a318874..ddaa53464 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -102,6 +102,8 @@ From 2eb05e8f354ffdd4f0833dbc365250ac7a99eeb2 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 31 Jul 2026 09:01:45 -0600 Subject: [PATCH 085/105] DataStore: deprecate toKotlinObject (#2964) 1. It can sometimes cause type erasure 2. It is less clear what it is doing 3. It is easy to replace 4. It is very rarely used, almost all app usage has already been replaced, and I haven't found any extensions actually using it but still following normal deprecation cycle as some still might be using it. --- .../lagradost/cloudstream3/utils/DataStore.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) 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 02ee69791..68f262d8d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -181,11 +181,11 @@ object DataStore { } fun Context.getKey(path: String, valueType: Class): T? { - try { + return try { val json: String = getSharedPrefs().getString(path, null) ?: return null - return parseJson(json, valueType.kotlin) - } catch (e: Exception) { - return null + parseJson(json, valueType.kotlin) + } catch (_: Exception) { + null } } @@ -193,21 +193,37 @@ object DataStore { setKey(getFolderName(folder, path), value) } + @Deprecated( + message = "Use parseJson(this) directly instead.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith( + expression = "parseJson(this)", + imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"], + ), + ) inline fun String.toKotlinObject(): T { return parseJson(this) } + @Deprecated( + message = "Use parseJson(this) directly instead.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith( + expression = "parseJson(this)", + imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"], + ), + ) fun String.toKotlinObject(valueType: Class): T { return parseJson(this, valueType.kotlin) } // GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR inline fun Context.getKey(path: String, defVal: T?): T? { - try { + return try { val json: String = getSharedPrefs().getString(path, null) ?: return defVal - return json.toKotlinObject() - } catch (e: Exception) { - return null + parseJson(json) + } catch (_: Exception) { + null } } From f09435dc183ba3e6c37579f10d8d8ba6aa5ba307 Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:25:00 +0000 Subject: [PATCH 086/105] Add source hiding (#2570) --- .../cloudstream3/ui/player/GeneratorPlayer.kt | 110 ++++++++++++---- .../ui/player/PlayerGeneratorViewModel.kt | 62 ++++++++- .../source_priority/QualityDataHelper.kt | 48 ++++++- .../source_priority/SourcePriorityDialog.kt | 121 +++++++++--------- .../SourceProfileSettingsDialog.kt | 48 +++++++ .../player_select_source_priority.xml | 34 +++-- .../layout/player_select_source_priority.xml | 7 + .../layout/source_profile_settings_dialog.xml | 99 ++++++++++++++ app/src/main/res/values/strings.xml | 8 ++ 9 files changed, 426 insertions(+), 111 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt create mode 100644 app/src/main/res/layout/source_profile_settings_dialog.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 91e3ff970..683a322c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -8,6 +8,7 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Build import android.os.Bundle import android.text.Spanned @@ -79,6 +80,7 @@ import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioT import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.ui.player.source_priority.LinkSource +import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog @@ -132,6 +134,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.io.Serializable +import java.lang.ref.WeakReference import java.util.Calendar import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -510,7 +513,7 @@ class GeneratorPlayer : FullScreenPlayer() { showDownloadProgress(DownloadEvent(0, 0, 0, null)) // uiReset() // Removed due to UX - + currentSelectedLink = link // setEpisodes(viewModel.getAllMeta() ?: emptyList()) setPlayerDimen(null) @@ -1112,21 +1115,29 @@ class GeneratorPlayer : FullScreenPlayer() { var sourceIndex = 0 var startSource = 0 - var sortedUrls = emptyList>() + // Filtered and sorted links + var currentHiddenFooter: View? = null + var filteredLinks: List = emptyList() fun refreshLinks(qualityProfile: Int) { - sortedUrls = viewModel.state.sortLinks(qualityProfile) - if (sortedUrls.isEmpty()) { + val currentLinkUsed = currentSelectedLink + // Always display current linkFooter + val sortedLinks = viewModel.state.sortLinks(qualityProfile) + + filteredLinks = sortedLinks.filter { it.shouldUseLink || it.link == currentLinkUsed } + + if (sortedLinks.isEmpty()) { sourceDialog.findViewById(R.id.sort_sources_holder)?.isGone = true } else { - startSource = sortedUrls.indexOf(currentSelectedLink) + startSource = filteredLinks.indexOfFirst { it.link == currentLinkUsed } sourceIndex = startSource val sourcesArrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) - sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) -> + sourcesArrayAdapter.addAll(filteredLinks.map { displayLink -> + val (link, uri) = displayLink.link val name = link?.name ?: uri?.name ?: "NULL" "$name ${Qualities.getStringByInt(link?.quality)}" }) @@ -1142,7 +1153,7 @@ class GeneratorPlayer : FullScreenPlayer() { } providerList.setOnItemLongClickListener { _, _, position, _ -> - sortedUrls.getOrNull(position)?.first?.url?.let { + sortedLinks.getOrNull(position)?.link?.first?.url?.let { clipboardHelper( txt(R.string.video_source), it @@ -1150,6 +1161,25 @@ class GeneratorPlayer : FullScreenPlayer() { } true } + + val hiddenLinks = sortedLinks.size - filteredLinks.size + providerList.removeFooterView(currentHiddenFooter) + + if (hiddenLinks > 0) { + val hiddenLinksFooter: TextView = layoutInflater.inflate( + R.layout.sort_bottom_footer_add_choice, null + ) as TextView + + providerList.addFooterView(hiddenLinksFooter, null, false) + currentHiddenFooter = hiddenLinksFooter + + val hiddenLinksText = + ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks) + .format(hiddenLinks) + hiddenLinksFooter.text = hiddenLinksText + hiddenLinksFooter.setCompoundDrawables(null, null, null, null) + hiddenLinksFooter.setTypeface(null, Typeface.ITALIC) + } } } @@ -1363,8 +1393,8 @@ class GeneratorPlayer : FullScreenPlayer() { } } if (init) { - sortedUrls.getOrNull(sourceIndex)?.let { - loadLink(it, true) + filteredLinks.getOrNull(sourceIndex)?.let { + loadLink(it.link, true) } } sourceDialog.dismissSafe(activity) @@ -1532,6 +1562,10 @@ class GeneratorPlayer : FullScreenPlayer() { } override fun playerError(exception: Throwable) { + currentSelectedLink?.let { link -> + viewModel.modifyState { this.addError(link) } + } + val currentUrl = currentSelectedLink?.let { it.first?.url ?: it.second?.uri?.toString() } ?: "unknown" val headers = currentSelectedLink?.first?.headers?.toString() ?: "none" @@ -1554,8 +1588,22 @@ class GeneratorPlayer : FullScreenPlayer() { private fun noLinksFound() { viewModel.forceClearCache = true + val hiddenLinks = viewModel.state.sortLinks(currentQualityProfile).count { !it.shouldUseLink } + + context?.let { ctx -> + // Display that there are hidden links to the user. + if (hiddenLinks > 0) { + val noLinksString = ctx.getString(R.string.no_links_found_toast) + val hiddenString = + ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks) + .format(hiddenLinks) + val toastText = "$noLinksString\n($hiddenString)" + showToast(toastText, Toast.LENGTH_SHORT) + } else { + showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) + } + } - showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) activity?.popCurrentPage() } @@ -1566,7 +1614,9 @@ class GeneratorPlayer : FullScreenPlayer() { } val links = viewModel.state.sortLinks(currentQualityProfile) - if (links.isEmpty()) { + + val firstAvailableLink = links.firstOrNull { it.shouldUseLink }?.link + if (firstAvailableLink == null) { noLinksFound() return } @@ -1574,7 +1624,7 @@ class GeneratorPlayer : FullScreenPlayer() { if (!isPlayerActive.compareAndSet(false, true)) { return } - loadLink(links.first(), false) + loadLink(firstAvailableLink, false) showPlayerMetadata() } @@ -1641,25 +1691,26 @@ class GeneratorPlayer : FullScreenPlayer() { } } - override fun hasNextMirror(): Boolean { + private fun getNextLink(): DisplayLink? { val links = viewModel.state.sortLinks(currentQualityProfile) - return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size + val currentIndex = links.indexOfFirst { it.link == currentSelectedLink } + val nextPotentialLink = + links.withIndex().firstOrNull { it.index > currentIndex && it.value.shouldUseLink } + return nextPotentialLink?.value + } + + override fun hasNextMirror(): Boolean { + return getNextLink() != null } override fun nextMirror() { - val links = viewModel.state.sortLinks(currentQualityProfile) - if (links.isEmpty()) { + val nextLink = getNextLink() + if (nextLink == null) { noLinksFound() return } - val newIndex = links.indexOf(currentSelectedLink) + 1 - if (newIndex >= links.size) { - noLinksFound() - return - } - - loadLink(links[newIndex], true) + loadLink(nextLink.link, true) } override fun onDestroy() { @@ -2166,6 +2217,7 @@ class GeneratorPlayer : FullScreenPlayer() { isPlayerActive.set(false) binding?.overlayLoadingSkipButton?.isVisible = false binding?.playerLoadingOverlay?.isVisible = true + viewModel.modifyState { setError(emptyList()) } uiReset() } @@ -2297,19 +2349,23 @@ class GeneratorPlayer : FullScreenPlayer() { } } - observe(viewModel.currentLinks) { (links, instance) -> + observe(viewModel.currentLinks) { (_, instance) -> if (instance != viewModel.state.instance) return@observe // Outdated observe - val turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true + val sortedLinks = viewModel.state.sortLinks(currentQualityProfile) + val usableLinks = sortedLinks.count { link -> link.shouldUseLink } + + val turnVisible = usableLinks > 0 && viewModel.generator?.canSkipLoading == true val wasGone = binding.overlayLoadingSkipButton.isGone binding.overlayLoadingSkipButton.apply { isVisible = turnVisible - if (links.isEmpty()) { + + if (usableLinks == 0) { setText(R.string.skip_loading) } else { @SuppressLint("SetTextI18n") - text = "${context.getString(R.string.skip_loading)} (${links.size})" + text = "${context.getString(R.string.skip_loading)} (${usableLinks})" } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index e3c390d50..cb8cf8bff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -10,6 +10,8 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings +import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -40,12 +42,20 @@ data class GeneratorState( val id: Int?, ) +data class DisplayLink( + val link: VideoLink, + // If the link should be displayed and used by the player + val shouldUseLink: Boolean, + val priority: Int +) + /** Immutable state of all current links relevant to displaying the video */ // @MustUseReturnValues // @Immutable data class VideoState( val subtitles: PersistentSet = persistentSetOf(), val links: PersistentSet = persistentSetOf(), + val erroredLinks: PersistentSet = persistentSetOf(), val stamps: PersistentList = persistentListOf(), val loading: Resource = Resource.Loading(), val generatorState: GeneratorState? = null, @@ -56,19 +66,52 @@ data class VideoState( * * sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation * */ - private val sortedLinks: ConcurrentHashMap> = ConcurrentHashMap() + private val sortedLinks: ConcurrentHashMap> = ConcurrentHashMap() + /** + * The cache is guaranteed to be up to date link-wise due to the immutable links. + * However, hideNegativeSources and hideErrorSources could be updated, which requires clearing the cache. + */ fun clearSortedLinksCache() = sortedLinks.clear() + private fun hasLinkErrored(link: VideoLink): Boolean { + return erroredLinks.any { it == link } + } + + private fun VideoLink.toDisplayLink( + qualityProfile: Int, + hideNegativeSources: Boolean, + hideErrorSources: Boolean + ): DisplayLink { + val priority = getLinkPriority(qualityProfile, this.first) + val shouldHideLink = + (hideNegativeSources && priority < 0) || (hideErrorSources && hasLinkErrored(this)) + val displayLink = DisplayLink(this, !shouldHideLink, priority) + + return displayLink + } + // Modifying sortedLinks is not considered a "visible" side effect, and rerunning it does not change the result // It is by all standards, idempotent and by extension also pure as it has no "visible" side effect /** Returns .links in the sorted order according to the qualityProfile. * Use .links if order is not needed */ @Contract(pure = true) - fun sortLinks(qualityProfile: Int): List { - return sortedLinks[qualityProfile] ?: links.sortedBy { link -> + fun sortLinks(qualityProfile: Int): List { + sortedLinks[qualityProfile]?.let { + return it + } + + val hideNegativeSources = + QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideNegativeSources) + val hideErrorSources = + QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideErrorSources) + + return links.map { link -> // negative because we want to sort highest quality first - -getLinkPriority(qualityProfile, link.first) + link.toDisplayLink(qualityProfile, hideNegativeSources, hideErrorSources) + }.sortedBy { + // negative because we want to sort highest quality first + -it.priority }.also { value -> sortedLinks[qualityProfile] = value } } @@ -113,6 +156,12 @@ data class VideoState( @JvmName("setVideoSkipStamp") @Contract(pure = true) fun set(items: Collection): VideoState = copy(stamps = items.toPersistentList()) + + @Contract(pure = true) + fun addError(item: VideoLink): VideoState = copy(erroredLinks = erroredLinks.add(item)) + + @Contract(pure = true) + fun setError(items: Collection): VideoState = copy(erroredLinks = items.toPersistentSet()) } data class VideoLive( @@ -141,9 +190,8 @@ class PlayerGeneratorViewModel : ViewModel() { var state = VideoState(instance = 0) private set - private val _currentLinks = - MutableLiveData>>>(null) - val currentLinks: LiveData>>> = _currentLinks + private val _currentLinks = MutableLiveData>>(null) + val currentLinks: LiveData>> = _currentLinks private val _currentSubtitles = MutableLiveData>>(null) val currentSubtitles: LiveData>> = _currentSubtitles diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt index 15d25148c..d8d47bd10 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt @@ -12,12 +12,16 @@ import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities +import java.util.EnumMap +import java.util.concurrent.ConcurrentHashMap +import kotlin.also import kotlin.math.abs object QualityDataHelper { private const val VIDEO_SOURCE_PRIORITY = "video_source_priority" private const val VIDEO_PROFILE_NAME = "video_profile_name" private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority" + const val VIDEO_PROFILE_SETTINGS = "video_profile_settings" // Old key only supporting one type per profile @Deprecated("Changed to support multiple types per profile") @@ -53,13 +57,21 @@ object QualityDataHelper { val types: Set ) + + // Map profile and name to priority + val sourcePriorityCache: ConcurrentHashMap> = ConcurrentHashMap() + fun getSourcePriority(profile: Int, name: String?): Int { if (name == null) return DEFAULT_SOURCE_PRIORITY - return getKey( + + return sourcePriorityCache[profile]?.get(name) ?: (getKey( "$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile", name, DEFAULT_SOURCE_PRIORITY - ) ?: DEFAULT_SOURCE_PRIORITY + ) ?: DEFAULT_SOURCE_PRIORITY).also { + sourcePriorityCache.getOrPut(profile) { hashMapOf() } + sourcePriorityCache[profile]?.set(name, it) + } } fun getAllSourcePriorityNames(profile: Int): List { @@ -77,6 +89,8 @@ object QualityDataHelper { } else { setKey(folder, name, priority) } + + sourcePriorityCache[profile]?.set(name, priority) } fun setProfileName(profile: Int, name: String?) { @@ -93,12 +107,17 @@ object QualityDataHelper { ?: txt(R.string.profile_number, profile) } + // Map profile and quality to priority + val qualityPriorityCache: ConcurrentHashMap> = ConcurrentHashMap() fun getQualityPriority(profile: Int, quality: Qualities): Int { - return getKey( + return qualityPriorityCache[profile]?.get(quality) ?: (getKey( "$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile", quality.value.toString(), quality.defaultPriority - ) ?: quality.defaultPriority + )?.also { + qualityPriorityCache.getOrPut(profile) { EnumMap(Qualities::class.java) } + qualityPriorityCache[profile]?.set(quality, it) + }) ?: quality.defaultPriority } fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) { @@ -107,8 +126,24 @@ object QualityDataHelper { quality.value.toString(), priority ) + qualityPriorityCache[profile]?.set(quality, priority) } + fun setProfileSetting(profile: Int, setting: ProfileSettings, value: T) { + val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile" + // Prevent unnecessary keys + if (value == setting.defaultValue) { + removeKey(folder, setting.key) + } else { + setKey(folder, setting.key, value) + } + } + + inline fun getProfileSetting(profile: Int, setting: ProfileSettings): T { + val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile" + val value = getKey(folder, setting.key) + return value ?: setting.defaultValue + } @Suppress("DEPRECATION") fun getQualityProfileTypes(profile: Int): Set { @@ -224,3 +259,8 @@ object QualityDataHelper { return Qualities.entries.minBy { abs(it.value - target) } } } + +sealed class ProfileSettings(val key: String, val defaultValue: T) { + object HideErrorSources : ProfileSettings("hide_error_sources", false) + object HideNegativeSources : ProfileSettings("hide_negative_sources", false) +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt index c8ac96ebb..604ec3149 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt @@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding class SourcePriorityDialog( val ctx: Context, - @StyleRes themeRes: Int, + @StyleRes val themeRes: Int, val links: List, private val profile: QualityDataHelper.QualityProfile, /** @@ -28,76 +28,79 @@ class SourcePriorityDialog( PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false) setContentView(binding.root) fixSystemBarsPadding(binding.root) - val sourcesRecyclerView = binding.sortSources - val qualitiesRecyclerView = binding.sortQualities - val profileText = binding.profileTextEditable - val saveBtt = binding.saveBtt - val exitBtt = binding.closeBtt - val helpBtt = binding.helpBtt - profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context)) - profileText.hint = txt(R.string.profile_number, profile.id).asString(context) + binding.apply { + profileTextEditable.setText( + QualityDataHelper.getProfileName(profile.id).asString(context) + ) + profileTextEditable.hint = txt(R.string.profile_number, profile.id).asString(context) - sourcesRecyclerView.adapter = PriorityAdapter( - ).apply { - submitList(links.map { link -> - SourcePriority( - null, - link.source, - QualityDataHelper.getSourcePriority(profile.id, link.source) - ) - }.distinctBy { it.name }.sortedBy { -it.priority }) - } + sortSources.adapter = PriorityAdapter( + ).apply { + val sortedLinks = links.map { link -> + SourcePriority( + null, + link.source, + QualityDataHelper.getSourcePriority(profile.id, link.source) + ) + }.distinctBy { it.name }.sortedBy { -it.priority } - qualitiesRecyclerView.adapter = PriorityAdapter( - ).apply { - submitList(Qualities.entries.mapNotNull { - SourcePriority( - it, - Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null }, - QualityDataHelper.getQualityPriority(profile.id, it) - ) - }.sortedBy { -it.priority }) - } - - @Suppress("UNCHECKED_CAST") // We know the types - saveBtt.setOnClickListener { - val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter - val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter - - val qualities = qualityAdapter?.immutableCurrentList ?: emptyList() - val sources = sourcesAdapter?.immutableCurrentList ?: emptyList() - - qualities.forEach { - QualityDataHelper.setQualityPriority(profile.id, it.data, it.priority) + submitList(sortedLinks) } - sources.forEach { - QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority) + sortQualities.adapter = PriorityAdapter( + ).apply { + submitList(Qualities.entries.mapNotNull { + SourcePriority( + it, + Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null }, + QualityDataHelper.getQualityPriority(profile.id, it) + ) + }.sortedBy { -it.priority }) } - qualityAdapter?.submitList(qualities.sortedBy { -it.priority }) - sourcesAdapter?.submitList(sources.sortedBy { -it.priority }) + @Suppress("UNCHECKED_CAST") // We know the types + saveBtt.setOnClickListener { + val qualityAdapter = sortQualities.adapter as? PriorityAdapter + val sourcesAdapter = sortSources.adapter as? PriorityAdapter - val savedProfileName = profileText.text.toString() - if (savedProfileName.isBlank()) { - QualityDataHelper.setProfileName(profile.id, null) - } else { - QualityDataHelper.setProfileName(profile.id, savedProfileName) + val qualities = qualityAdapter?.immutableCurrentList ?: emptyList() + val sources = sourcesAdapter?.immutableCurrentList ?: emptyList() + + qualities.forEach { + QualityDataHelper.setQualityPriority(profile.id, it.data, it.priority) + } + + sources.forEach { + QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority) + } + + qualityAdapter?.submitList(qualities.sortedBy { -it.priority }) + sourcesAdapter?.submitList(sources.sortedBy { -it.priority }) + + val savedProfileName = profileTextEditable.text.toString() + if (savedProfileName.isBlank()) { + QualityDataHelper.setProfileName(profile.id, null) + } else { + QualityDataHelper.setProfileName(profile.id, savedProfileName) + } + updatedCallback.invoke() } - updatedCallback.invoke() - } - exitBtt.setOnClickListener { - this.dismissSafe() - } + closeBtt.setOnClickListener { + dismissSafe() + } - helpBtt.setOnClickListener { - AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { - setMessage(R.string.quality_profile_help) - }.show() - } + helpBtt.setOnClickListener { + AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { + setMessage(R.string.quality_profile_help) + }.show() + } + settingsBtt.setOnClickListener { + SourceProfileSettingsDialog(ctx, themeRes, profile.id).show() + } + } super.show() } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt new file mode 100644 index 000000000..c26955b5e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt @@ -0,0 +1,48 @@ +package com.lagradost.cloudstream3.ui.player.source_priority + +import android.app.Dialog +import android.content.Context +import android.view.LayoutInflater +import androidx.annotation.StyleRes +import com.lagradost.cloudstream3.databinding.SourceProfileSettingsDialogBinding +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding + +class SourceProfileSettingsDialog( + val ctx: Context, + @StyleRes themeRes: Int, + val profile: Int +) : Dialog(ctx, themeRes) { + override fun show() { + val binding = + SourceProfileSettingsDialogBinding.inflate(LayoutInflater.from(ctx), null, false) + setContentView(binding.root) + fixSystemBarsPadding(binding.root) + + binding.apply { + var hideErrorSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideErrorSources) + var hideNegativeSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideNegativeSources) + + profileHideErrorSources.isChecked = hideErrorSources + profileHideErrorSources.setOnCheckedChangeListener { _, bool -> + hideErrorSources = bool + } + + profileHideNegativeSources.isChecked = hideNegativeSources + profileHideNegativeSources.setOnCheckedChangeListener { _, bool -> + hideNegativeSources = bool + } + + applyBtt.setOnClickListener { + QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideErrorSources, hideErrorSources) + QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideNegativeSources, hideNegativeSources) + dismissSafe() + } + + cancelBtt.setOnClickListener { + dismissSafe() + } + } + super.show() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout-port/player_select_source_priority.xml b/app/src/main/res/layout-port/player_select_source_priority.xml index 2cba9c869..926227c58 100644 --- a/app/src/main/res/layout-port/player_select_source_priority.xml +++ b/app/src/main/res/layout-port/player_select_source_priority.xml @@ -1,6 +1,5 @@ - - + @@ -33,14 +32,21 @@ android:textSize="20sp" android:textStyle="bold" /> + + + android:src="@drawable/baseline_help_outline_24" /> - + + tools:ignore="LabelFor" + tools:text="@string/profile_number" /> + android:text="@string/sort_save" /> + android:text="@string/sort_close" /> diff --git a/app/src/main/res/layout/player_select_source_priority.xml b/app/src/main/res/layout/player_select_source_priority.xml index 182cd1861..0d825de77 100644 --- a/app/src/main/res/layout/player_select_source_priority.xml +++ b/app/src/main/res/layout/player_select_source_priority.xml @@ -160,6 +160,13 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31cf951cf..14780da6b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -622,6 +622,14 @@ Subscribe Unsubscribe Profile %d + Profile settings + Hide sources with a negative priority + Hide sources with errors + + %d hidden link + %d hidden links + + Wi-Fi Mobile data Set default From a2adb6caaad38465b7b1e75a2d0de35c7e95ccf2 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:35:02 +0000 Subject: [PATCH 087/105] [skip ci] Swallow exception when device does not support channels --- .../com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index 4c0717b3f..b5b367262 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -149,10 +149,12 @@ object TvChannelUtils { .setInputId(inputId) .build() - val channelUri = context.contentResolver.insert( - TvContractCompat.Channels.CONTENT_URI, - channel.toContentValues() - ) + val channelUri = runCatching { + context.contentResolver.insert( + TvContractCompat.Channels.CONTENT_URI, + channel.toContentValues() + ) + }.getOrNull() channelUri?.let { val channelId = ContentUris.parseId(it) From ff701cb220897af32b02623db333adf263b443aa Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:46 +0000 Subject: [PATCH 088/105] Add Belarusian, Catalan and Albanian --- .../com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt | 3 +++ app/src/main/res/{values-be => values-b+be}/strings.xml | 0 app/src/main/res/{values-ca => values-b+ca}/strings.xml | 0 app/src/main/res/{values-sq => values-b+sq}/strings.xml | 0 4 files changed, 3 insertions(+) rename app/src/main/res/{values-be => values-b+be}/strings.xml (100%) rename app/src/main/res/{values-ca => values-b+ca}/strings.xml (100%) rename app/src/main/res/{values-sq => values-b+sq}/strings.xml (100%) 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 354424978..631b12990 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 @@ -132,6 +132,9 @@ val appLanguages = arrayListOf( Pair("日本語 (にほんご)", "ja"), Pair("正體中文(臺灣)", "zh-TW"), Pair("한국어", "ko"), + Pair("беларуская мова", "be"), + Pair("català", "ca"), + Pair("Shqip мова", "sq"), /* end language list */ ).sortedBy { it.first.lowercase(Locale.ROOT) } // ye, we go alphabetical, so ppl don't put their lang on top diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-b+be/strings.xml similarity index 100% rename from app/src/main/res/values-be/strings.xml rename to app/src/main/res/values-b+be/strings.xml diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-b+ca/strings.xml similarity index 100% rename from app/src/main/res/values-ca/strings.xml rename to app/src/main/res/values-b+ca/strings.xml diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-b+sq/strings.xml similarity index 100% rename from app/src/main/res/values-sq/strings.xml rename to app/src/main/res/values-b+sq/strings.xml From 7f750964f3929679a6674f1d13d62d082110af56 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:00:05 +0000 Subject: [PATCH 089/105] chore(locales): fix locale issues --- .../lagradost/cloudstream3/ui/settings/SettingsGeneral.kt | 6 +++--- app/src/main/res/values-b+be/strings.xml | 2 +- app/src/main/res/values-b+ca/strings.xml | 2 +- app/src/main/res/values-b+sq/strings.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) 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 631b12990..b04f9a4a2 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 @@ -77,6 +77,7 @@ val appLanguages = arrayListOf( Pair("Azərbaycan dili", "az"), Pair("Bahasa Indonesia", "in"), Pair("Bahasa Melayu", "ms"), + Pair("català", "ca"), Pair("Deutsch", "de"), Pair("English", "en"), Pair("Español", "es"), @@ -97,6 +98,7 @@ val appLanguages = arrayListOf( Pair("Português", "pt"), Pair("Português (Brasil)", "pt-BR"), Pair("Română", "ro"), + Pair("Shqip мова", "sq"), Pair("Slovenčina", "sk"), Pair("Soomaaliga", "so"), Pair("Svenska", "sv"), @@ -106,6 +108,7 @@ val appLanguages = arrayListOf( Pair("Wikang Filipino", "fil"), Pair("Čeština", "cs"), Pair("Ελληνικά", "el"), + Pair("беларуская мова", "be"), Pair("български", "bg"), Pair("македонски", "mk"), Pair("русский", "ru"), @@ -132,9 +135,6 @@ val appLanguages = arrayListOf( Pair("日本語 (にほんご)", "ja"), Pair("正體中文(臺灣)", "zh-TW"), Pair("한국어", "ko"), - Pair("беларуская мова", "be"), - Pair("català", "ca"), - Pair("Shqip мова", "sq"), /* end language list */ ).sortedBy { it.first.lowercase(Locale.ROOT) } // ye, we go alphabetical, so ppl don't put their lang on top diff --git a/app/src/main/res/values-b+be/strings.xml b/app/src/main/res/values-b+be/strings.xml index 3d9200faf..9fbf8a8d5 100644 --- a/app/src/main/res/values-b+be/strings.xml +++ b/app/src/main/res/values-b+be/strings.xml @@ -125,7 +125,7 @@ Працягнуць прагляд Выдаліць Больш інфармацыі - \@string/home_play + @string/home_play Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN Гэты пастаўшчык — Torrent, рэкамендуецца VPN Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма. diff --git a/app/src/main/res/values-b+ca/strings.xml b/app/src/main/res/values-b+ca/strings.xml index 495eb5e3c..621243c78 100644 --- a/app/src/main/res/values-b+ca/strings.xml +++ b/app/src/main/res/values-b+ca/strings.xml @@ -123,7 +123,7 @@ Continua mirant Eliminar Més info - \@string/home_play + @string/home_play Potser necessiteu una VPN per que aquest proveïdor funcioni correctament Aquest proveïdor és un Torrent, es recomana fer servir una VPN El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc. diff --git a/app/src/main/res/values-b+sq/strings.xml b/app/src/main/res/values-b+sq/strings.xml index a13f87bfa..f45c8d87a 100644 --- a/app/src/main/res/values-b+sq/strings.xml +++ b/app/src/main/res/values-b+sq/strings.xml @@ -127,7 +127,7 @@ Vazhdo shikimin Hiq Më shumë informacion - \@string/home_play + @string/home_play Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull Ky ofrues është Torrent, rekomandohet një VPN Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe. From e80fa1b30dc56b6cfb39ace68c8c284b255df785 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 31 Jul 2026 19:00:10 +0200 Subject: [PATCH 090/105] Translated using Weblate (Albanian) Currently translated at 100.0% (733 of 733 strings) Translated using Weblate (Polish) Currently translated at 100.0% (733 of 733 strings) Translated using Weblate (Bengali) Currently translated at 52.6% (384 of 729 strings) Translated using Weblate (English) Currently translated at 100.0% (729 of 729 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Update translation files Updated by "Remove blank strings" hook in Weblate. Translated using Weblate (Bengali) Currently translated at 51.0% (372 of 729 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Dutch) Currently translated at 92.4% (674 of 729 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (729 of 729 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (729 of 729 strings) Translated using Weblate (Bengali) Currently translated at 75.0% (3 of 4 strings) Translated using Weblate (Bengali) Currently translated at 49.9% (364 of 729 strings) Co-authored-by: 007 Co-authored-by: Aishik Mukherjee Co-authored-by: Hosted Weblate Co-authored-by: Man Co-authored-by: Matthaiks Co-authored-by: caviaz Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/bn/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/en/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/nl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sq/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/bn/ Translation: Cloudstream/App Translation: Cloudstream/Fastlane --- app/src/main/res/values-b+bn/strings.xml | 49 ++++++++++++++++--- app/src/main/res/values-b+nl/strings.xml | 13 +++++ app/src/main/res/values-b+pl/strings.xml | 9 ++++ app/src/main/res/values-b+sq/strings.xml | 7 +++ app/src/main/res/values-b+vi/strings.xml | 10 ++-- app/src/main/res/values/strings.xml | 1 - fastlane/metadata/android/bn/changelogs/2.txt | 1 + .../metadata/android/bn/full_description.txt | 7 +++ .../metadata/android/bn/short_description.txt | 1 + fastlane/metadata/android/bn/title.txt | 1 + 10 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 fastlane/metadata/android/bn/changelogs/2.txt create mode 100644 fastlane/metadata/android/bn/full_description.txt create mode 100644 fastlane/metadata/android/bn/short_description.txt create mode 100644 fastlane/metadata/android/bn/title.txt diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index e90608793..b6edbd1cd 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -3,7 +3,7 @@ পোস্টার ক্লাউডস্ট্রিম দিয়ে চালান হোম - অভিনয়েঃ %s + অভিনয়ে: %s %1$dদিন %2$dঘন্টা %3$dমিনিট %1$dঘন্টা %2$dমিনিট %d মিনিট @@ -22,7 +22,7 @@ খুঁজুন ডাউনলোডসমূহ সেটিংস - %1$s এপি %2$d + %1$s পর্ব %2$d পর্ব %d মুক্তির তারিখ শেয়ার ব্রাউজারে খুলুন @@ -131,8 +131,8 @@ স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন। তথ্য ট্রেইলার প্রদর্শন করুন - কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন - স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ + Kitsu থেকে পোস্টারসমূহ প্রদর্শন করুন + স্বয়ংক্রিয় প্লাগইন আপডেট স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড হালনাগাদ ও ব্যাকআপ অ্যাপ এর হালনাগাদ দেখান @@ -148,9 +148,9 @@ সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন ফাইল ডিলিট মান ডিফল্ট এ রিসেট করুন - স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন + স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন যোগ। সিনেমা - এনিমে এর ফিলার পর্ব দেখায় + অ্যানিমে ফিলার পর্ব প্রদর্শন টিভি সিরিজ লিংক পাওয়া যায়নি চা খাওয়ানো হয়েছে @@ -174,7 +174,7 @@ ডিলিট শুরু কার্টুন - কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন। + Some devices do not support the new package installer. Try the legacy option if updates do not install। সাবটাইটেল নেই এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না উন্নত অনুসন্ধান @@ -204,7 +204,7 @@ আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন 18+ এনিমে - অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান + অনুসন্ধানের ফলে নির্বাচিত ভিডিওর মান লুকান অ্যাপ লাইভস্ট্রিম APK ইনস্টলার @@ -354,4 +354,37 @@ স্পিচ রিকগনিশন উপলব্ধ নেই কথা বলা শুরু করুন… ডাউনলোড তালিকা + সম্পূর্ণ সিরিজ প্লে করুন + এই ভিডিওটি একটি টরেন্ট, অর্থাৎ আপনার ভিডিও অ্যাক্টিভিটি ট্র্যাক করা সম্ভব।\nএগোনোর আগে টরেন্টিং সম্পর্কে ভালোভাবে বুঝে নিন। + ডিলিট করার জন্য আইটেম সিলেক্ট করুন + বর্তমানে কোনো ডাউনলোড নেই। + বর্তমানে কোনো ডাউনলোড কিউতে নেই। + অফলাইনে দেখার জন্য উপলব্ধ + সব সিলেক্ট করুন + সব ডিসিলেক্ট করুন + লোকাল ভিডিও খুলুন + অতিরিক্ত ব্রাইটনেস + ডিসপ্লে ব্রাইটনেস ১০০% ছাড়িয়ে গেলে ব্রাইটনেস ফিল্টার চালু করুন + সার্চ সাজেশন + টাইপ করার সময় সার্চ সাজেশন দেখান + সাজেশন মুছে ফেলুন + প্লেয়ার মেটাডেটা ওভারলে প্রদর্শন + কাস্ট প্যানেল প্রদর্শন + প্রাক-মুক্তি সংস্করণ ইনস্টলেশন + প্রাক-মুক্তি সংস্করণ ইতোমধ্যে ইনস্টল রয়েছে। + প্রাক-মুক্তি সংস্করণ ইনস্টল করতে সমস্যা হয়েছে। + (%1$d | %2$s) মুছুন + সাবধান + আপনি কি নিম্নলিখিত আইটেমগুলো স্থায়ীভাবে মুছতে চান?\n\n%s + ফাইল মুছুন + আপনি কি নিশ্চিত যে আপনি %1$s-এ নিচের এপিসোডগুলো স্থায়ীভাবে মুছে ফেলতে চান?\n\n%2$s + আপনি নিচের সিরিজগুলোর সব এপিসোডও স্থায়ীভাবে মুছে ফেলবেন:\n\n%s + আপনি কি নিশ্চিত যে আপনি নিচের সিরিজটির সব এপিসোড স্থায়ীভাবে মুছে ফেলতে চান?\n\n%s + সঙ্গীত + অডিও বই + মিডিয়া + অডিও + পডকাস্ট + ভিডিও + এনকোডিং ত্রুটি diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index 8a2424326..feec84263 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -682,4 +682,17 @@ Zet torrent aan in Instellingen/Providers/Media voorkeur Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan. Handmatige Update Knop + Open de app met Vingerafdruk, Face ID, PIN, Pattern en Wachtwoord. + Seekbar preview + Zet preview thumbnail aan op seekbar + Software decodering + Software decodering staat de mogelijkheid toe om de speler videobestanden af te laten spelen op jouw apparaat, maar het kan een haperige of onstabiele kijkervaring zorgen op hoge resolutie. + Volume heeft de 100% overschreden + Update Plugins + Geen plugins zijn geupdate. + Speler meldingen + De speler melding om de kijkervaring van de achtergrond de besturen + Online + Hoeveel verschillende items kunnen in parallel gedownload worden + Parallel downloads diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 35367a1b2..b69146c92 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -742,4 +742,13 @@ Wideo Zapowiedź Na żywo + Ustawienia profilu + Ukryj źródła z negatywnym priorytetem + Ukryj źródła z błędami + + %d ukryty odnośnik + %d ukryte odnośniki + %d ukrytych odnośników + %d ukrytych odnośników + diff --git a/app/src/main/res/values-b+sq/strings.xml b/app/src/main/res/values-b+sq/strings.xml index a13f87bfa..4716d757f 100644 --- a/app/src/main/res/values-b+sq/strings.xml +++ b/app/src/main/res/values-b+sq/strings.xml @@ -735,4 +735,11 @@ %d shkarkime në radhë Live + Cilësimet e profil-it + Fshih burimet me prioritet negativ + Fshih burimet me error-e + + %d link i fshehur + %d linqe të fshehura + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 3b2b3876f..53679c05c 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -36,7 +36,7 @@ Tập tiếp theo Thể loại Chia sẻ - Mở bằng trình duyệt + Phát bằng Trình duyệt Bỏ tải Đang tải… Đang xem @@ -255,13 +255,13 @@ Chất lượng xem ưu tiên (WiFi) Số ký tự tối đa tiêu đề trình phát Hiển thị thông tin trình phát - Kích thước bộ nhớ đệm video - Thời lượng bộ nhớ đệm + Kích thước bộ đệm video + Thời lượng bộ đệm video Bộ nhớ đệm video trên thiết bị Xoá bộ nhớ đệm hình ảnh và video Sẽ gây lỗi nếu đặt quá cao trên thiết bị có bộ nhớ thấp như Android TV. - Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng lưu trữ thấp như Android TV. - DNS over HTTPS + Sẽ gây lỗi nếu đặt quá cao trên thiết bị có dung lượng lưu trữ thấp như Android TV. + DNS qua HTTPS Rất hữu ích để bỏ chặn ISP Bản sao trang web Xoá trang web diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14780da6b..4a918231f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -791,5 +791,4 @@ %d downloads queued Live - diff --git a/fastlane/metadata/android/bn/changelogs/2.txt b/fastlane/metadata/android/bn/changelogs/2.txt new file mode 100644 index 000000000..91032cb8a --- /dev/null +++ b/fastlane/metadata/android/bn/changelogs/2.txt @@ -0,0 +1 @@ +- চেঞ্জলগ যোগ করা হয়েছে! diff --git a/fastlane/metadata/android/bn/full_description.txt b/fastlane/metadata/android/bn/full_description.txt new file mode 100644 index 000000000..e1866f54f --- /dev/null +++ b/fastlane/metadata/android/bn/full_description.txt @@ -0,0 +1,7 @@ +CloudStream-3 দিয়ে সিনেমা, টিভি-সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করা যায়। + +অ্যাপে কোনো বিজ্ঞাপন ও অ্যানালিটিক্স নেই এবং +এটি একাধিক ট্রেলার ও মুভি সাইট সাপোর্ট করে, আরও আছে যেমন— +বুকমার্ক +সাবটাইটেল ডাউনলোড +ক্রোমকাস্ট সাপোর্ট diff --git a/fastlane/metadata/android/bn/short_description.txt b/fastlane/metadata/android/bn/short_description.txt new file mode 100644 index 000000000..3d62da485 --- /dev/null +++ b/fastlane/metadata/android/bn/short_description.txt @@ -0,0 +1 @@ +সিনেমা, টিভি সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করুন। diff --git a/fastlane/metadata/android/bn/title.txt b/fastlane/metadata/android/bn/title.txt new file mode 100644 index 000000000..dde89d58f --- /dev/null +++ b/fastlane/metadata/android/bn/title.txt @@ -0,0 +1 @@ +CloudStream From caf0db3dc13bd75a496d4a94152bb8f22f8fdb1e Mon Sep 17 00:00:00 2001 From: Jelle Thijs Date: Sat, 1 Aug 2026 03:14:02 +0200 Subject: [PATCH 091/105] Feat: Add optional clock to TV player --- .../cloudstream3/ui/home/HomeFragment.kt | 28 +++++++++++++++++++ .../ui/player/FullScreenPlayer.kt | 3 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 8 ++++++ .../cloudstream3/ui/settings/SettingsUI.kt | 2 ++ .../res/drawable/ic_baseline_clock_24.xml | 9 ++++++ app/src/main/res/layout/fragment_home.xml | 6 ++++ app/src/main/res/layout/fragment_home_tv.xml | 17 +++++++++++ .../main/res/layout/player_custom_layout.xml | 9 ++++++ .../res/layout/player_custom_layout_tv.xml | 16 +++++++++++ .../main/res/layout/trailer_custom_layout.xml | 6 ++++ .../res/values/donottranslate-strings.xml | 1 + app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/settings_ui.xml | 6 ++++ 13 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_baseline_clock_24.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 604695859..0e52baff6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -701,6 +701,23 @@ class HomeFragment : BaseFragment( homeViewModel.queryTextSubmit("") } + // Load value for toggling Tv layout real time clock. Hide by default at startup + // set visibility first, to apply a scroll effect later + context?.let { + if (isLayout(TV)) { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(it) + val toggleClock = + settingsManager.getBoolean( + getString(R.string.tv_layout_clock_key), + false + ) + binding.homeClock.isVisible = toggleClock + } else { + binding.homeClock.isVisible = false + } + } + + homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (isLayout(PHONE)) { @@ -740,6 +757,17 @@ class HomeFragment : BaseFragment( view.getLocationInWindow(rect) scrollParent.isVisible = true scrollParent.translationY = rect[1].toFloat() - 60.toPx + + // Move the TV layout real time clock out of the way too + // We check if we have the correct layout and if the clock is enabled + if(isLayout(TV) && binding.homeClock.isVisible) { + val scrollParent = binding.homeClock + + val rect = IntArray(2) + view.getLocationInWindow(rect) + scrollParent.isVisible = true + scrollParent.translationY = rect[1].toFloat() - 60.toPx + } } } super.onScrolled(recyclerView, dx, dy) 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 20c37dcf4..d90b6043f 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 @@ -307,6 +307,7 @@ open class FullScreenPlayer : AbstractPlayerFragment( playerVideoTitleRez, playerVideoInfo, playerGoBackHolder, + playerVideoClock, ).forEach { it.animateY(titleMove) } @@ -772,7 +773,7 @@ open class FullScreenPlayer : AbstractPlayerFragment( val showPlayerEpisodes = !isGone && isThereEpisodes() playerEpisodesButtonRoot.isVisible = showPlayerEpisodes playerEpisodesButton.isVisible = showPlayerEpisodes - playerVideoTitleHolder.isGone = togglePlayerTitleGone + playerVideoTitleHolder.isGone = togglePlayerTitleGone || playerVideoTitle.text.isBlank() playerVideoTitleRez.isGone = isGone || playerVideoTitleRez.text.isBlank() playerEpisodeFiller.isGone = isGone playerCenterMenu.isGone = isGone diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 683a322c4..4495a5602 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -2271,6 +2271,14 @@ class GeneratorPlayer : FullScreenPlayer() { fromTagToEnglishLanguageName(it)?.lowercase() ?: return@mapNotNull null } ?: listOf() } + + // Set up TV clock visibility + if (isLayout(TV)) { + val showTvClock = settingsManager.getBoolean(ctx.getString(R.string.tv_layout_clock_key), false) + playerBinding?.playerVideoClock?.isVisible = showTvClock + } else { + playerBinding?.playerVideoClock?.isVisible = false + } } unwrapBundle(savedInstanceState) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index f4c522bf9..5f86a3a5a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -228,6 +228,8 @@ class SettingsUI : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } + getPref(R.string.tv_layout_clock_key)?.hideOn(PHONE or EMULATOR) + getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener { val prefNames = resources.getStringArray(R.array.confirm_exit) val prefValues = resources.getIntArray(R.array.confirm_exit_values) diff --git a/app/src/main/res/drawable/ic_baseline_clock_24.xml b/app/src/main/res/drawable/ic_baseline_clock_24.xml new file mode 100644 index 000000000..3436c6208 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_clock_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 99a764dee..8d78c01b3 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -227,6 +227,12 @@ tools:listitem="@layout/homepage_parent" tools:visibility="gone" /> + + + + + + + diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 88e4cd984..c6445d80f 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -352,6 +352,22 @@ android:layout_marginEnd="32dp" android:orientation="vertical"> + + + + opensubtitles_key subdl_key animeskip_key + tv_layout_clock_key pref_category_security_key pref_category_gestures_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a918231f..eed41ce81 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -374,6 +374,8 @@ Looks Features General + Show real time clock + Show a real time clock at the top of the screen. Applies to the homepage and player Random Button Show random button on Homepage and Library Extension languages diff --git a/app/src/main/res/xml/settings_ui.xml b/app/src/main/res/xml/settings_ui.xml index 1b516ffa3..774724f99 100644 --- a/app/src/main/res/xml/settings_ui.xml +++ b/app/src/main/res/xml/settings_ui.xml @@ -89,6 +89,12 @@ android:icon="@drawable/metadata_overlay_icon" android:key="@string/show_player_metadata_key" android:title="@string/show_player_metadata_overlay" /> + Date: Thu, 6 Aug 2026 00:52:36 +0200 Subject: [PATCH 092/105] feat(extractors): add vidara mirrors and fix links with filecodes (#3104) [skip ci] --- .../cloudstream3/extractors/Vidara.kt | 45 ++++++++++++++++++- .../cloudstream3/utils/ExtractorApi.kt | 16 +++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt index 04c60675e..e3a33b0cb 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.newSubtitleFile @@ -43,6 +44,46 @@ class VidaraSo : Vidara() { override val mainUrl = "https://vidara.so" } +@Prerelease +class Odysseusa : Vidara() { + override val mainUrl = "https://odysseusa.cc" +} + +@Prerelease +class Handfacesnap : Vidara() { + override val mainUrl = "https://handfacesnap.cc" +} + +@Prerelease +class Namefacesnap : Vidara() { + override val mainUrl = "https://namefacesnap.cc" +} + +@Prerelease +class Thebesthostertv : Vidara() { + override val mainUrl = "https://thebesthosterv.com" +} + +@Prerelease +class Vidmatrixa : Vidara() { + override val mainUrl = "https://vidmatrixa.com" +} + +@Prerelease +class VidChampions : Vidara() { + override val mainUrl = "https://vidchampions.com/" +} + +@Prerelease +class Antarcticadocs : Vidara() { + override val mainUrl = "https://antarcticadocs.com" +} + +@Prerelease +class Nameitweb : Vidara() { + override val mainUrl = "https://nameitweb.com" +} + open class Vidara : ExtractorApi() { override val name = "Vidara" override val mainUrl = "https://vidara.to" @@ -54,7 +95,9 @@ open class Vidara : ExtractorApi() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { - val fileCode = url.substringAfterLast("/") + // URLs sometimes contain a mirror selector query parameter, e.g. "https://vidara.to/xkNOuHSrCNMj?24" + // that is not part of the file code and hence we must remove it + val fileCode = url.substringAfterLast("/").substringBefore("?") val fileInfo = app.post( "$mainUrl/api/stream", json = mapOf( "filecode" to fileCode, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 163059a97..2c3fff571 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.extractors.Acefile import com.lagradost.cloudstream3.extractors.Ahvsh import com.lagradost.cloudstream3.extractors.Aico +import com.lagradost.cloudstream3.extractors.Antarcticadocs import com.lagradost.cloudstream3.extractors.Asnwish import com.lagradost.cloudstream3.extractors.Auvexiug import com.lagradost.cloudstream3.extractors.Awish @@ -109,6 +110,7 @@ import com.lagradost.cloudstream3.extractors.HDMomPlayer import com.lagradost.cloudstream3.extractors.HDPlayerSystem import com.lagradost.cloudstream3.extractors.HDStreamAble import com.lagradost.cloudstream3.extractors.Habetar +import com.lagradost.cloudstream3.extractors.Handfacesnap import com.lagradost.cloudstream3.extractors.Haxloppd import com.lagradost.cloudstream3.extractors.Hgcloudto import com.lagradost.cloudstream3.extractors.HglinkTo @@ -158,6 +160,8 @@ import com.lagradost.cloudstream3.extractors.Multimovies import com.lagradost.cloudstream3.extractors.Mvidoo import com.lagradost.cloudstream3.extractors.MyVidPlay import com.lagradost.cloudstream3.extractors.Mwish +import com.lagradost.cloudstream3.extractors.Namefacesnap +import com.lagradost.cloudstream3.extractors.Nameitweb import com.lagradost.cloudstream3.extractors.NathanFromSubject import com.lagradost.cloudstream3.extractors.Nekostream import com.lagradost.cloudstream3.extractors.Nekowish @@ -165,6 +169,7 @@ import com.lagradost.cloudstream3.extractors.Neonime7n import com.lagradost.cloudstream3.extractors.Neonime8n import com.lagradost.cloudstream3.extractors.Obeywish import com.lagradost.cloudstream3.extractors.Odnoklassniki +import com.lagradost.cloudstream3.extractors.Odysseusa import com.lagradost.cloudstream3.extractors.OkRuHTTP import com.lagradost.cloudstream3.extractors.OkRuHTTPMobile import com.lagradost.cloudstream3.extractors.OkRuSSL @@ -269,6 +274,8 @@ import com.lagradost.cloudstream3.extractors.VidHidePro6 import com.lagradost.cloudstream3.extractors.VidHideHub import com.lagradost.cloudstream3.extractors.Ryderjet import com.lagradost.cloudstream3.extractors.Streamcash +import com.lagradost.cloudstream3.extractors.Thebesthostertv +import com.lagradost.cloudstream3.extractors.VidChampions import com.lagradost.cloudstream3.extractors.VidMoxy import com.lagradost.cloudstream3.extractors.VidStack import com.lagradost.cloudstream3.extractors.VideoSeyred @@ -295,6 +302,7 @@ import com.lagradost.cloudstream3.extractors.Vidaraw import com.lagradost.cloudstream3.extractors.Vidarax import com.lagradost.cloudstream3.extractors.Vidavaca import com.lagradost.cloudstream3.extractors.Vide0Net +import com.lagradost.cloudstream3.extractors.Vidmatrixa import com.lagradost.cloudstream3.extractors.Vids import com.lagradost.cloudstream3.extractors.Vidsonic import com.lagradost.cloudstream3.extractors.VkExtractor @@ -1181,6 +1189,14 @@ val extractorApis: AtomicMutableList = atomicListOf( Vidaratem(), VidaaraxCom(), VidaaraxNet(), + Odysseusa(), + Handfacesnap(), + Namefacesnap(), + Thebesthostertv(), + Vidmatrixa(), + VidChampions(), + Antarcticadocs(), + Nameitweb(), GamoVideo(), Gdriveplayerapi(), From 9d070417ed3d807d0482a3c63dd173bfad4a6d9a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 6 Aug 2026 00:53:20 +0200 Subject: [PATCH 093/105] Translated using Weblate (Albanian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (4 of 4 strings) Translated using Weblate (Albanian) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Tamil) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Italian) Currently translated at 100.0% (735 of 735 strings) Update translation files Updated by "Remove blank strings" hook in Weblate. Translated using Weblate (Spanish) Currently translated at 99.7% (733 of 735 strings) Translated using Weblate (Vietnamese) Currently translated at 99.8% (734 of 735 strings) Translated using Weblate (Swedish) Currently translated at 99.8% (734 of 735 strings) Translated using Weblate (Polish) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Arabic) Currently translated at 99.7% (733 of 735 strings) Translated using Weblate (English) Currently translated at 99.8% (734 of 735 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Polish) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Czech) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Italian) Currently translated at 100.0% (733 of 733 strings) Translated using Weblate (Czech) Currently translated at 100.0% (733 of 733 strings) Co-authored-by: 007 Co-authored-by: Aziz Hadidi Co-authored-by: Dan Co-authored-by: Fjuro Co-authored-by: Hosted Weblate Co-authored-by: Hosted Weblate user 54392 Co-authored-by: Man Co-authored-by: Massimo Pissarello Co-authored-by: Matthaiks Co-authored-by: Oğuz Ersen Co-authored-by: win7guru Co-authored-by: தமிழ்நேரம் Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ar/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/en/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/it/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sq/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ta/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/tr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/sq/ Translation: Cloudstream/App Translation: Cloudstream/Fastlane --- app/src/main/res/values-b+ar/strings.xml | 5 ++++ app/src/main/res/values-b+cs/strings.xml | 11 +++++++++ app/src/main/res/values-b+es/strings.xml | 5 ++++ app/src/main/res/values-b+it/strings.xml | 24 +++++++++++++------ app/src/main/res/values-b+pl/strings.xml | 4 +++- app/src/main/res/values-b+sq/strings.xml | 8 ++++--- app/src/main/res/values-b+sv/strings.xml | 9 +++++++ app/src/main/res/values-b+ta/strings.xml | 17 +++++++++++-- app/src/main/res/values-b+tr/strings.xml | 11 ++++++++- app/src/main/res/values-b+uk/strings.xml | 13 +++++++++- app/src/main/res/values-b+vi/strings.xml | 8 +++++++ app/src/main/res/values-b+zh/strings.xml | 10 +++++++- app/src/main/res/values/strings.xml | 3 +-- .../metadata/android/sq/full_description.txt | 2 +- 14 files changed, 111 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index f543136dc..65fcc149d 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -756,4 +756,9 @@ مقطع استعراض البث قائم + أظهر ساعة الوقت الحقيقي + أظهر شاشة الوقت الحقيقي في قمة الشاشة. ينطبق على الصفحة الرئيسية و المشغل + اعدادات الملف الشخصي + أخفي المصادر صاحبة الأولوية السلبية + أخفي المصادر التي تحتوي أخطاء diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index 983a03db7..e03da1d6f 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -754,4 +754,15 @@ Video Náhled Živě + Nastavení profilu + Skrýt zdroje s negativní prioritou + Skrýt zdroje s chybami + + %d skrytý odkaz + %d skryté odkazy + %d skrytých odkazů + %d skrytých odkazů + + Zobrazit hodiny s reálným časem + Zobrazit hodiny s reálným časem v horní části obrazovky. Platí pro domovskou obrazovku a přehrávač diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 167de546d..1287c7c09 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -737,4 +737,9 @@ Vídeo Vista previa En Vivo + Mostrar reloj en tiempo real + Muestra un reloj en tiempo real en la parte superior de la pantalla. Se aplica a la página de inicio y al reproductor + Configuración del perfil + Ocultar fuentes con prioridad negativa + Ocultar fuentes con errores diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index c47a84842..2672f39b1 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -225,7 +225,7 @@ Livestream NSFW Altro - Errore sorgente + Errore fonte Errore remoto Errore del renderer Errore imprevisto del lettore @@ -247,7 +247,7 @@ Controlla aggiornamenti Blocca Ridimensiona - Sorgente + Fonte Salta OP Non mostrare di nuovo Salta questo aggiornamento @@ -346,7 +346,7 @@ Protagonista Supporto Secondario - Sorgente + Fonte Casuale Prossimamente… Cam @@ -670,7 +670,7 @@ Data di messa in onda (più vecchia) Valutazione %s Avvio del processo di aggiornamento plugin! - %d plugin aggiornati con successo! + %d plugin aggiornati correttamente Nessun plugin è stato aggiornato. Notifiche lettore La notifica del lettore per controllare la riproduzione in background @@ -733,7 +733,7 @@ Cancella suggerimenti Mostra pannello cast Info sui media - Nome sorgente + Nome fonte Luminosità extra Attiva il filtro di luminosità quando viene superato il 100% della luminosità dello schermo extra_brightness_enabled @@ -753,10 +753,20 @@ %d download in coda %d download in coda - Priorità sorgente - Decidi come le sorgenti video devono essere ordinate nel lettore + Priorità fonti + Decidi come le fonti video devono essere ordinate nel lettore Mostra sovrapposizione metadati lettore Video Anteprima Live + Impostazioni profilo + Nascondi fonti con priorità negativa + Nascondi fonti con errori + + %d collegamento nascosto + %d collegamenti nascosti + %d collegamenti nascosti + + Mostra orologio in tempo reale + Mostra un orologio in tempo reale nella parte superiore dello schermo. Si applica alla homepage e al lettore diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index b69146c92..418a4a2ca 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -649,7 +649,7 @@ Odcinek (malejąco) Ocena (najwyższa) Zaktualizuj wtyczki - Pomyślnie zaktualizowano wtyczki: %d! + Pomyślnie zaktualizowano wtyczki: %d Nie zaktualizowano żadnych wtyczek. Zaktualizuj wtyczki ręcznie Rozpoczęcie procesu aktualizacji wtyczek! @@ -751,4 +751,6 @@ %d ukrytych odnośników %d ukrytych odnośników + Pokaż zegar czasu rzeczywistego + Wyświetl zegar czasu rzeczywistego u góry ekranu. Dotyczy strony głównej i odtwarzacza diff --git a/app/src/main/res/values-b+sq/strings.xml b/app/src/main/res/values-b+sq/strings.xml index c33ea0447..828066ead 100644 --- a/app/src/main/res/values-b+sq/strings.xml +++ b/app/src/main/res/values-b+sq/strings.xml @@ -117,7 +117,7 @@ Madhësia e shkrimit Kërko duke përdorur ofruesit Kërko duke përdorur tipet - %d Banane të dhëna per developer-at + %d Banane të dhëna për developer-at Asnjë Banane e dhënë Zgjedhje automatike e gjuhës Shkarko gjuhët @@ -216,7 +216,7 @@ %1$s %2$d%3$s Asnje sezon Episodi - Episodet + Episode %1$d-%2$d %1$d %2$s Vazhdon në %s @@ -679,7 +679,7 @@ Përditëso shtesat Përditëso shtesat manualisht Filloi procesi i përditësimit të shtesave! - %d Shtes(at) u përditësuan me sukses! + %d Shtes(a) u përditësuan me sukses Nuk u përditësua asnjë shtesë. Njoftimet e luajtësit Njoftimi për kontrollin e luajtjes nga sfondi @@ -742,4 +742,6 @@ %d link i fshehur %d linqe të fshehura + Shfaq orën në kohë reale + Shfaq një orë në krye të ekranit. Aplikohet në faqen kryesore dhe në luajtës diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 52d4e1d05..0740e0b8d 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -735,4 +735,13 @@ Video Förhandsvisning Live + Visa realtidsklocka + Visa en realtidsklocka högst upp på skärmen. Gäller startsidan och spelaren + Profilinställningar + Dölj källor med negativ prioritet + Dölj källor med fel + + %d dold länk + %d dolda länkar + diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 4cde39d8f..2537eba0c 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -609,7 +609,7 @@ பயன்பாட்டிலிருந்து வெளியேறுவதற்கு முன் உரையாடலைக் காட்டு உள்நாட்டில் அங்கீகரிக்கவும் தள்ளுபடி - உங்கள் ச்மார்ட்போன் அல்லது கணினியில் %s ஐப் பார்வையிட்டு மேலே உள்ள குறியீட்டை உள்ளிடவும் + உங்கள் ச்மார்ட்போன் அல்லது கணினியில் %s ஐப் பார்வையிட்டு மேலே உள்ள குறியீட்டை உள்ளிடவும் சாதன முள் குறியீட்டைப் பெற முடியாது, உள்ளக அங்கீகாரத்தை முயற்சிக்கவும் முள் குறியீடு இப்போது காலாவதியானது! விளிம்பு அளவு @@ -652,7 +652,7 @@ செருகுநிரல்களைப் புதுப்பிக்கவும் செருகுநிரல்களை கைமுறையாக புதுப்பிக்கவும் சொருகி புதுப்பிப்பு செயல்முறையைத் தொடங்குகிறது! - வெற்றிகரமாக புதுப்பிக்கப்பட்டது %d சொருகி (கள்)! + %d செருகுநிரல்(கள்) வெற்றிகரமாக புதுப்பிக்கப்பட்டது செருகுநிரல்கள் எதுவும் புதுப்பிக்கப்படவில்லை. பிளேயர் அறிவிப்புகள் பின்னணியில் இருந்து பின்னணியைக் கட்டுப்படுத்துவதற்கான பிளேயர் அறிவிப்பு @@ -731,4 +731,17 @@ %d பதிவிறக்கம் வரிசையில் உள்ளது %d பதிவிறக்கங்கள் வரிசையில் உள்ளன + பிளேயர் மேனிலை தரவு மேலடுக்கைக் காட்டு + ஒளிதோற்றம் + உண்மையான நேர கடிகாரத்தைக் காட்டு + திரையின் மேற்புறத்தில் நிகழ் நேர கடிகாரத்தைக் காட்டு. முகப்புப்பக்கம் மற்றும் பிளேயருக்குப் பொருந்தும் + முன்னோட்டம் + சுயவிவர அமைப்புகள் + எதிர்மறையான முன்னுரிமையுடன் ஆதாரங்களை மறை + பிழைகளுடன் ஆதாரங்களை மறைக்கவும் + + %d மறைக்கப்பட்ட இணைப்பு + %d மறைக்கப்பட்ட இணைப்புகள் + + வாழ்க diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index d711505a8..e4a2dbc37 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -661,7 +661,7 @@ Tarih %s Eklentileri Güncelle Eklenti güncellemesi başlıyor! - %d eklenti başarıyla güncellendi! + %d eklenti başarıyla güncellendi Hiçbir eklenti güncellenmedi. Eklentileri el ile güncelle Oynatıcı bildirimi arka planda oynatmasını kontrol etmek içindir @@ -749,4 +749,13 @@ Canlı Ön Gösterim Video + Gerçek zamanlı saati göster + Ekranın üst kısmında gerçek zamanlı bir saat göster. Ana sayfa ve oynatıcı için uygulanır + Profil ayarları + Eksi önceliğe sahip kaynakları gizle + Hatalı kaynakları gizle + + %d gizli bağlantı + %d gizli bağlantı + diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index ba46736ac..56e5a916e 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -646,7 +646,7 @@ Еп. %s Дата %s Оновити розширення - Успішно оновлено %d розширення(-ь)! + Успішно оновлено %d розширення(-ь) Оновити розширення вручну Починається оновлення розширень! Не оновлено жодного розширення. @@ -739,4 +739,15 @@ Відео Передперегляд Наживо + Показувати поточний час + Показувати поточний час у верхній частині екрана (на головній та у програвачі) + Налаштування профілю + Приховати джерела з від’ємним пріоритетом + Приховати джерела з помилками + + %d приховане посилання + %d приховані посилання + %d прихованих посилань + %d прихованих посилань + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 53679c05c..3e2a93fb4 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -748,4 +748,12 @@ Trực tiếp Video Xem trước + Hiển thị đồng hồ thời gian thực + Hiển thị đồng hồ thời gian thực ở phía trên màn hình. Áp dụng cho trang chủ và trình phát + Cài đặt hồ sơ + Ẩn nguồn có độ ưu tiên âm + Ẩn nguồn có lỗi + + %d liên kết bị ẩn + diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 56ec8f43e..dc7eab451 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -655,7 +655,7 @@ 得分 %s 日期 %s 更新插件 - 成功更新了 %d 个插件! + 成功更新了 %d 个插件 没有插件被更新。 手动更新插件 集数(升序) @@ -747,4 +747,12 @@ 视频 预览 播放中 + 显示实时时钟 + 在屏幕顶部显示实时时钟。应用于主页和播放器 + 配置文件设置 + 隐藏负优先级的来源 + 隐藏有错误的来源 + + %d 条隐藏链接 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eed41ce81..7d530b59f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -631,7 +631,6 @@ %d hidden link %d hidden links - Wi-Fi Mobile data Set default @@ -734,7 +733,7 @@ Update plugins manually Starting plugin update process! - Successfully updated %d plugin(s)! + Successfully updated %d plugin(s) No plugins were updated. Player notifications The player notification for controlling the playback from the background diff --git a/fastlane/metadata/android/sq/full_description.txt b/fastlane/metadata/android/sq/full_description.txt index f9d6e6f9e..a7881e62e 100644 --- a/fastlane/metadata/android/sq/full_description.txt +++ b/fastlane/metadata/android/sq/full_description.txt @@ -1,7 +1,7 @@ CloudStream-3 ju lejon të shikoni dhe shkarkoni filma, seriale televizive dhe anime. Aplikacioni vjen pa asnjë reklamë dhe analitikë -dhe suporton faqe të shumta trailerash dhe filmash dhe më shumë, p.sh. +dhe suporton faqe të shumta trailer-ash dhe filmash, dhe më shumë, p.sh. Të ruajturat From 2207215a829030afff96268b840cb38dadac2e8e Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 6 Aug 2026 00:53:51 +0200 Subject: [PATCH 094/105] feat(extractors): add playmate extractor (#3103) --- .../cloudstream3/extractors/Playmate.kt | 43 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 2 + 2 files changed, 45 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt new file mode 100644 index 000000000..e319d4c17 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt @@ -0,0 +1,43 @@ +package com.lagradost.cloudstream3.extractors + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Prerelease +class Playmate : ExtractorApi() { + override val name: String = "Playmate" + override val mainUrl: String = "https://playmate.to" + override val requiresReferer: Boolean = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val id = url.substringAfterLast("/") + + val resp = app.post( + "$mainUrl/api/s", + json = mapOf("c" to id, "d" to "web"), + headers = mapOf("User-Agent" to "Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0") + ).parsed() + + callback.invoke(newExtractorLink(source = name, name = name, url = resp.sx, type = ExtractorLinkType.M3U8)) + } + + @Serializable + private data class StreamInfo( + @SerialName("sx") + @JsonProperty("sx") + val sx: String, + ) +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 2c3fff571..e09ebbc8b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -183,6 +183,7 @@ import com.lagradost.cloudstream3.extractors.PlayLtXyz import com.lagradost.cloudstream3.extractors.PlayRu import com.lagradost.cloudstream3.extractors.PlayerVoxzer import com.lagradost.cloudstream3.extractors.Playerwish +import com.lagradost.cloudstream3.extractors.Playmate import com.lagradost.cloudstream3.extractors.Playmogo import com.lagradost.cloudstream3.extractors.Rabbitstream import com.lagradost.cloudstream3.extractors.RapidVid @@ -1336,6 +1337,7 @@ val extractorApis: AtomicMutableList = atomicListOf( Flyfile(), Firestream(), Vids(), + Playmate() ) From 0361a6f1dd4587d0b71969295daf58f2d7c61fa7 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 23 Aug 2026 15:21:29 +0200 Subject: [PATCH 095/105] feat(extractors): add hubu.cloud extractor (#3131) --- .../cloudstream3/extractors/HubuCloud.kt | 27 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt new file mode 100644 index 000000000..85151b1d7 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt @@ -0,0 +1,27 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.Prerelease +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.newExtractorLink + +@Prerelease +open class HubuCloud: ExtractorApi() { + override val name: String = "Hubu" + override val mainUrl: String = "https://hubu.cloud" + override val requiresReferer: Boolean = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val doc = app.get(url).document + + val streamUrl = doc.select("source").attr("src") + callback.invoke(newExtractorLink(source = name, name = name, url = streamUrl)) + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index e09ebbc8b..245339f00 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -119,6 +119,7 @@ import com.lagradost.cloudstream3.extractors.Hotlinger import com.lagradost.cloudstream3.extractors.HubCloud import com.lagradost.cloudstream3.extractors.Hxfile import com.lagradost.cloudstream3.extractors.HlsWish +import com.lagradost.cloudstream3.extractors.HubuCloud import com.lagradost.cloudstream3.extractors.InternetArchive import com.lagradost.cloudstream3.extractors.JWPlayer import com.lagradost.cloudstream3.extractors.Jeniusplay @@ -1148,6 +1149,7 @@ val extractorApis: AtomicMutableList = atomicListOf( Vicloud(), Uservideo(), Userscloud(), + HubuCloud(), Movhide(), StreamhideCom(), From a4824a2950f56e4c33105c677d29f7a9a767d59f Mon Sep 17 00:00:00 2001 From: RjBiermann <198417406+RjBiermann@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:23:59 -0400 Subject: [PATCH 096/105] feat(tv): add plugin settings button to home top bar (#3122) --- .../cloudstream3/ui/home/HomeFragment.kt | 23 +++++++++++++++++++ app/src/main/res/layout/fragment_home.xml | 7 ++++++ app/src/main/res/layout/fragment_home_tv.xml | 21 +++++++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 0e52baff6..f382e01d3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -30,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.chip.Chip import com.lagradost.api.Log +import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.CommonActivity.showToast @@ -701,6 +702,21 @@ class HomeFragment : BaseFragment( homeViewModel.queryTextSubmit("") } + homePreviewSettingsButton.setOnClickListener { view -> + val apiName = homeViewModel.apiName.value + val plugin = APIHolder.getApiFromNameNull(apiName) + ?.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin + val openSettings = plugin?.openSettings + if (openSettings != null) { + try { + val activityContext = view.context.getActivity() ?: view.context + openSettings.invoke(activityContext) + } catch (e: Throwable) { + logError(e) + } + } + } + // Load value for toggling Tv layout real time clock. Hide by default at startup // set visibility first, to apply a scroll effect later context?.let { @@ -800,6 +816,12 @@ class HomeFragment : BaseFragment( observe(homeViewModel.page) { data -> binding.apply { + if (isLayout(TV or EMULATOR)) { + val plugin = APIHolder.getApiFromNameNull(homeViewModel.apiName.value) + ?.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin + homePreviewSettingsButton.isGone = plugin?.openSettings == null + } + when (data) { is Resource.Success -> { val d = data.value @@ -815,6 +837,7 @@ class HomeFragment : BaseFragment( homeLoadingError.isVisible = false homeMasterRecycler.isVisible = true homeLoadingShimmer.stopShimmer() + //home_loaded?.isVisible = true if (toggleRandomButton) { val distinct = d.values diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 8d78c01b3..dc1635392 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -260,4 +260,11 @@ android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_tv.xml b/app/src/main/res/layout/fragment_home_tv.xml index d50c4c2e4..f8559ab14 100644 --- a/app/src/main/res/layout/fragment_home_tv.xml +++ b/app/src/main/res/layout/fragment_home_tv.xml @@ -200,11 +200,28 @@ android:gravity="center" android:nextFocusLeft="@id/navigation_home" tools:text="Hello!" - android:nextFocusRight="@id/home_preview_reload_provider" + android:nextFocusRight="@id/home_preview_settings_button" android:nextFocusDown="@id/home_preview_info_btt" > + + Date: Wed, 26 Aug 2026 22:28:55 +0300 Subject: [PATCH 097/105] Fix: Missing translations in extension menu (#3138) --- .../cloudstream3/ui/home/HomeFragment.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index f382e01d3..d0b4b0f20 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -110,6 +110,14 @@ class HomeFragment : BaseFragment( val errorProfilePic = errorProfilePics.random() + fun Context.getDisplayName(apiName: String?): String? { + return when (apiName) { + noneApi.name -> getString(R.string.none) + randomApi.name -> getString(R.string.home_random) + else -> apiName + } + } + //fun Activity.loadHomepageList( // item: HomePageList, // deleteCallback: (() -> Unit)? = null, @@ -503,8 +511,10 @@ class HomeFragment : BaseFragment( addAll(remainingApis) } - val names = - currentValidApis.map { if (isMultiLang) "${getFlagFromIso(it.lang)?.plus(" ") ?: ""}${it.name}" else it.name } + val names = currentValidApis.map { + val displayName = getDisplayName(it.name) + if (isMultiLang) "${getFlagFromIso(it.lang)?.plus(" ") ?: ""}$displayName" else displayName + } val index = currentValidApis.map { it.name }.indexOf(currentApiName) listView?.setItemChecked(index, true) arrayAdapter.addAll(names) @@ -806,9 +816,10 @@ class HomeFragment : BaseFragment( observe(homeViewModel.apiName) { apiName -> currentApiName = apiName + val displayApiName = context?.getDisplayName(apiName) ?: apiName binding.apply { - homeApiFab.text = apiName - homeChangeApi.text = apiName + homeApiFab.text = displayApiName + homeChangeApi.text = displayApiName homePreviewReloadProvider.isGone = (apiName == noneApi.name) homePreviewSearchButton.isGone = (apiName == noneApi.name) } From adcbae055e9e14d8538526dbd68c1e0cd0926485 Mon Sep 17 00:00:00 2001 From: ByAyzen <189399597+ByAyzen@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:40:24 +0300 Subject: [PATCH 098/105] Fix: Upcoming post alignment issue (#3136) --- app/src/main/res/layout/fragment_result.xml | 3 ++- app/src/main/res/layout/fragment_result_tv.xml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index a5c933d6a..9db120ad0 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -553,7 +553,8 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" - android:paddingTop="50dp" + android:paddingTop="20dp" + android:paddingBottom="20dp" android:text="@string/coming_soon" android:textColor="?attr/textColor" android:textSize="20sp" diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 3dff8f0d5..db07250c3 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -654,7 +654,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" - android:paddingTop="50dp" + android:paddingTop="20dp" + android:paddingBottom="20dp" android:text="@string/coming_soon" android:textColor="?attr/textColor" android:textSize="20sp" @@ -1036,7 +1037,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" - android:paddingTop="50dp" + android:paddingTop="20dp" + android:paddingBottom="20dp" android:text="@string/coming_soon" android:textColor="?attr/textColor" android:textSize="20sp" From e3ddcd741dbf178d15a9e6a6ddf24d715daf7469 Mon Sep 17 00:00:00 2001 From: ByAyzen <189399597+ByAyzen@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:49:40 +0300 Subject: [PATCH 099/105] Fix: Cast text in emulator layout (#3134) --- .../ui/result/ResultFragmentTv.kt | 2 +- .../main/res/layout/fragment_result_tv.xml | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index ce67763d0..c316fae17 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -874,7 +874,6 @@ class ResultFragmentTv : BaseFragment( resultMetaRating.setText(d.ratingText) resultMetaStatus.setText(d.onGoingText) resultMetaContentRating.setText(d.contentRatingText) - resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) resultPoster.loadImage(d.posterImage, headers = d.posterHeaders) @@ -932,6 +931,7 @@ class ResultFragmentTv : BaseFragment( true ) + resultCastText.setText(if (showCast) d.actorsText else null) resultCastItems.isGone = !showCast || d.actors.isNullOrEmpty() (resultCastItems.adapter as? ActorAdaptor)?.submitList(if (showCast) d.actors else emptyList()) diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index db07250c3..b06886a97 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -631,6 +631,18 @@ https://developer.android.com/design/ui/tv/samples/jet-fit + + - - Date: Wed, 26 Aug 2026 22:41:22 +0200 Subject: [PATCH 100/105] Translated using Weblate (Albanian) Currently translated at 100.0% (735 of 735 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Japanese) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Somali) Currently translated at 66.8% (491 of 735 strings) Translated using Weblate (German) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Russian) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Somali) Currently translated at 62.8% (462 of 735 strings) Translated using Weblate (Bengali) Currently translated at 54.1% (398 of 735 strings) Translated using Weblate (French) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (French) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Indonesian) Currently translated at 99.8% (734 of 735 strings) Translated using Weblate (Russian) Currently translated at 99.8% (734 of 735 strings) Translated using Weblate (Czech) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (French) Currently translated at 100.0% (735 of 735 strings) Translated using Weblate (French) Currently translated at 100.0% (735 of 735 strings) Co-authored-by: 007 Co-authored-by: Aesthetic Fighter Co-authored-by: Aishik Mukherjee Co-authored-by: Dmitry Warkentin Co-authored-by: Fjuro Co-authored-by: Gabriel Co-authored-by: Hosted Weblate Co-authored-by: Man Co-authored-by: Noordin Mohamed Co-authored-by: Posemartonis Co-authored-by: Takeru Mikenu Co-authored-by: ardev Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/bn/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/de/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/id/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ja/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ru/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/so/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sq/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translation: Cloudstream/App --- app/src/main/res/values-b+bn/strings.xml | 14 +++++ app/src/main/res/values-b+cs/strings.xml | 2 +- app/src/main/res/values-b+de/strings.xml | 11 +++- app/src/main/res/values-b+es/strings.xml | 7 ++- app/src/main/res/values-b+fr/strings.xml | 14 ++++- app/src/main/res/values-b+in/strings.xml | 8 +++ app/src/main/res/values-b+ja/strings.xml | 12 +++- app/src/main/res/values-b+pt+BR/strings.xml | 12 +++- app/src/main/res/values-b+ru/strings.xml | 11 ++++ app/src/main/res/values-b+so/strings.xml | 65 +++++++++++++++++---- app/src/main/res/values-b+sq/strings.xml | 2 +- app/src/main/res/values-b+sv/strings.xml | 2 +- app/src/main/res/values-b+vi/strings.xml | 2 +- 13 files changed, 139 insertions(+), 23 deletions(-) diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index b6edbd1cd..da3f819b6 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -387,4 +387,18 @@ পডকাস্ট ভিডিও এনকোডিং ত্রুটি + অসমর্থিত ত্রুটি + কাস্ট মিরর + প্লে মিরর" + রেটিং লেবেল + পর্বের বিবরণ + প্লেয়ারে প্রদর্শিত - স্কিপের পরিমাণ + প্লেয়ার দৃশ্যমান থাকলে যে সময় পরিমাণ স্কিপ করা হয় + GitHub-এর সাথে সংযোগ করা যায়নি। jsDelivr proxy চালু করা হচ্ছে। + jsDelivr ব্যবহার করে GitHub-এর সরাসরি লিংক ব্লক করা এড়িয়ে যান। এতে আপডেট কয়েকদিন বিলম্বিত হতে পারে। + নিরাপত্তা + অ্যাকাউন্টসমূহ + লাইভ ঘড়ি দেখান + পর্দার উপরের অংশে লাইভ ঘড়ি দেখায়। হোমপেজ ও প্লেয়ারে প্রযোজ্য + %1$s %2$s diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index e03da1d6f..c713b17fd 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -660,7 +660,7 @@ Spouštím proces aktualizace doplňků! Nebyly aktualizovány žádné doplňky. Ep %s - Úspěšně aktualizováno %d doplňků! + Úspěšně aktualizováno %d doplňků Epizoda (sestupně) Hodnocení (nejnižší) Ručně aktualizovat doplňky diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index e674fafd6..f00db9ab4 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -639,7 +639,7 @@ Softwaredekodierung ermöglicht es dem Player, Videodateien abzuspielen, die von deinem Gerät nicht unterstützt werden, könnte jedoch bei hoher Auflösung zu ruckelnder oder instabiler Wiedergabe führen. App neu starten und Stream Torrent Pop-Up akzeptieren, um fortzufahren. Plugins aktualisieren - %d Plugin(s) erfolgreich aktualisiert! + %d Plugin(s) erfolgreich aktualisiert Plugins manuell aktualisieren Podcast Nicht unterstützter Fehler @@ -735,4 +735,13 @@ Video Vorschau Live + Echtzeituhr anzeigen + Echtzeituhr oben auf dem Bildschirm anzeigen. Gilt für Startseite und Player + Profil-Einstellungen + Quellen mit negativer Priorität verbergen + Quellen mit Fehlern ausblenden + + %d ausgeblendeter Link + %d ausgeblendete Links + diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 1287c7c09..df6b181f1 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -647,7 +647,7 @@ Actualizar complementos Fecha de emisión (más antigua) Puntuar %s - ¡%d complemento(s) actualizados correctamente! + %d complemento(s) actualizados correctamente No se actualizó ningún complemento. Episodio (Ascendente) Reconocimiento de habla no disponible @@ -742,4 +742,9 @@ Configuración del perfil Ocultar fuentes con prioridad negativa Ocultar fuentes con errores + + %d enlace oculto + %d enlaces ocultos + %d enlaces ocultos + diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index bf401b20a..76f7240cf 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -641,7 +641,7 @@ Note %s Date %s Mettre à jour les plugins - %d plugin(s) mis à jour avec succès ! + %d plugin(s) mis à jour avec succès Aucun plugin n\'a été mis à jour. Note (Plus Haute) Mettre à jour les plugins manuellement @@ -737,5 +737,15 @@ Afficher les métadata de l\'overlay du lecteur vidéo Vidéo Prévisualisation - Direct + Live + Cacher les sources avec une priorité négative + Afficher l\'heure + Affiche l\'heure en haut de l\'écran. S\'applique à la page d\'Accueil et au lecteur + Paramètres du profil + Cacher les sources avec des erreurs + + %d lien caché + %d liens cachés + %d liens cachés + diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 7d6475f31..4597ec5ac 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -747,4 +747,12 @@ Live Video Pratinjau + Tampilkan jam waktu nyata + Tampilkan jam waktu nyata di bagian atas layar. Berlaku untuk halaman rumah dan pemain + Pengaturan profil + Sembunyikan sumber dengan prioritas negatif + Sembunyikan sumber dengan kesalahan + + %d link tersembunyi + diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index c5e6272b2..c7bdb4ef0 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -383,7 +383,7 @@ お気に入り 生体認証は、このデバイスではサポートされていません 指紋、顔ID、PIN、パターン、パスワードでアプリをロック解除します. - スマホやPCで%sにアクセスし、上記のコードを入力してください + スマホやパソコンで %s にアクセスし、上記のコードを入力してください PINコードが期限切れになりました! ライブラリが空です :(\nライブラリアカウントにログインするか、ローカルライブラリに表示を追加してください。 このリストは空です。 別のものに切り替えてみてください。 @@ -645,7 +645,7 @@ アプリを再起動し、ポップアップに同意して続行します。 ソフトウェアデコーディング ソフトウェアデコーディングにより、機器でサポートされていないビデオファイルを再生できますが、高解像度で遅延や不安定な再生を引き起こす可能性があります。 - %d 個のプラグインを正常に更新しました! + %d 個のプラグインを更新しました 更新されたプラグインはありません。 プレーヤー通知 バックグラウンドから再生を制御するためのプレーヤー通知 @@ -733,4 +733,12 @@ 動画 プレビュー ライブ + 現地時刻を表示 + 画面上部に現地時刻を表示します。ホーム画面とプレイヤーに適用されます + プロフィール設定 + 優先度がマイナスのソースを非表示 + エラーが発生したソースを非表示 + + %d件の非表示リンク + diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 455478b5a..680307ae9 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -662,7 +662,7 @@ Data %s Atualizar plugins Iniciando o processo de atualização do plugin! - Atualização bem-sucedida para %d plugin(s)! + Atualização bem-sucedida para %d plugin(s) Nenhum plugin foi atualizado. Atualizar plugins manualmente Notificações de reprodução @@ -752,4 +752,14 @@ Vídeo Visualização Ao vivo + Mostrar relógio em tempo real + Exiba um relógio em tempo real na parte superior da tela. Aplica-se à página inicial e ao player + Configurações de perfil + Ocultar fontes com prioridade negativa + Ocultar fontes com erros + + %d link oculto + %d link ocultos + %d link ocultos + diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index 38e576baa..7a174483f 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -739,4 +739,15 @@ Видео Предпросмотр Прямой эфир + Отображать текущее время + Отображать в верхней части экрана часы с текущим временем. Применяется к главной странице и плееру + Настройки профиля + Скрывать источники с отрицательным приоритетом + Скрывать источники с ошибками + + %d скрытая ссылка + %d скрытые ссылки + %d скрытых ссылок + %d скрытых ссылок + diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index 0eb787311..8a8f29095 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -65,7 +65,7 @@ Kaydka gudaha Trj Way bilaabmatay soo dejintu - Daalaco + Khadka ka daaro Waa la joojiyey dejintan Way bilaabantay cuaboonaysiintu Soo raridda lifaaqyadu way fashilantay @@ -91,7 +91,7 @@ Tirtir Nooca cidhifyada Isticmaal xaddiga iftiinka ee moobilkaaga halkii aad adigu habayn lahayd ugu horreynba - Gurmadka iyo cusbooneysiinta + Cusboonaysiinta iyo Kaydinta Waa la kaydiyet xogta gurmadka Raadinta waxay kuugu kala qaybinaysaa mid kasta iyo qaybiyihiisa Way fashilantay in xogta laga soo celiyo faylka gurmadka%s @@ -100,7 +100,7 @@ Xogta waa la kaydiyey Heerka Ogolaanshaha lagu kaydinayo ma hayno. Fadlan isku day mar kale. - Koontooyinka + Akoonada iyo Amniga Xog Si iskii ah usoo deji sidkanaha Qari tayada muqaalka aad dooratay natiijada waxa aad raadiso @@ -153,7 +153,7 @@ Tusi Boodhka Kitsu Si iskii ah u cusbooneysii sidkanaha Si iskii ah usoo deji dhamaan sidkanayaasha aan weli lasoo dejin ee ku jira kaydka aad keentay. - Marka ugu horreysa ee aad appka furto ayaad arkaysaa haddii wax cusub yihiin + Si toos ah u raadi update cusub markii aad furto appka. Light novel app, isla dhiseyaasha appkan baa leh Tus wixii appka ku cusub Dib uso bilow fadhiisinta appka @@ -238,13 +238,13 @@ Hareeraha madow ka saar markaad daawanayso Qoraal-hoosaadka Habaynta Qrl-hoosaadka Koromakaastiga - Habka Xawaare-kordhinta - Midig iyo bidix u jiid si aad marba dhinac ugu dhaafiso muqaalka + Xawaaraha muuqaalka + Midig iyo bidix u jiid si aad u xakameyso halka uu muuqaalku marayo Laba jeer taabo midig ama bidix si aad u dhaafiso ama ku ceshato Laba jeer taabo si aad u dhaafiso - Inta aad is dhaafinayso + Inta aad is-dhaafinayso (Ilbiriqsiyo ahaan) Laba jeer taabo si aad u qallajiso - Dhexda taabo si aad u qallajisato + Labo jeer dhexda taabo si aad u joojiso Gurmad u samee xogta Iftiinka moobilka istcimaal Cusbooneysii marba halkaad marayso daawashada @@ -285,7 +285,7 @@ La degtay Soo degaya Ka Saar - Kor ama hoos u jiid labada dhinaca ee daaraha si aad u maamusho codka ama iftiinka + Kor ama hoos u jiid geesaha muuqaalka si aad u maamusho codka ama iftiinka Iskii-u-daarka xalqadda xigta Si iskii ah ayuu u daarayaa xalqadda xigta marka aad dhamaysato kaad daawanayso Si iskii ah ha ula socdo appku hadba xalqadda aad marayso @@ -310,7 +310,7 @@ Safe mode ayaa daaran Dhammaan Kordhiyeyaasha waa la wada demiyey cillad timi aawadeed si aad u ogaato ka cillada sababaya. %s (La xayiray) - Marka dambe eed gasho isticmaal + Dib u fur app-ka si aad u aragto isbeddelada. Eeg warbixinra Cilladda Qiimeynta: %s Lahaanshaha @@ -424,8 +424,8 @@ Xayiran: %d Aan dejinayn: %d Waxa la cusbooneysiiyey %d sidkane - Ugu horreyn cloudstream ma laha wax websaydyo uu filimaanta kasoo xigto, waxay noqonaysaa inaad adigu rakibato reboositarradooda... \n \nSababtuna waa in mar dhexdaas ah na dacweeyeen shirkadda Sky UK Limited🤮, markaa si aan mar dambe taasi u dhicin anagu kuma rakibi karno... \n \nDiscord naga soo qabo ama internetka ka baadh. - Soo deji dhamaan sidkanayaasha reboositarkan? + Ugu horreyn cloudstream ma laha wax websaydyo uu filimaanta kasoo xigto, waxay noqonaysaa inaad adigu rakibato reboositarradooda. Sababtuna waa in mar dhexdaas ah na dacweeyeen shirkadda Sky UK Limited, markaa si aan mar dambe taasi u dhicin anagu kuma rakibi karno...\n\nDiscord naga soo qabo ama internetka ka baadh. + Digniin: CloudStream mas\'uuliyad kama qaadanayo isticmaalka kordhinnada (extensions) dhinac saddexaad mana bixiyo wax taageero ah oo iyaga ku saabsan! Boodhka Boodhka xalqadda Boodhka weyn @@ -465,4 +465,45 @@ Bilowga Bilow isku qasan Qoraalka dhamaadka + Seasonka %1$d Xalqada %2$d waxaa la soo deyn doonaa + %1$d saac %2$d daq %3$d ilb + %1$ddaq %2$dilb + %1$dilb + Sawirka + Ka bilow bilowga + Waxa soo degaya + Aqoonsiga codka ma shaqeynayo + Bilow hadalka… + biraawsar + Daaro musalsalka oo dhan + Fiidiyowgaan waa Torrent, taasi waxay la macno tahay in la raadraaci karo dhaqdhaqaaqaaga daawashada.\nHubi inaad fahamsan tahay sida Torrent-ku u shaqeeyo ka hor intaadan sii socon. + Dooro waxyaabaha aad tirayso + Ma jiro muuqaal aad soo degsatay. + Hadda ma jiro wax safka ku jira oo soo degaya. + Waxaa laa daawan karaa khad la\'aan + Dooro dhammaan + Ha dooran dhammaan + Daaro muuqaallada taleefanka kuugu jira + Linkska dib ayaa loo cusboonaysiiyay + Magaca Repository-ka iyo URL-ka + Waa la koobiyeeyay! + Ogeysiiska xalqada cusub + Ka raadi extensions-ka kale + Muuji waxyaabaha laguu soo jeediyay + Daaro + Diiwaanka + Wuxuu ku darayaa inaad doorato xawaaraha muuqaalka + Iftiin dheeraad ah + Daar filter-ka iftiinka marka uu iftiinku 100% ka bato + Iftiinka dheeraadka ah waa shidan yahay + Inta jeer ee la kaydinayo + Kaydka + Talooyinka Raadinta + Soo bandhig talooyinka inta aad wax qorayso + Tirtir Talooyinka + Muuji xogta dulsaaran muuqaalka + Soo bandhig guddiga dhibidda + Dooro habka loo kala saarayo soo dejinta plugins-ka + Soo deji nooca hordhaca ah + Nooca hordhaca ah horey ayaa loo soo dejiyay. diff --git a/app/src/main/res/values-b+sq/strings.xml b/app/src/main/res/values-b+sq/strings.xml index 828066ead..d083809bb 100644 --- a/app/src/main/res/values-b+sq/strings.xml +++ b/app/src/main/res/values-b+sq/strings.xml @@ -269,7 +269,7 @@ NSFW Tjera Film - Seri + Serial Vizatimorë Anime OVA diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 0740e0b8d..0b35d39a7 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -722,7 +722,7 @@ Inget konto Dra uppåt igen för att gå över 100% Startar plugin uppdateringsprocessen! - %d plugin(s) har uppdaterats! + %d plugin(s) har uppdaterats Inga plugins blev uppdaterade. Spelarnotiser Spelarnotisen för att styra uppspelningen i bakgrunden diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 3e2a93fb4..a68d45183 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -660,7 +660,7 @@ Cập nhật tiện ích mở rộng Không có tiện ích mở rộng nào được cập nhật. Ngày phát sóng (Cũ nhất) - Đã cập nhật thành công %d tiện ích mở rộng! + Đã cập nhật thành công %d tiện ích mở rộng Xếp hạng (Cao nhất) Cập nhật tiện ích mở rộng thủ công Tập (Giảm dần) From 8a01c6c9628e256a3e990dea51b3a1f964acf3eb Mon Sep 17 00:00:00 2001 From: ByAyzen <189399597+ByAyzen@users.noreply.github.com> Date: Sat, 29 Aug 2026 01:56:49 +0300 Subject: [PATCH 101/105] Open in browser is now directing immedialetly. (#3137) --- .../cloudstream3/ui/home/HomeFragment.kt | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index d0b4b0f20..c9c9adfc2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -32,6 +32,7 @@ import com.google.android.material.chip.Chip import com.lagradost.api.Log import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.apis +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainAPI @@ -67,6 +68,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettin import com.lagradost.cloudstream3.utils.AppContextUtils.isNetworkAvailable import com.lagradost.cloudstream3.utils.AppContextUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppContextUtils.loadSearchResult +import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser import com.lagradost.cloudstream3.utils.AppContextUtils.ownHide import com.lagradost.cloudstream3.utils.AppContextUtils.ownShow import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus @@ -869,26 +871,16 @@ class HomeFragment : BaseFragment( homeRandomButtonTv.isGone = true } } - + //Open browser directly, without a menu. is Resource.Failure -> { homeLoadingShimmer.stopShimmer() homeReloadConnectionerror.setOnClickListener(apiChangeClickListener) - homeReloadConnectionOpenInBrowser.setOnClickListener { view -> - val validAPIs = apis//.filter { api -> api.hasMainPage } - - view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> - Pair( - index, - api.name - ) - }) { - try { - val i = Intent(Intent.ACTION_VIEW) - i.data = validAPIs[itemId].mainUrl.toUri() - startActivity(i) - } catch (e: Exception) { - logError(e) - } + homeReloadConnectionOpenInBrowser.setOnClickListener { + val currentApi = currentApiName?.let { getApiFromNameNull(it) } + ?: homeViewModel.apiName.value?.let { getApiFromNameNull(it) } + val mainUrl = currentApi?.mainUrl + if (!mainUrl.isNullOrBlank()) { + context?.openBrowser(mainUrl) } } From 92b3ea92efe1f5a423fab882ab62cc57e80b1e48 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:50:26 +0200 Subject: [PATCH 102/105] Compose work (#3151) --- app/build.gradle.kts | 7 + .../lagradost/cloudstream3/CloudStreamApp.kt | 4 +- .../cloudstream3/actions/VideoClickAction.kt | 2 +- .../cloudstream3/network/RequestsHelper.kt | 2 +- .../source_priority/QualityProfileDialog.kt | 7 +- .../cloudstream3/ui/settings/Globals.kt | 32 +- .../ui/settings/SettingsAccount.kt | 19 +- .../ui/settings/SettingsAccount2.kt | 7 + .../ui/settings/SettingsAccountScreen.kt | 143 +++++ .../ui/settings/SettingsGeneral.kt | 178 ++++--- .../ui/settings/SettingsGeneral2.kt | 7 + .../ui/settings/SettingsGeneralScreen.kt | 268 ++++++++++ .../ui/settings/SettingsPlayer2.kt | 7 + .../ui/settings/SettingsPlayerScreen.kt | 353 +++++++++++++ .../ui/settings/SettingsProviders.kt | 3 +- .../ui/settings/SettingsProviders2.kt | 6 + .../ui/settings/SettingsProvidersScreen.kt | 88 ++++ .../cloudstream3/ui/settings/SettingsUI2.kt | 7 + .../ui/settings/SettingsUIScreen.kt | 289 +++++++++++ .../ui/settings/SettingsUpdates2.kt | 7 + .../ui/settings/SettingsUpdatesScreen.kt | 222 ++++++++ .../ui/settings/logcat/LogcatDialog.kt | 258 +++++++++ .../ui/settings/logcat/LogcatParser.kt | 140 +++++ .../cloudstream3/utils/AppContextUtils.kt | 13 +- .../cloudstream3/utils/BackupUtils.kt | 3 +- .../cloudstream3/utils/BaseComposeFragment.kt | 43 ++ .../cloudstream3/utils/PowerManagerAPI.kt | 2 +- .../cloudstream3/utils/videoskip/AnimeSkip.kt | 2 + app/src/main/res/drawable/article_24px.xml | 11 + .../main/res/drawable/aspect_ratio_24px.xml | 10 + app/src/main/res/drawable/autorenew_24px.xml | 10 + .../main/res/drawable/battery_alert_24px.xml | 10 + app/src/main/res/drawable/cast.xml | 10 + app/src/main/res/drawable/colors_24px.xml | 10 + .../main/res/drawable/construction_24px.xml | 10 + app/src/main/res/drawable/copy_all_24px.xml | 10 + app/src/main/res/drawable/dns_24px.xml | 10 + app/src/main/res/drawable/extension_24px.xml | 10 + .../main/res/drawable/extention_download.xml | 14 + app/src/main/res/drawable/extention_renew.xml | 21 + .../main/res/drawable/extention_renew2.xml | 11 + app/src/main/res/drawable/face_24px.xml | 10 + app/src/main/res/drawable/filter_alt_24px.xml | 10 + app/src/main/res/drawable/folder_24px.xml | 10 + app/src/main/res/drawable/hard_drive_24px.xml | 10 + app/src/main/res/drawable/high_res_24px.xml | 10 + .../res/drawable/history_toggle_off_24px.xml | 10 + .../keyboard_double_arrow_right_24px.xml | 11 + app/src/main/res/drawable/label_24px.xml | 11 + .../drawable/language_korean_latin_24px.xml | 10 + app/src/main/res/drawable/light_mode_24px.xml | 10 + app/src/main/res/drawable/match_word_24px.xml | 10 + app/src/main/res/drawable/memory_24px.xml | 10 + app/src/main/res/drawable/memory_alt_24px.xml | 10 + .../res/drawable/mobile_arrow_down_24px.xml | 10 + .../main/res/drawable/mobile_code_24px.xml | 10 + .../main/res/drawable/mobile_wrench_24px.xml | 10 + app/src/main/res/drawable/movie_edit_24px.xml | 10 + app/src/main/res/drawable/movie_info_24px.xml | 10 + .../drawable/notifications_active_24px.xml | 10 + app/src/main/res/drawable/palette_24px.xml | 10 + .../picture_in_picture_center_24px.xml | 10 + app/src/main/res/drawable/pip_24px.xml | 10 + app/src/main/res/drawable/play_arrow_24px.xml | 10 + app/src/main/res/drawable/plugin_lang.xml | 12 + .../main/res/drawable/quick_novel_icon.xml | 50 +- .../res/drawable/responsive_layout_24px.xml | 10 + .../main/res/drawable/restore_page_24px.xml | 10 + app/src/main/res/drawable/save_as_24px.xml | 10 + app/src/main/res/drawable/save_clock_24px.xml | 10 + .../res/drawable/screen_rotation_alt_24px.xml | 10 + app/src/main/res/drawable/shuffle_24px.xml | 10 + app/src/main/res/drawable/skip_next_24px.xml | 10 + .../main/res/drawable/subtitles_gear_24px.xml | 10 + app/src/main/res/drawable/swipe_24px.xml | 10 + .../main/res/drawable/swipe_vertical_24px.xml | 10 + app/src/main/res/drawable/tooltip_24px.xml | 10 + .../main/res/drawable/touch_double_24px.xml | 10 + .../main/res/drawable/visibility_off_24px.xml | 10 + app/src/main/res/drawable/wifi_proxy_24px.xml | 10 + .../main/res/navigation/mobile_navigation.xml | 12 +- .../res/values/donottranslate-strings.xml | 10 +- app/src/main/res/values/strings.xml | 5 +- build.gradle.kts | 2 + desktopApp/build.gradle.kts | 55 ++ desktopApp/src/desktop-icons/icon-linux.png | Bin 0 -> 8334 bytes desktopApp/src/desktop-icons/icon-windows.ico | Bin 0 -> 8868 bytes .../kotlin/com/lagradost/cloudstream4/Main.kt | 59 +++ desktopApp/src/readme.md | 1 + gradle/libs.versions.toml | 27 +- settings.gradle.kts | 2 +- shared/build.gradle.kts | 55 ++ shared/readme.md | 112 ++++ .../DevicePreferenceStore.android.kt | 19 + .../cloudstream4/compose/BackHandler.kt | 42 ++ .../cloudstream4/compose/ComposeFragment.kt | 51 ++ .../lagradost/cloudstream4/compose/Layout.kt | 48 ++ .../cloudstream4/theme/ColorPreference.kt | 72 +++ .../cloudstream4/theme/DeviceTheme.android.kt | 73 +++ .../common/preference/AndroidPreference.kt | 235 +++++++++ .../preference/AndroidPreferenceStore.kt | 102 ++++ .../composeResources/drawable/arrow_back.xml | 10 + .../drawable/arrow_downward.xml | 9 + .../drawable/arrow_upward.xml | 9 + .../composeResources/drawable/cancel.xml | 9 + .../composeResources/drawable/check.xml | 9 + .../composeResources/drawable/close.xml | 9 + .../drawable/default_icon.png | Bin 0 -> 8334 bytes .../composeResources/drawable/error.xml | 9 + .../composeResources/drawable/info.xml | 9 + .../composeResources/drawable/preview.xml | 9 + .../font/productsans_black.ttf | Bin 0 -> 91660 bytes .../font/productsans_blackitalic.ttf | Bin 0 -> 95404 bytes .../font/productsans_bold.ttf | Bin 0 -> 92096 bytes .../font/productsans_bolditalic.ttf | Bin 0 -> 95504 bytes .../font/productsans_italic.ttf | Bin 0 -> 95744 bytes .../font/productsans_light.ttf | Bin 0 -> 92564 bytes .../font/productsans_lightitalic.ttf | Bin 0 -> 96000 bytes .../font/productsans_medium.ttf | Bin 0 -> 92312 bytes .../font/productsans_mediumitalic.ttf | Bin 0 -> 95656 bytes .../font/productsans_regular.ttf | Bin 0 -> 109128 bytes .../font/productsans_thin.ttf | Bin 0 -> 92836 bytes .../font/productsans_thinitalic.ttf | Bin 0 -> 96540 bytes .../composeResources/values/strings.xml | 9 + .../cloudstream4/DevicePreferenceStore.kt | 202 +++++++ .../lagradost/cloudstream4/compose/Colors.kt | 24 + .../lagradost/cloudstream4/compose/Dialog.kt | 491 ++++++++++++++++++ .../cloudstream4/compose/Modifiers.kt | 63 +++ .../lagradost/cloudstream4/compose/Screen.kt | 8 + .../cloudstream4/compose/Viewmodel.kt | 145 ++++++ .../cloudstream4/theme/DeviceTheme.kt | 17 + .../com/lagradost/cloudstream4/theme/Font.kt | 71 +++ .../lagradost/cloudstream4/theme/Palette.kt | 84 +++ .../lagradost/cloudstream4/theme/Scheme.kt | 162 ++++++ .../com/lagradost/cloudstream4/theme/Theme.kt | 132 +++++ .../src/commonMain/kotlin/com/mihon/LICENSE | 176 +++++++ .../preference/InMemoryPreferenceStore.kt | 125 +++++ .../mihon/common/preference/PreferenceData.kt | 71 +++ .../common/preference/PreferenceStore.kt | 87 ++++ .../kotlin/com/mihon/material/AppBar.kt | 403 ++++++++++++++ .../kotlin/com/mihon/material/Constants.kt | 26 + .../com/mihon/material/LabeledCheckbox.kt | 50 ++ .../kotlin/com/mihon/material/Pill.kt | 61 +++ .../kotlin/com/mihon/material/Scaffold.kt | 85 +++ .../com/mihon/material/SettingsItems.kt | 474 +++++++++++++++++ .../kotlin/com/mihon/material/Slider.kt | 58 +++ .../kotlin/com/mihon/material/Typography.kt | 14 + .../kotlin/com/mihon/presentation/Modifier.kt | 120 +++++ .../com/mihon/presentation/Navigation.kt | 9 + .../mihon/presentation/settings/Preference.kt | 198 +++++++ .../presentation/settings/PreferenceItem.kt | 188 +++++++ .../settings/PreferenceScaffold.kt | 46 ++ .../presentation/settings/PreferenceScreen.kt | 96 ++++ .../settings/SearchableSettings.kt | 39 ++ .../settings/widget/BasePreferenceWidget.kt | 127 +++++ .../widget/EditTextPreferenceWidget.kt | 97 ++++ .../settings/widget/InfoWidget.kt | 53 ++ .../settings/widget/ListPreferenceWidget.kt | 110 ++++ .../widget/MultiSelectListPreferenceWidget.kt | 97 ++++ .../settings/widget/PreferenceGroupHeader.kt | 28 + .../settings/widget/SwitchPreferenceWidget.kt | 73 +++ .../settings/widget/TextPreferenceWidget.kt | 86 +++ .../src/commonMain/kotlin/com/mihon/readme.md | 4 + .../cloudstream4/DevicePreferenceStore.jvm.kt | 16 + .../cloudstream4/theme/DeviceTheme.jvm.kt | 19 + shared/src/readme.md | 11 + 166 files changed, 8041 insertions(+), 178 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt create mode 100644 app/src/main/res/drawable/article_24px.xml create mode 100644 app/src/main/res/drawable/aspect_ratio_24px.xml create mode 100644 app/src/main/res/drawable/autorenew_24px.xml create mode 100644 app/src/main/res/drawable/battery_alert_24px.xml create mode 100644 app/src/main/res/drawable/cast.xml create mode 100644 app/src/main/res/drawable/colors_24px.xml create mode 100644 app/src/main/res/drawable/construction_24px.xml create mode 100644 app/src/main/res/drawable/copy_all_24px.xml create mode 100644 app/src/main/res/drawable/dns_24px.xml create mode 100644 app/src/main/res/drawable/extension_24px.xml create mode 100644 app/src/main/res/drawable/extention_download.xml create mode 100644 app/src/main/res/drawable/extention_renew.xml create mode 100644 app/src/main/res/drawable/extention_renew2.xml create mode 100644 app/src/main/res/drawable/face_24px.xml create mode 100644 app/src/main/res/drawable/filter_alt_24px.xml create mode 100644 app/src/main/res/drawable/folder_24px.xml create mode 100644 app/src/main/res/drawable/hard_drive_24px.xml create mode 100644 app/src/main/res/drawable/high_res_24px.xml create mode 100644 app/src/main/res/drawable/history_toggle_off_24px.xml create mode 100644 app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml create mode 100644 app/src/main/res/drawable/label_24px.xml create mode 100644 app/src/main/res/drawable/language_korean_latin_24px.xml create mode 100644 app/src/main/res/drawable/light_mode_24px.xml create mode 100644 app/src/main/res/drawable/match_word_24px.xml create mode 100644 app/src/main/res/drawable/memory_24px.xml create mode 100644 app/src/main/res/drawable/memory_alt_24px.xml create mode 100644 app/src/main/res/drawable/mobile_arrow_down_24px.xml create mode 100644 app/src/main/res/drawable/mobile_code_24px.xml create mode 100644 app/src/main/res/drawable/mobile_wrench_24px.xml create mode 100644 app/src/main/res/drawable/movie_edit_24px.xml create mode 100644 app/src/main/res/drawable/movie_info_24px.xml create mode 100644 app/src/main/res/drawable/notifications_active_24px.xml create mode 100644 app/src/main/res/drawable/palette_24px.xml create mode 100644 app/src/main/res/drawable/picture_in_picture_center_24px.xml create mode 100644 app/src/main/res/drawable/pip_24px.xml create mode 100644 app/src/main/res/drawable/play_arrow_24px.xml create mode 100644 app/src/main/res/drawable/plugin_lang.xml create mode 100644 app/src/main/res/drawable/responsive_layout_24px.xml create mode 100644 app/src/main/res/drawable/restore_page_24px.xml create mode 100644 app/src/main/res/drawable/save_as_24px.xml create mode 100644 app/src/main/res/drawable/save_clock_24px.xml create mode 100644 app/src/main/res/drawable/screen_rotation_alt_24px.xml create mode 100644 app/src/main/res/drawable/shuffle_24px.xml create mode 100644 app/src/main/res/drawable/skip_next_24px.xml create mode 100644 app/src/main/res/drawable/subtitles_gear_24px.xml create mode 100644 app/src/main/res/drawable/swipe_24px.xml create mode 100644 app/src/main/res/drawable/swipe_vertical_24px.xml create mode 100644 app/src/main/res/drawable/tooltip_24px.xml create mode 100644 app/src/main/res/drawable/touch_double_24px.xml create mode 100644 app/src/main/res/drawable/visibility_off_24px.xml create mode 100644 app/src/main/res/drawable/wifi_proxy_24px.xml create mode 100644 desktopApp/build.gradle.kts create mode 100644 desktopApp/src/desktop-icons/icon-linux.png create mode 100644 desktopApp/src/desktop-icons/icon-windows.ico create mode 100644 desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt create mode 100644 desktopApp/src/readme.md create mode 100644 shared/build.gradle.kts create mode 100644 shared/readme.md create mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt create mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/BackHandler.kt create mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt create mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt create mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt create mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt create mode 100644 shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt create mode 100644 shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt create mode 100644 shared/src/commonMain/composeResources/drawable/arrow_back.xml create mode 100644 shared/src/commonMain/composeResources/drawable/arrow_downward.xml create mode 100644 shared/src/commonMain/composeResources/drawable/arrow_upward.xml create mode 100644 shared/src/commonMain/composeResources/drawable/cancel.xml create mode 100644 shared/src/commonMain/composeResources/drawable/check.xml create mode 100644 shared/src/commonMain/composeResources/drawable/close.xml create mode 100644 shared/src/commonMain/composeResources/drawable/default_icon.png create mode 100644 shared/src/commonMain/composeResources/drawable/error.xml create mode 100644 shared/src/commonMain/composeResources/drawable/info.xml create mode 100644 shared/src/commonMain/composeResources/drawable/preview.xml create mode 100644 shared/src/commonMain/composeResources/font/productsans_black.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_blackitalic.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_bold.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_bolditalic.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_italic.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_light.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_lightitalic.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_medium.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_mediumitalic.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_regular.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_thin.ttf create mode 100644 shared/src/commonMain/composeResources/font/productsans_thinitalic.ttf create mode 100644 shared/src/commonMain/composeResources/values/strings.xml create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt create mode 100644 shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/LICENSE create mode 100644 shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/Constants.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/Pill.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/Slider.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/material/Typography.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/readme.md create mode 100644 shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt create mode 100644 shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt create mode 100644 shared/src/readme.md diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ad8451a3..c714368ee 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,8 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.dokka) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.compose.compiler) } val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) @@ -288,7 +290,12 @@ dependencies { implementation(libs.work.runtime.ktx) implementation(libs.nicehttp) // HTTP Lib + implementation(libs.bundles.compose) + implementation(libs.activity.compose) + implementation(libs.kotlinx.io.core) // Logcat parser + implementation(project(":library")) + implementation(project(":shared")) } tasks.register("androidSourcesJar") { diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index 466ef5a00..6ff3ab54d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -170,11 +170,11 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { } /** Will fall back to WebView if in TV or emulator layout. */ - fun openBrowser(url: String, activity: FragmentActivity?) { + fun openBrowser(url: String, activity: Activity?) { openBrowser( url, isLayout(TV or EMULATOR), - activity?.supportFragmentManager?.fragments?.lastOrNull() + (activity as? FragmentActivity)?.supportFragmentManager?.fragments?.lastOrNull() ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index f4e8768d8..0fdccb476 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -98,7 +98,7 @@ object VideoClickActionHolder { ?.second } - fun getPlayers(activity: Activity? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShowSafe(activity, null) } + fun getPlayers(context: Context? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShowSafe(context, null) } } abstract class VideoClickAction { diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt index 203a503e9..e49245904 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -37,7 +37,7 @@ fun buildDefaultClient(context: Context, ignoreSSL: Boolean = false): OkHttpClie safe { Security.insertProviderAt(Conscrypt.newProvider(), 1) } val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) - val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0) + val dns = settingsManager.getInt(context.getString(R.string.dns_key), 0) val baseClient = OkHttpClient.Builder() .followRedirects(true) .followSslRedirects(true) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt index 6a0f12e9a..e1b8ad479 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.player.source_priority +import android.app.Activity import android.app.Dialog import androidx.annotation.StyleRes import androidx.core.view.isVisible @@ -26,7 +27,7 @@ data class LinkSource( class QualityProfileDialog private constructor( - val activity: FragmentActivity, + val activity: Activity, @StyleRes val themeRes: Int, private val links: List, private val usedProfile: Int?, @@ -34,7 +35,7 @@ class QualityProfileDialog private constructor( private val useProfileSelection: Boolean ) : Dialog(activity, themeRes) { constructor( - activity: FragmentActivity, + activity: Activity, @StyleRes themeRes: Int, links: List, usedProfile: Int, @@ -42,7 +43,7 @@ class QualityProfileDialog private constructor( ) : this(activity, themeRes, links, usedProfile, profileSelectionCallback, true) constructor( - activity: FragmentActivity, + activity: Activity, @StyleRes themeRes: Int, links: List ) : this(activity, themeRes, links, null, null, false) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt index 93e469a4d..70fd2a19d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt @@ -1,19 +1,18 @@ package com.lagradost.cloudstream3.ui.settings -import android.app.UiModeManager import android.content.Context import android.content.res.Configuration import android.content.res.Resources -import android.os.Build import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream4.compose.DeviceLayout.Companion.isAutoTv object Globals { var beneneCount = 0 - const val PHONE : Int = 0b001 - const val TV : Int = 0b010 - const val EMULATOR : Int = 0b100 + const val PHONE: Int = 0b00001 + const val TV: Int = 0b00010 + const val EMULATOR: Int = 0b00100 private const val INVALID = -1 private var layoutId = INVALID @@ -22,18 +21,9 @@ object Globals { return settingsManager.getInt(this.getString(R.string.app_layout_key), -1) } - private fun Context.isAutoTv(): Boolean { - val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager? - // AFT = Fire TV - val model = Build.MODEL.lowercase() - return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Build.MODEL.contains( - "AFT" - ) || model.contains("firestick") || model.contains("fire tv") || model.contains("chromecast") - } - - private fun Context.layoutIntCorrected(): Int { - return when(getLayoutInt()) { - -1 -> if (isAutoTv()) TV else PHONE + fun Context.updateTv() { + layoutId = when (getLayoutInt()) { + -1 -> if (isAutoTv(this)) TV else PHONE 0 -> PHONE 1 -> TV 2 -> EMULATOR @@ -41,14 +31,10 @@ object Globals { } } - fun Context.updateTv() { - layoutId = layoutIntCorrected() - } - /** Returns true if the current orientation is landscape. */ fun isLandscape(): Boolean = isLayout(TV or EMULATOR) || - Resources.getSystem().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + Resources.getSystem().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE /** Returns true if the layout is any of the flags, * so isLayout(TV or EMULATOR) is a valid statement for checking if the layout is in the emulator @@ -56,7 +42,7 @@ object Globals { * * Valid flags are: PHONE, TV, EMULATOR * */ - fun isLayout(flags: Int) : Boolean { + fun isLayout(flags: Int): Boolean { return (layoutId and flags) != 0 } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 75ba55674..6f67ab826 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.settings import android.annotation.SuppressLint +import android.app.Activity import android.graphics.Bitmap import android.os.Bundle import android.os.CountDownTimer @@ -13,7 +14,6 @@ import androidx.appcompat.app.AlertDialog import androidx.core.content.edit import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceManager import androidx.preference.SwitchPreference import androidx.recyclerview.widget.RecyclerView @@ -29,8 +29,8 @@ import com.lagradost.cloudstream3.databinding.DeviceAuthBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.animeSkipApi -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.kitsuApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi @@ -42,8 +42,8 @@ import com.lagradost.cloudstream3.syncproviders.SubtitleRepo import com.lagradost.cloudstream3.syncproviders.SyncRepo import com.lagradost.cloudstream3.ui.BasePreferenceFragmentCompat import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR -import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.PHONE +import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn @@ -76,7 +76,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { /** Used by nginx plugin too */ @SuppressLint("StringFormatInvalid") fun showLoginInfo( - activity: FragmentActivity?, + activity: Activity?, api: AuthRepo, info: AuthUser?, index: Int, @@ -121,7 +121,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { } } - private fun showAccountSwitch(activity: FragmentActivity, api: AuthRepo) { + private fun showAccountSwitch(activity: Activity, api: AuthRepo) { val accounts = api.accounts val binding: AccountSwitchBinding = AccountSwitchBinding.inflate(activity.layoutInflater, null, false) @@ -153,7 +153,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { @UiThread - fun showPin(activity: FragmentActivity, api: AuthRepo) { + fun showPin(activity: Activity, api: AuthRepo) { val binding: DeviceAuthBinding = DeviceAuthBinding.inflate(activity.layoutInflater, null, false) @@ -273,8 +273,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { } - fun showAppLogin(activity: FragmentActivity, api: AuthRepo) { - + fun showAppLogin(activity: Activity, api: AuthRepo) { val binding: AddAccountInputBinding = AddAccountInputBinding.inflate(activity.layoutInflater, null, false) val builder = @@ -315,7 +314,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { binding.createAccount.setOnClickListener { openBrowser( api.createAccountUrl ?: return@setOnClickListener, - activity + activity, ) dialog.dismissSafe() } @@ -391,7 +390,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback { } @UiThread - fun addAccount(activity: FragmentActivity, api: AuthRepo) { + fun addAccount(activity: Activity, api: AuthRepo) { try { if (api.hasPin && !isLayout(PHONE)) { showPin(activity, api) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt new file mode 100644 index 000000000..0185370ce --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt @@ -0,0 +1,7 @@ +package com.lagradost.cloudstream3.ui.settings + +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.mihon.presentation.settings.SearchableSettings + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsAccount2 : BaseComposeFragment(), SearchableSettings by SettingsAccountScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt new file mode 100644 index 000000000..47dac104d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt @@ -0,0 +1,143 @@ +package com.lagradost.cloudstream3.ui.settings + +import android.content.Context +import androidx.activity.compose.LocalActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.fragment.app.FragmentActivity +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.animeSkipApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.kitsuApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi +import com.lagradost.cloudstream3.syncproviders.PlainAuthRepo +import com.lagradost.cloudstream3.syncproviders.SubtitleRepo +import com.lagradost.cloudstream3.syncproviders.SyncRepo +import com.lagradost.cloudstream3.ui.settings.SettingsAccount.Companion.addAccount +import com.lagradost.cloudstream3.ui.settings.SettingsAccount.Companion.showLoginInfo +import com.lagradost.cloudstream3.utils.AppContextUtils.html +import com.lagradost.cloudstream3.utils.BackupUtils +import com.lagradost.cloudstream3.utils.BiometricAuthenticator +import com.lagradost.cloudstream3.utils.BiometricAuthenticator.authCallback +import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt +import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock +import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled +import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo +import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText +import com.lagradost.cloudstream4.AppSettings +import com.lagradost.cloudstream4.compose.PHONE +import com.lagradost.cloudstream4.compose.isLayout +import com.lagradost.cloudstream4.rememberAppSettings +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import kotlinx.collections.immutable.persistentListOf + +object SettingsAccountScreen : SearchableSettings, BiometricAuthenticator.BiometricCallback { + val syncApis = persistentListOf( + SyncRepo(malApi), + SyncRepo(kitsuApi), + SyncRepo(aniListApi), + SyncRepo(simklApi), + SubtitleRepo(openSubtitlesApi), + SubtitleRepo(subDlApi), + PlainAuthRepo(animeSkipApi), + ) + private fun updateAuthPreference(context: Context, enabled: Boolean) { + val settings = AppSettings(context) + settings.security.biometrics.set(enabled) + } + + override fun onAuthenticationError() { + val context = activity ?: return + updateAuthPreference(context, !isAuthEnabled(context)) + } + + override fun onAuthenticationSuccess() { + val context = activity ?: return + + if (isAuthEnabled(context)) { + updateAuthPreference(context, true) + BackupUtils.backup(context) + context.showBottomDialogText( + context.getString(R.string.biometric_setting), + context.getString(R.string.biometric_warning).html() + ) { onDialogDismissedEvent } + } else { + updateAuthPreference(context, false) + } + } + + @Composable + override fun getTitleRes(): String = stringResource(R.string.category_account) + + @Composable + override fun getPreferences(): List { + val settings = rememberAppSettings() + val activity = LocalActivity.current + val context = LocalContext.current + val hasSecurity = remember(context) { + deviceHasPasswordPinLock(context) + } + + return persistentListOf( + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_accounts), + preferenceItems = syncApis.map { api -> + Preference.PreferenceItem.TextPreference( + title = api.name, + icon = api.icon?.let { painterResource(it) }, + onClick = { + val activity = activity ?: return@TextPreference + val info = api.authUser() + val index = + api.accounts.indexOfFirst { account -> account.user.id == info?.id } + if (api.accounts.isNotEmpty()) { + showLoginInfo(activity, api, info, index) + } else { + addAccount(activity, api) + } + }) + } + Preference.PreferenceItem.SwitchPreference( + preference = settings.security.skipAccountSelection, + title = stringResource(R.string.skip_startup_account_select_pref), + icon = painterResource(R.drawable.ic_outline_account_circle_24) + ), + ), Preference.PreferenceGroup( + enabled = hasSecurity && isLayout(PHONE), + title = stringResource(R.string.pref_category_security), + preferenceItems = persistentListOf( + Preference.PreferenceItem.SwitchPreference( + preference = settings.security.biometrics, + title = stringResource(R.string.biometric_setting), + subtitle = stringResource(R.string.biometric_setting_summary), + icon = painterResource(R.drawable.ic_fingerprint), + onValueChanged = { _ -> + val activity = + activity as? FragmentActivity ?: return@SwitchPreference false + + if (deviceHasPasswordPinLock(activity)) { + startBiometricAuthentication( + activity, R.string.biometric_authentication_title, false + ) + promptInfo?.let { + authCallback = this + biometricPrompt?.authenticate(it) + } + } + + return@SwitchPreference true + }) + ) + ) + ) + } +} \ No newline at end of file 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 b04f9a4a2..91d729b91 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 @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.settings import android.content.Context import android.net.Uri import android.os.Bundle +import android.view.LayoutInflater import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -17,6 +18,7 @@ import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity +import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R @@ -175,6 +177,96 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { putString(context.getString(R.string.download_path_key_visual), visual) } } + + fun getCurrent(): MutableList { + return getKey>(USER_PROVIDER_API)?.toMutableList() + ?: mutableListOf() + } + fun showAdd() { + val providers = allProviders.distinctBy { it::class }.sortedBy { it.name } + val context = activity + context?.showDialog( + providers.map { "${it.name} (${it.mainUrl})" }, + -1, + context.getString(R.string.add_site_pref), + true, + {}) { selection -> + val provider = providers.getOrNull(selection) ?: return@showDialog + + val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(LayoutInflater.from( + context + ),null,false) + + val builder = + AlertDialog.Builder(context, R.style.AlertDialogCustom) + .setView(binding.root) + + val dialog = builder.create() + dialog.show() + + binding.text2.text = provider.name + binding.applyBtt.setOnClickListener { + val name = binding.siteNameInput.text?.toString() + val url = binding.siteUrlInput.text?.toString() + val lang = binding.siteLangInput.text?.toString() + val realLang = if (lang.isNullOrBlank()) provider.lang else lang + val simpleName = provider::class.simpleName + if (url.isNullOrBlank() || name.isNullOrBlank() || simpleName == null) { + showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) + return@setOnClickListener + } + + val current = getCurrent() + val newSite = CustomSite(simpleName, name, url, realLang) + current.add(newSite) + setKey(USER_PROVIDER_API, current.toTypedArray()) + // reload apis + MainActivity.afterPluginsLoadedEvent.invoke(false) + + dialog.dismissSafe(activity) + } + binding.cancelBtt.setOnClickListener { + dialog.dismissSafe(activity) + } + } + } + + fun showDelete() { + val current = getCurrent() + val context = activity + context?.showMultiDialog( + current.map { it.name }, + listOf(), + context.getString(R.string.remove_site_pref), + {}) { indexes -> + current.removeAll(indexes.map { current[it] }) + setKey(USER_PROVIDER_API, current.toTypedArray()) + } + } + + fun showAddOrDelete() { + val context = activity + + val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate( + LayoutInflater.from( + context + ),null,false) + val builder = + AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom) + .setView(binding.root) + + val dialog = builder.create() + dialog.show() + + binding.addSite.setOnClickListener { + showAdd() + dialog.dismissSafe(activity) + } + binding.removeSite.setOnClickListener { + showDelete() + dialog.dismissSafe(activity) + } + } } private val pathPicker = getChooseFolderLauncher { uri, path -> @@ -186,10 +278,6 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { setPreferencesFromResource(R.xml.settings_general, rootKey) val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) - fun getCurrent(): MutableList { - return getKey>(USER_PROVIDER_API)?.toMutableList() - ?: mutableListOf() - } getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> val current = getCurrentLocale(pref.context) @@ -226,83 +314,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { true } - fun showAdd() { - val providers = allProviders.distinctBy { it::class }.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 binding : AddSiteInputBinding = AddSiteInputBinding.inflate(layoutInflater,null,false) - - val builder = - AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom) - .setView(binding.root) - - val dialog = builder.create() - dialog.show() - - binding.text2.text = provider.name - binding.applyBtt.setOnClickListener { - val name = binding.siteNameInput.text?.toString() - val url = binding.siteUrlInput.text?.toString() - val lang = binding.siteLangInput.text?.toString() - val realLang = if (lang.isNullOrBlank()) provider.lang else lang - val simpleName = provider::class.simpleName - if (url.isNullOrBlank() || name.isNullOrBlank() || simpleName == null) { - showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) - return@setOnClickListener - } - - val current = getCurrent() - val newSite = CustomSite(simpleName, name, url, realLang) - current.add(newSite) - setKey(USER_PROVIDER_API, current.toTypedArray()) - // reload apis - MainActivity.afterPluginsLoadedEvent.invoke(false) - - dialog.dismissSafe(activity) - } - binding.cancelBtt.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 binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate(layoutInflater,null,false) - val builder = - AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom) - .setView(binding.root) - - val dialog = builder.create() - dialog.show() - - binding.addSite.setOnClickListener { - showAdd() - dialog.dismissSafe(activity) - } - binding.removeSite.setOnClickListener { - showDelete() - dialog.dismissSafe(activity) - } - } getPref(R.string.override_site_key)?.setOnPreferenceClickListener { _ -> @@ -329,7 +341,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { val prefValues = resources.getIntArray(R.array.dns_pref_values) val currentDns = - settingsManager.getInt(getString(R.string.dns_pref), 0) + settingsManager.getInt(getString(R.string.dns_key), 0) activity?.showBottomDialog( prefNames.toList(), @@ -337,7 +349,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { getString(R.string.dns_pref), true, {}) { - settingsManager.edit { putInt(getString(R.string.dns_pref), prefValues[it]) } + settingsManager.edit { putInt(getString(R.string.dns_key), prefValues[it]) } (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true @@ -355,7 +367,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { (first + ctx.getExternalFilesDirs("").mapNotNull { it.path } + currentDir) - } catch (e: Exception) { + } catch (_: Exception) { first }).filterNotNull().distinct() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt new file mode 100644 index 000000000..37b1812b9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt @@ -0,0 +1,7 @@ +package com.lagradost.cloudstream3.ui.settings + +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.mihon.presentation.settings.SearchableSettings + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsGeneral2 : BaseComposeFragment(), SearchableSettings by SettingsGeneralScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt new file mode 100644 index 000000000..7ce49e8e9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt @@ -0,0 +1,268 @@ +package com.lagradost.cloudstream3.ui.settings + +import android.content.Intent +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.integerArrayResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.UnsafeSSL +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.insecureApp +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.network.initClient +import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.isAppRestricted +import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.showRequestIgnoreBatteryOptDialog +import com.lagradost.cloudstream4.AppSettings +import com.lagradost.cloudstream4.compose.ActionDialog +import com.lagradost.cloudstream4.compose.PHONE +import com.lagradost.cloudstream4.compose.isLayout +import com.lagradost.cloudstream4.rememberAppSettings +import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme +import com.lagradost.safefile.SafeFile +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import com.mihon.presentation.settings.collectAsState +import kotlinx.collections.immutable.persistentListOf + +object SettingsGeneralScreen : SearchableSettings { + @Composable + override fun getTitleRes(): String = stringResource(R.string.category_general) + + @Composable + override fun getPreferences(): List { + val settings = rememberAppSettings() + + // TODO Refactor entirely to use a different file path selector ect like QuickNovel + val selectFileSelector = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> + // It lies, it can be null if file manager quits. + if (uri == null) return@rememberLauncherForActivityResult + val context = CloudStreamApp.context ?: return@rememberLauncherForActivityResult + + try { + val settings = AppSettings(context) + // RW perms for the path + val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + context.contentResolver.takePersistableUriPermission(uri, flags) + + val filePath = SafeFile.fromUri(context, uri)?.filePath() + println("Selected URI path: $uri - Full path: $filePath") + + // store the actual URI instead of the path due to permissions. + // filePath should only be used for cosmetic purposes. + val visual = filePath ?: uri.toString() + settings.general.downloadPath.set(uri.toString()) + settings.general.downloadPathVisual.set(visual) + } catch (t: Throwable) { + logError(t) + } + } + + val bananas by settings.general.bananas.collectAsState() + val parallelDownloads by settings.general.parallelDownloads.collectAsState() + val concurrentConnections by settings.general.concurrentConnections.collectAsState() + val locale by settings.general.locale.collectAsState() + val downloadPathVisual by settings.general.downloadPathVisual.collectAsState() + //val downloadPath by settings.general.downloadPath.collectAsState() + + var isBatteryShown by remember { mutableStateOf(false) } + val context = LocalContext.current + + if (isBatteryShown) { + ActionDialog( + icon = painterResource(R.drawable.battery_alert_24px), + title = stringResource(R.string.battery_dialog_title), + text = stringResource(R.string.battery_dialog_message), + confirmText = stringResource(R.string.ok), + dismissText = stringResource(R.string.cancel), + dismiss = { + isBatteryShown = false + settings.general.batterOptimization.set(false) + }, + confirm = { + isBatteryShown = false + // The og impl never modified it to true? + // settings.general.batterOptimization.set(true) + context.showRequestIgnoreBatteryOptDialog() + } + ) + } + + return persistentListOf( + Preference.PreferenceItem.BasicListPreference( + value = locale, + entries = appLanguages.associate { (name, code) -> (code to (name to code).nameNextToFlagEmoji()) }, + title = stringResource(R.string.app_language), + icon = painterResource(R.drawable.language_korean_latin_24px), + onValueChanged = { value -> + settings.general.locale.set(value) + activity?.recreate() + }, + subtitleProvider = { v, e -> e[v] ?: getCurrentLocale(LocalContext.current) } + ), + Preference.PreferenceGroup( + title = stringResource(R.string.title_downloads), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + icon = painterResource(R.drawable.netflix_download), + subtitle = downloadPathVisual, + title = stringResource(R.string.download_path_pref), + onClick = { + // This is not a ListPreference because the old selection system is + // broken af. This needs to be refactored to QuickNovels download path + // system. + selectFileSelector.launch(Uri.EMPTY) + }, + ), + Preference.PreferenceItem.SliderPreference( + icon = painterResource(R.drawable.arrow_or_edge_24px), + value = parallelDownloads, + valueRange = 1..10, + title = stringResource(R.string.parallel_downloads), + subtitle = stringResource(R.string.download_parallel_settings_des), + onValueChanged = settings.general.parallelDownloads::set, + ), + Preference.PreferenceItem.SliderPreference( + icon = painterResource(R.drawable.arrow_and_edge_24px), + value = concurrentConnections, + valueRange = 1..10, + title = stringResource(R.string.concurrent_connections), + subtitle = stringResource(R.string.concurrent_connections_settings_des), + onValueChanged = settings.general.concurrentConnections::set, + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.battery_dialog_title), + icon = painterResource(R.drawable.battery_alert_24px), + enabled = isLayout(PHONE), + onClick = { + if (isAppRestricted(context)) { + isBatteryShown = true + } else { + showToast(R.string.app_unrestricted_toast) + } + } + ), + ) + ), + + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_bypass), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.add_site_pref), + subtitle = stringResource(R.string.add_site_summary), + icon = painterResource(R.drawable.copy_all_24px), + onClick = { + // TODO refactor into compose + if (SettingsGeneral.getCurrent().isEmpty()) { + SettingsGeneral.showAdd() + } else { + SettingsGeneral.showAddOrDelete() + } + } + ), + Preference.PreferenceItem.ListPreference( + title = stringResource(R.string.dns_pref), + subtitle = stringResource(R.string.dns_pref_summary), + icon = painterResource(R.drawable.dns_24px), + preference = settings.general.dns, + entries = integerArrayResource(R.array.dns_pref_values).zip( + stringArrayResource(R.array.dns_pref) + ).toMap(), + onValueChanged = { + (CloudStreamApp.context)?.let { ctx -> + app.initClient(ctx, ignoreSSL = false) + @OptIn(UnsafeSSL::class) + insecureApp.initClient(ctx, ignoreSSL = true) + } + return@ListPreference true + }, + ), + Preference.PreferenceItem.SwitchPreference( + title = stringResource(R.string.jsdelivr_proxy), + subtitle = stringResource(R.string.jsdelivr_proxy_summary), + icon = painterResource(R.drawable.wifi_proxy_24px), + preference = settings.general.jsdelivrProxy + ) + ) + ), + + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_links), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.github), + subtitle = "https://github.com/recloudstream/cloudstream", + icon = painterResource(R.drawable.ic_github_logo), + onClick = { + CloudStreamApp.openBrowser("https://github.com/recloudstream/cloudstream") + } + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.lightnovel), + subtitle = "https://github.com/LagradOst/QuickNovel", + icon = painterResource(R.drawable.quick_novel_icon), + onClick = { + CloudStreamApp.openBrowser("https://github.com/LagradOst/QuickNovel") + } + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.discord), + subtitle = "https://discord.gg/5Hus6fM", + icon = painterResource(R.drawable.ic_baseline_discord_24), + onClick = { + CloudStreamApp.openBrowser("https://discord.gg/5Hus6fM") + } + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.cs3wiki), + subtitle = "https://cloudstream.miraheze.org/", + icon = painterResource(R.drawable.baseline_description_24), + onClick = { + CloudStreamApp.openBrowser("https://cloudstream.miraheze.org/") + } + ), + ) + ), + + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.benene), + subtitle = if (bananas == 0) { + stringResource(R.string.benene_count_text_none) + } else { + stringResource(R.string.benene_count_text, bananas) + }, + onClick = { + settings.general.bananas.set(bananas + 1) + }, + icon = painterResource(R.drawable.benene), + ), + Preference.PreferenceItem.InfoPreference(title = stringResource(R.string.legal_notice_text)), + ) + } +} + + +@PreviewLightDark +@Composable +private fun SettingGeneralPreview() { + CloudStreamPreviewTheme { + SettingsGeneralScreen.Content() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt new file mode 100644 index 000000000..3c6a272d7 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt @@ -0,0 +1,7 @@ +package com.lagradost.cloudstream3.ui.settings + +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.mihon.presentation.settings.SearchableSettings + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsPlayer2 : BaseComposeFragment(), SearchableSettings by SettingsPlayerScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt new file mode 100644 index 000000000..309b65cb8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt @@ -0,0 +1,353 @@ +package com.lagradost.cloudstream3.ui.settings + +import android.text.format.Formatter.formatShortFileSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.integerArrayResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickActionHolder +import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize +import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment +import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream4.compose.EMULATOR +import com.lagradost.cloudstream4.compose.PHONE +import com.lagradost.cloudstream4.compose.TV +import com.lagradost.cloudstream4.compose.isLayout +import com.lagradost.cloudstream4.rememberAppSettings +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import com.mihon.presentation.settings.collectAsState +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlin.reflect.jvm.jvmName + +object SettingsPlayerScreen : SearchableSettings { + @Composable + override fun getTitleRes(): String = stringResource(R.string.category_player) + + @Composable + override fun getPreferences(): List { + val settings = rememberAppSettings() + + val context = LocalContext.current + val defaultPlayerName = stringResource(R.string.player_settings_play_in_app) + val players = remember(context) { + mapOf("" to defaultPlayerName) + VideoClickActionHolder.getPlayers() + .associate { player -> + player.uniqueId() to (player.name.asStringNull(context) + ?: player::class.simpleName ?: player::class.jvmName) + } + } + + val playerSeekTime by settings.player.doubleTapTime.collectAsState() + val tvSeekOnTime by settings.player.tvSeekOnTime.collectAsState() + val tvSeekOffTime by settings.player.tvSeekOffTime.collectAsState() + + var cacheSize by remember { mutableLongStateOf(0L) } + var cacheCleared by remember { mutableIntStateOf(0) } + val cacheDir = LocalContext.current.cacheDir + LaunchedEffect(cacheCleared) { + withContext(Dispatchers.IO) { + cacheSize = getFolderSize(cacheDir) + } + } + + return persistentListOf( + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_subtitles), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.player_subtitles_settings), + subtitle = stringResource(R.string.player_subtitles_settings_des), + icon = painterResource(R.drawable.subtitles_gear_24px), + onClick = { + SubtitlesFragment.push(activity, false) + }), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.chromecast_subtitles_settings), + subtitle = stringResource(R.string.chromecast_subtitles_settings_des), + icon = painterResource(R.drawable.cast), + onClick = { + ChromecastSubtitlesFragment.push(activity, false) + }), + ), + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_player_features), + preferenceItems = persistentListOf( + Preference.PreferenceItem.ListPreference( + title = stringResource(R.string.player_pref), + icon = painterResource(R.drawable.play_arrow_24px), + preference = settings.player.defaultPlayer, + entries = players, + ), + + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.source_priority), + subtitle = stringResource(R.string.source_priority_help), + icon = painterResource(R.drawable.ic_baseline_people_24), + onClick = { + ioSafe { + val defaultSources = QualityProfileDialog.getAllDefaultSources() + val activity = activity ?: return@ioSafe + activity.runOnUiThread { + QualityProfileDialog( + activity, + R.style.DialogFullscreenPlayer, + defaultSources, + ).show() + } + } + }), + + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.pipEnabled, + title = stringResource(R.string.picture_in_picture), + subtitle = stringResource(R.string.picture_in_picture_des), + icon = painterResource(R.drawable.pip_24px), + ), + + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.resizeEnabled, + title = stringResource(R.string.player_size_settings), + subtitle = stringResource(R.string.player_size_settings_des), + icon = painterResource(R.drawable.aspect_ratio_24px), + ), + + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.speedEnabled, + title = stringResource(R.string.eigengraumode_settings), + subtitle = stringResource(R.string.speed_setting_summary), + icon = painterResource(R.drawable.ic_baseline_speed_24), + ), + + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.tiktokEnabled, + title = stringResource(R.string.speedup_title), + subtitle = stringResource(R.string.speedup_summary), + icon = painterResource(R.drawable.speedup), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.autoPlayEnabled, + title = stringResource(R.string.autoplay_next_settings), + subtitle = stringResource(R.string.autoplay_next_settings_des), + icon = painterResource(R.drawable.skip_next_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.skipOpEnabled, + title = stringResource(R.string.video_skip_op), + subtitle = stringResource(R.string.enable_skip_op_from_database_des), + icon = painterResource(R.drawable.keyboard_double_arrow_right_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.autoRotateEnabled, + title = stringResource(R.string.auto_rotate_video), + subtitle = stringResource(R.string.auto_rotate_video_desc), + icon = painterResource(R.drawable.screen_rotation_alt_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.rotateButtonEnabled, + title = stringResource(R.string.rotate_video), + subtitle = stringResource(R.string.rotate_video_desc), + icon = painterResource(R.drawable.screen_rotation), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.previewBarEnabled, + enabled = isLayout(PHONE or EMULATOR), + title = stringResource(R.string.preview_seekbar), + subtitle = stringResource(R.string.preview_seekbar_desc), + icon = painterResource(R.drawable.picture_in_picture_center_24px), + ), + Preference.PreferenceItem.ListPreference( + preference = settings.player.softwareDecoding, + title = stringResource(R.string.software_decoding), + subtitle = stringResource(R.string.software_decoding_desc), + icon = painterResource(R.drawable.memory_24px), + entries = integerArrayResource(R.array.software_decoding_switch_values).zip( + stringArrayResource(R.array.software_decoding_switch) + ).toMap() + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.extraBrightnessEnabled, + title = stringResource(R.string.extra_brightness_settings), + subtitle = stringResource(R.string.extra_brightness_settings_des), + icon = painterResource(R.drawable.light_mode_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.episodeSync, + title = stringResource(R.string.episode_sync_settings), + subtitle = stringResource(R.string.episode_sync_settings_des), + icon = painterResource(R.drawable.autorenew_24px) + ), + ), + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_gestures), + preferenceItems = persistentListOf( + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.swipeHorizontalEnabled, + title = stringResource(R.string.swipe_to_seek_settings), + subtitle = stringResource(R.string.swipe_to_seek_settings_des), + icon = painterResource(R.drawable.swipe_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.swipeVerticalEnabled, + title = stringResource(R.string.swipe_to_change_settings), + subtitle = stringResource(R.string.swipe_to_change_settings_des), + icon = painterResource(R.drawable.swipe_vertical_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.doubleTapToSeekEnabled, + title = stringResource(R.string.double_tap_to_seek_settings), + subtitle = stringResource(R.string.double_tap_to_seek_settings_des), + icon = painterResource(R.drawable.touch_double_24px), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.doubleTapToPauseEnabled, + title = stringResource(R.string.double_tap_to_pause_settings), + subtitle = stringResource(R.string.double_tap_to_pause_settings_des), + icon = painterResource(R.drawable.touch_double_24px), + ), + + Preference.PreferenceItem.SliderPreference( + value = playerSeekTime, + title = stringResource(R.string.double_tap_to_seek_amount_settings), + icon = painterResource(R.drawable.go_forward_30), + valueRange = 5..60, + steps = 10, + onValueChanged = settings.player.doubleTapTime::set + ), + ) + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_android_tv), + enabled = isLayout(TV or EMULATOR), + preferenceItems = persistentListOf( + Preference.PreferenceItem.SliderPreference( + value = tvSeekOnTime, + title = stringResource(R.string.android_tv_interface_on_seek_settings), + subtitle = stringResource(R.string.android_tv_interface_on_seek_settings_summary), + icon = painterResource(R.drawable.go_forward_30), + valueRange = 5..60, + steps = 10, + onValueChanged = settings.player.tvSeekOnTime::set + ), + Preference.PreferenceItem.SliderPreference( + value = tvSeekOffTime, + title = stringResource(R.string.android_tv_interface_off_seek_settings), + subtitle = stringResource(R.string.android_tv_interface_off_seek_settings_summary), + icon = painterResource(R.drawable.go_forward_30), + valueRange = 5..60, + steps = 10, + onValueChanged = settings.player.tvSeekOffTime::set + ), + ) + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_player_layout), + preferenceItems = persistentListOf( + Preference.PreferenceItem.ListPreference( + preference = settings.player.limitPlayerTitle, + title = stringResource(R.string.limit_title), + icon = painterResource(R.drawable.match_word_24px), + entries = integerArrayResource(R.array.limit_title_pref_values).zip( + stringArrayResource(R.array.limit_title_pref_names) + ).toMap() + ), + Preference.PreferenceItem.SwitchPreference( + enabled = isLayout(PHONE or EMULATOR), + preference = settings.player.hidePlayerControlNames, + icon = painterResource(R.drawable.visibility_off_24px), + title = stringResource(R.string.hide_player_control_names), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.showName, + icon = painterResource(R.drawable.label_24px), + title = stringResource(R.string.source_name), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.showMediaInfo, + icon = painterResource(R.drawable.movie_info_24px), + title = stringResource(R.string.video_info), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.player.showResolution, + title = stringResource(R.string.resolution), + icon = painterResource(R.drawable.high_res_24px), + ), + + // Unsure if we want to have MultiSelectListPreference or boolean + /*Preference.PreferenceItem.MultiSelectListPreference( + preference = settings.player.showPlayerInfo, + title = stringResource(R.string.limit_title_rez), + icon = painterResource(R.drawable.ic_baseline_text_format_24), + entries = persistentMapOf( + ShowPlayerInfo.Name to stringResource(R.string.source_name), + ShowPlayerInfo.Resolution to stringResource(R.string.resolution), + ShowPlayerInfo.VideoInfo to stringResource(R.string.video_info), + ) + ), */ + ) + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_cache), + preferenceItems = persistentListOf( + Preference.PreferenceItem.ListPreference( + preference = settings.player.bufferDiskMB, + title = stringResource(R.string.video_buffer_disk_settings), + subtitle = "%s\n" + stringResource(R.string.video_disk_description), + icon = painterResource(R.drawable.hard_drive_24px), + entries = integerArrayResource(R.array.video_buffer_size_values).zip( + stringArrayResource(R.array.video_buffer_size_names) + ).toMap() + ), + + Preference.PreferenceItem.ListPreference( + preference = settings.player.bufferRamMB, + title = stringResource(R.string.video_buffer_size_settings), + subtitle = "%s\n" + stringResource(R.string.video_ram_description), + icon = painterResource(R.drawable.memory_alt_24px), + entries = integerArrayResource(R.array.video_buffer_size_values).zip( + stringArrayResource(R.array.video_buffer_size_names) + ).toMap() + ), + + Preference.PreferenceItem.ListPreference( + preference = settings.player.bufferTimeSec, + title = stringResource(R.string.video_buffer_length_settings), + subtitle = "%s\n" + stringResource(R.string.video_ram_description), + icon = painterResource(R.drawable.history_toggle_off_24px), + entries = integerArrayResource(R.array.video_buffer_length_values).zip( + stringArrayResource(R.array.video_buffer_length_names) + ).toMap() + ), + + Preference.PreferenceItem.TextPreference( + icon = painterResource(R.drawable.ic_baseline_delete_outline_24), + title = stringResource(R.string.video_buffer_clear_settings), + subtitle = formatShortFileSize(LocalContext.current, cacheSize), + onClick = { + ioSafe { + cacheDir.deleteRecursively() + cacheCleared += 1 + } + }) + ) + ), + + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index c8478a840..08b147f52 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -109,7 +109,8 @@ class SettingsProviders : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener { + getPref(R.string. + provider_lang_key)?.setOnPreferenceClickListener { activity?.getApiProviderLangSettings()?.let { currentLangTags -> val languagesTagName = APIHolder.apis.withLock { listOf(Pair(AllLanguagesName, getString(R.string.all_languages_preference))) + diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt new file mode 100644 index 000000000..adb44a94d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt @@ -0,0 +1,6 @@ +package com.lagradost.cloudstream3.ui.settings +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.mihon.presentation.settings.SearchableSettings + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsProviders2 : BaseComposeFragment(), SearchableSettings by SettingsProvidersScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt new file mode 100644 index 000000000..48c305a32 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt @@ -0,0 +1,88 @@ +package com.lagradost.cloudstream3.ui.settings + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.lagradost.cloudstream3.APIHolder +import com.lagradost.cloudstream3.AllLanguagesName +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName +import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji +import com.lagradost.cloudstream3.utils.UIHelper.navigate +import com.lagradost.cloudstream4.rememberAppSettings +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import kotlinx.collections.immutable.persistentListOf + +object SettingsProvidersScreen : SearchableSettings { + @Composable + override fun getTitleRes(): String = stringResource(R.string.category_providers) + + fun TvType.toStringRes() = when (this) { + TvType.TvSeries -> R.string.tv_series_singular + TvType.Anime -> R.string.anime_singular + TvType.OVA -> R.string.ova_singular + TvType.AnimeMovie -> R.string.movies_singular + TvType.Cartoon -> R.string.cartoons_singular + TvType.Documentary -> R.string.documentaries_singular + TvType.Movie -> R.string.movies_singular + TvType.Torrent -> R.string.torrent_singular + TvType.AsianDrama -> R.string.asian_drama_singular + TvType.Live -> R.string.live_singular + TvType.Others -> R.string.other_singular + TvType.NSFW -> R.string.nsfw_singular + TvType.Music -> R.string.music_singular + TvType.AudioBook -> R.string.audio_book_singular + TvType.CustomMedia -> R.string.custom_media_singular + TvType.Audio -> R.string.audio_singular + TvType.Podcast -> R.string.podcast_singular + TvType.Video -> R.string.video_singular + } + + // TODO Move this into general and layout? + @Composable + override fun getPreferences(): List { + val settings = rememberAppSettings() + + val default = AllLanguagesName to stringResource(R.string.all_languages_preference) + val languages = APIHolder.apis.withLock { + APIHolder.apis.map { api -> api.lang }.distinct() + }.sortedBy { fromTagToLanguageName(it) ?: it } + + return persistentListOf( + Preference.PreferenceItem.MultiSelectListPreference( + title = stringResource(R.string.provider_lang_settings), + icon = painterResource(R.drawable.plugin_lang), + entries = mapOf(default) + languages.associateWith { lang -> + (getNameNextToFlagEmoji( + lang + ) ?: lang) + }, + preference = settings.provider.extensionLanguages + ), Preference.PreferenceItem.MultiSelectListPreference( + title = stringResource(R.string.preferred_media_settings), + icon = painterResource(R.drawable.movie_edit_24px), + preference = settings.provider.preferredMedia, + entries = TvType.entries.associate { + it.ordinal.toString() to stringResource(it.toStringRes()) + }), Preference.PreferenceItem.MultiSelectListPreference( + title = stringResource(R.string.display_subbed_dubbed_settings), + icon = painterResource(R.drawable.ic_outline_voice_over_off_24), + preference = settings.provider.displayDubSub, + entries = mapOf( + DubStatus.None.name to stringResource(R.string.none), + DubStatus.Dubbed.name to stringResource(R.string.app_dubbed_text), + DubStatus.Subbed.name to stringResource(R.string.app_subbed_text), + ) + ), Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.test_extensions), + subtitle = stringResource(R.string.test_extensions_summary), + icon = painterResource(R.drawable.baseline_network_ping_24), + onClick = { + activity?.navigate(R.id.navigation_test_providers) + })) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt new file mode 100644 index 000000000..86f3371fa --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt @@ -0,0 +1,7 @@ +package com.lagradost.cloudstream3.ui.settings + +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.mihon.presentation.settings.SearchableSettings + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsUI2 : BaseComposeFragment(), SearchableSettings by SettingsUIScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt new file mode 100644 index 000000000..3e67ed15e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt @@ -0,0 +1,289 @@ +package com.lagradost.cloudstream3.ui.settings + +import android.os.Build +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.integerArrayResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.MainActivity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.ui.clear +import com.lagradost.cloudstream3.ui.home.HomeChildItemAdapter +import com.lagradost.cloudstream3.ui.home.ParentItemAdapter +import com.lagradost.cloudstream3.ui.search.SearchAdapter +import com.lagradost.cloudstream3.ui.settings.Globals.updateTv +import com.lagradost.cloudstream3.utils.UIHelper.toPx +import com.lagradost.cloudstream4.compose.TV +import com.lagradost.cloudstream4.compose.isLayout +import com.lagradost.cloudstream4.rememberAppSettings +import com.lagradost.cloudstream4.theme.CloudStreamPrimaryColor +import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors +import com.lagradost.cloudstream4.theme.modeToTheme +import com.lagradost.cloudstream4.theme.perfToColor +import com.lagradost.cloudstream4.theme.perfToMode +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import com.mihon.presentation.settings.collectAsState +import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentMap + +object SettingsUIScreen : SearchableSettings { + @Composable + override fun getTitleRes(): String = stringResource(R.string.category_ui) + + fun SearchQuality.toStringRes() = when (this) { + SearchQuality.BlueRay -> R.string.quality_blueray + SearchQuality.Cam -> R.string.quality_cam + SearchQuality.CamRip -> R.string.quality_cam_rip + SearchQuality.DVD -> R.string.quality_dvd + SearchQuality.HD -> R.string.quality_hd + SearchQuality.HQ -> R.string.quality_hq + SearchQuality.HdCam -> R.string.quality_cam_hd + SearchQuality.Telecine -> R.string.quality_tc + SearchQuality.Telesync -> R.string.quality_ts + SearchQuality.WorkPrint -> R.string.quality_workprint + SearchQuality.SD -> R.string.quality_sd + SearchQuality.FourK -> R.string.quality_4k + SearchQuality.UHD -> R.string.quality_uhd + SearchQuality.SDR -> R.string.quality_sdr + SearchQuality.HDR -> R.string.quality_hdr + SearchQuality.WebRip -> R.string.quality_webrip + } + + @Composable + fun RoundColor(color: Color) { + Box( + modifier = Modifier + .padding(start = 15.dp) + .size(20.dp) + .border(width = 1.5.dp, shape = CircleShape, color = colors.onBackground) + .background(color, CircleShape) + ) + } + + @Composable + override fun getPreferences(): List { + val settings = rememberAppSettings() + + val overscanDp by settings.ui.overscanDp.collectAsState() + val posterSize by settings.ui.posterSize.collectAsState() + + return persistentListOf( + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_looks), + preferenceItems = persistentListOf( + Preference.PreferenceItem.ListPreference( + preference = settings.ui.primaryColor, + icon = painterResource(R.drawable.colors_24px), + title = stringResource(R.string.primary_color_settings), + entries = stringArrayResource(R.array.themes_overlay_names_values).zip( + stringArrayResource(R.array.themes_overlay_names) + ).toMap().toPersistentMap().mutate { map -> + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // remove monet on android 11 and less + map.remove("Monet") + map.remove("Monet2") + } + }, + iconProvider = { k, _ -> + val color = perfToColor(k) + RoundColor(color.color) + }, + onValueChanged = { newValue -> + settings.ui.primaryColor.set(newValue) // We need to set before we recreate + activity?.recreate() + return@ListPreference true + }), + Preference.PreferenceItem.ListPreference( + preference = settings.ui.theme, + icon = painterResource(R.drawable.palette_24px), + title = stringResource(R.string.app_theme_settings), + entries = stringArrayResource(R.array.themes_names_values).zip( + stringArrayResource(R.array.themes_names) + ).toMap().toPersistentMap().mutate { map -> + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // remove monet on android 11 and less + map.remove("Monet") + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // Remove system on android 9 and less + map.remove("System") + } + }, + iconProvider = { k, _ -> + val theme = modeToTheme(perfToMode(k), CloudStreamPrimaryColor.NORMAL) + RoundColor(theme.background) + }, + onValueChanged = { newValue -> + settings.ui.theme.set(newValue) // We need to set before we recreate + activity?.recreate() + return@ListPreference true + }), + Preference.PreferenceItem.ListPreference( + preference = settings.ui.layout, + icon = painterResource(R.drawable.responsive_layout_24px), + title = stringResource(R.string.app_layout), + entries = integerArrayResource(R.array.app_layout_values).zip( + stringArrayResource(R.array.app_layout) + ).toMap().toPersistentMap(), + onValueChanged = { newValue -> + settings.ui.layout.set(newValue) // We need to set before we recreate + activity?.updateTv() + activity?.recreate() + return@ListPreference true + }), + ) + ), + + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_ui_features), + preferenceItems = persistentListOf( + Preference.PreferenceItem.SliderPreference( + value = overscanDp, + title = stringResource(R.string.overscan_settings), + subtitle = stringResource(R.string.overscan_settings_des), + valueRange = 0..100, + icon = painterResource(R.drawable.arrows_input_24px), + enabled = isLayout(TV), + onValueChanged = { newValue -> + settings.ui.overscanDp.set(newValue) + val padding = newValue.toPx + (activity as? MainActivity)?.binding?.homeRoot?.setPadding( + padding, padding, padding, padding + ) + }), + + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.advancedSearch, + title = stringResource(R.string.advanced_search), + subtitle = stringResource(R.string.advanced_search_des), + icon = painterResource(R.drawable.search_icon) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.searchSuggestions, + title = stringResource(R.string.search_suggestions), + subtitle = stringResource(R.string.search_suggestions_des), + icon = painterResource(R.drawable.tooltip_24px) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.trailersEnabled, + title = stringResource(R.string.show_trailers_settings), + icon = painterResource(R.drawable.baseline_theaters_24) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.kitsuPostersEnabled, + title = stringResource(R.string.kitsu_settings), + icon = painterResource(R.drawable.kitsu_icon) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.castEnabled, + title = stringResource(R.string.show_cast_in_details), + icon = painterResource(R.drawable.face_24px) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.fillersEnabled, + title = stringResource(R.string.show_fillers_settings), + icon = painterResource(R.drawable.skip_next_24px) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.showMetadataOverlay, + title = stringResource(R.string.show_player_metadata_overlay), + icon = painterResource(R.drawable.metadata_overlay_icon) + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.showClock, + title = stringResource(R.string.tv_layout_clock_settings), + subtitle = stringResource(R.string.tv_layout_clock_settings_des), + icon = painterResource(R.drawable.ic_baseline_clock_24), + enabled = isLayout(TV), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.randomButtonEnabled, + title = stringResource(R.string.random_button_settings), + subtitle = stringResource(R.string.random_button_settings_desc), + icon = painterResource(R.drawable.shuffle_24px) + ), + Preference.PreferenceItem.ListPreference( + preference = settings.ui.confirmExit, + title = stringResource(R.string.confirm_before_exiting_title), + subtitle = stringResource(R.string.confirm_before_exiting_desc), + icon = painterResource(R.drawable.ic_baseline_exit_24), + entries = integerArrayResource(R.array.confirm_exit_values).zip( + stringArrayResource(R.array.confirm_exit) + ).toMap() + ), + ) + ), + + Preference.PreferenceGroup( + title = stringResource(R.string.search_poster_img_des), + preferenceItems = persistentListOf( + Preference.PreferenceItem.MultiSelectListPreference( + preference = settings.ui.filterQuality, + title = stringResource(R.string.pref_filter_search_quality), + icon = painterResource(R.drawable.filter_alt_24px), + entries = SearchQuality.entries.associateWith { stringResource(it.toStringRes()) }), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.bottomTitle, + title = stringResource(R.string.bottom_title_settings), + subtitle = stringResource(R.string.bottom_title_settings_des), + icon = painterResource(R.drawable.title_24px) + ), + Preference.PreferenceItem.SliderPreference( + value = posterSize, + title = stringResource(R.string.poster_size_settings), + subtitle = stringResource(R.string.poster_size_settings_des), + valueRange = 0..15, + icon = painterResource(R.drawable.baseline_grid_view_24), + onValueChanged = { newValue -> + HomeChildItemAdapter.sharedPool.clear() + ParentItemAdapter.sharedPool.clear() + SearchAdapter.sharedPool.clear() + activity?.let { HomeChildItemAdapter.updatePosterSize(it, newValue) } + settings.ui.posterSize.set(newValue) + }), + ), + ), + Preference.PreferenceGroup( + title = stringResource(R.string.poster_ui_settings), + preferenceItems = persistentListOf( + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.posterShowRating, + title = stringResource(R.string.show_rating), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.posterShowEpisode, + title = stringResource(R.string.show_episode_text), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.posterShowTitle, + title = stringResource(R.string.show_title), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.posterShowHd, + title = stringResource(R.string.show_hd), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.posterShowDub, + title = stringResource(R.string.show_dub), + ), + Preference.PreferenceItem.SwitchPreference( + preference = settings.ui.posterShowSub, + title = stringResource(R.string.show_sub), + ), + ) + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt new file mode 100644 index 000000000..50070a384 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt @@ -0,0 +1,7 @@ +package com.lagradost.cloudstream3.ui.settings + +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.mihon.presentation.settings.SearchableSettings + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsUpdates2 : BaseComposeFragment(), SearchableSettings by SettingsUpdatesScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt new file mode 100644 index 000000000..dc03174ff --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt @@ -0,0 +1,222 @@ +package com.lagradost.cloudstream3.ui.settings + +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.integerArrayResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.utils.BackupUtils +import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.InAppUpdater.installPreReleaseIfNeeded +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate +import com.lagradost.cloudstream3.utils.UIHelper.navigate +import com.lagradost.cloudstream4.AppSettings +import com.lagradost.cloudstream4.rememberAppSettings +import com.lagradost.safefile.SafeFile +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import com.mihon.presentation.settings.collectAsState +import kotlinx.collections.immutable.persistentListOf + +object SettingsUpdatesScreen : SearchableSettings { + @Composable + override fun getTitleRes(): String = stringResource(R.string.category_updates) + + @Composable + override fun getPreferences(): List { + val settings = rememberAppSettings() + + // TODO Refactor entirely to use a different file path selector ect like QuickNovel + val selectFileSelector = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> + // It lies, it can be null if file manager quits. + if (uri == null) return@rememberLauncherForActivityResult + val context = CloudStreamApp.context ?: return@rememberLauncherForActivityResult + + try { + val settings = AppSettings(context) + // RW perms for the path + val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + context.contentResolver.takePersistableUriPermission(uri, flags) + + val filePath = SafeFile.fromUri(context, uri)?.filePath() + println("Selected URI path: $uri - Full path: $filePath") + + // store the actual URI instead of the path due to permissions. + // filePath should only be used for cosmetic purposes. + val visual = filePath ?: uri.toString() + settings.backup.path.set(uri.toString()) + settings.backup.visualPath.set(visual) + } catch (t: Throwable) { + logError(t) + } + } + + val visualBackupPath by settings.backup.visualPath.collectAsState() + var showDialog by remember { mutableStateOf(false) } + + if (showDialog) { + com.lagradost.cloudstream3.ui.settings.logcat.LogcatDialog { + showDialog = false + } + } + + return persistentListOf( + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_app_updates), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.check_for_update), + subtitle = BuildConfig.VERSION_NAME, + icon = painterResource(R.drawable.mobile_arrow_down_24px), + onClick = { + ioSafe { + if (activity?.runAutoUpdate(false) == false) { + activity?.runOnUiThread { + showToast( + R.string.no_update_found, + Toast.LENGTH_SHORT + ) + } + } + } + } + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.install_prerelease), + icon = painterResource(R.drawable.mobile_code_24px), + enabled = BuildConfig.FLAVOR == "stable", + onClick = { + activity?.installPreReleaseIfNeeded() + } + ), + + Preference.PreferenceItem.ListPreference( + title = stringResource(R.string.apk_installer_settings), + subtitle = stringResource(R.string.apk_installer_settings_des), + icon = painterResource(R.drawable.mobile_wrench_24px), + entries = integerArrayResource(R.array.apk_installer_values).zip( + stringArrayResource(R.array.apk_installer_pref) + ).toMap(), + preference = settings.updates.apkInstaller + ), + + Preference.PreferenceItem.SwitchPreference( + title = stringResource(R.string.updates_settings), + subtitle = stringResource(R.string.updates_settings_des), + icon = painterResource(R.drawable.notifications_active_24px), + preference = settings.updates.showAppUpdates + ) + ) + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_backup), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.backup_settings), + icon = painterResource(R.drawable.save_as_24px), + onClick = { + BackupUtils.backup(activity) + } + ), + Preference.PreferenceItem.ListPreference( + title = stringResource(R.string.backup_frequency), + icon = painterResource(R.drawable.save_clock_24px), + entries = integerArrayResource(R.array.periodic_work_values).zip( + stringArrayResource(R.array.periodic_work_names) + ).toMap(), + preference = settings.backup.frequency + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.backup_path_title), + icon = painterResource(R.drawable.folder_24px), + subtitle = visualBackupPath, + onClick = { + // This is not a ListPreference because the old selection system is + // broken af. This needs to be refactored to QuickNovels download path + // system. + selectFileSelector.launch(Uri.EMPTY) + } + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.restore_settings), + icon = painterResource(R.drawable.restore_page_24px), + onClick = { + activity?.restorePrompt() + } + ), + ) + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_extensions), + preferenceItems = persistentListOf( + Preference.PreferenceItem.SwitchPreference( + title = stringResource(R.string.automatic_plugin_updates), + icon = painterResource(R.drawable.extension_24px), + preference = settings.plugins.autoUpdate, + ), + + Preference.PreferenceItem.ListPreference( + title = stringResource(R.string.automatic_plugin_download), + subtitle = "%s\n"+stringResource(R.string.automatic_plugin_download_summary), + icon = painterResource(R.drawable.extention_renew2), + entries = AutoDownloadMode.entries.map { it.value }.sorted() + .zip(stringArrayResource((R.array.auto_download_plugin))).toMap(), + preference = settings.plugins.autoDownload + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.update_plugins), + subtitle = stringResource(R.string.update_plugins_manually), + icon = painterResource(R.drawable.extention_download), + onClick = { + ioSafe { + PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_manuallyReloadAndUpdatePlugins( + activity ?: return@ioSafe + ) + } + } + ), + ) + ), + Preference.PreferenceGroup( + title = stringResource(R.string.pref_category_actions), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.show_log_cat), + icon = painterResource(R.drawable.article_24px), + onClick = { + showDialog = true + } + ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.redo_setup_process), + icon = painterResource(R.drawable.construction_24px), + onClick = { + activity?.navigate(R.id.navigation_setup_language) + } + ), + ) + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt new file mode 100644 index 000000000..a157a0cda --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt @@ -0,0 +1,258 @@ +package com.lagradost.cloudstream3.ui.settings.logcat + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.DialogProperties +import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper +import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager +import com.lagradost.cloudstream3.utils.txt +import com.lagradost.cloudstream4.compose.Colors +import com.lagradost.cloudstream4.compose.circle +import com.lagradost.cloudstream4.compose.ripple +import com.lagradost.cloudstream4.compose.rounded +import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.OutputStream +import java.lang.System.currentTimeMillis +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + + +@Composable +fun LogcatDialog(dismiss: () -> Unit) { + + val list = remember { mutableStateOf(persistentListOf()) } + LaunchedEffect(dismiss) { + try { + // https://developer.android.com/studio/command-line/logcat + val process = Runtime.getRuntime().exec("logcat --binary -d") + val items = arrayListOf() + LogcatBinaryParser(process.inputStream).use { parser -> + while (true) { + val item = parser.parseItem() ?: break + items.add(item) + } + } + + list.value = items.toPersistentList() + } catch (e: Exception) { + logError(e) // kinda ironic + } + } + + val context = LocalContext.current + val scope = rememberCoroutineScope() + AlertDialog( + containerColor = colors.background, + onDismissRequest = dismiss, + title = { + Text(text = stringResource(R.string.log_cat)) + }, + text = { + LazyColumn { + items(items = list.value) { item -> + LogcatItem(item) + } + } + }, + confirmButton = { + Button( + onClick = { + scope.launch { + withContext(Dispatchers.IO) { + val date = SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.getDefault()).format(Date(currentTimeMillis())) + var fileStream: OutputStream? + try { + fileStream = VideoDownloadManager.setupStream( + context, + "logcat_${date}", + null, + "txt", + false + ).openNew() + fileStream.bufferedWriter() + .use { writer -> + list.value.forEach { + writer.write(it.toString()) + writer.write("\n\n") + } + } + dismiss() + } catch (t: Throwable) { + logError(t) + showToast(t.message) + } + /*try { + val date = SimpleDateFormat( + "yyyy_MM_dd_HH_mm", + Locale.getDefault() + ).format( + Date(System.currentTimeMillis()) + ) + + val file = FileHelper.logcat.createFile(context, "logcat_${date}") + ?: throw ErrorLoadingException("Unable to create file") + val stream = file.openOutputStream(append = false) + ?: throw ErrorLoadingException("Unable to create stream") + + stream.bufferedWriter() + .use { writer -> + list.value.forEach { + writer.write(it.toString()) + writer.write("\n\n") + } + } + dismiss() + showToast( + txt( + R.string.logcat_success, + file.absolutePath ?: file.uri.toString() + ), + Toast.LENGTH_LONG + ) + } catch (t: Throwable) { + logError(t) + showToast(t.message) + }*/ + } + } + }, colors = Colors.whiteButton + ) { Text(text = stringResource(R.string.sort_save)) } + Button( + onClick = { + clipboardHelper( + txt("Logcat"), + list.value.joinToString(separator = "\n\n") { it.toString() } + ) + }, colors = Colors.whiteButton + ) { Text(text = stringResource(R.string.sort_copy)) } + Button( + onClick = { + try { + Runtime.getRuntime().exec("logcat -c") + } catch (t: Throwable) { + logError(t) + } + dismiss() + }, colors = Colors.whiteButton + ) { Text(text = stringResource(R.string.sort_clear)) } + }, + dismissButton = { + Button( + onClick = dismiss, colors = Colors.blackButton + ) { Text(text = stringResource(R.string.sort_close)) } + }, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) +} + + +@Composable +fun LogcatItem(item: LogcatItem) { + val interactionSource = remember { MutableInteractionSource() } + + val color = when (item.level) { + LogcatLevel.Fatal -> Color.Magenta + LogcatLevel.Error -> Color.Red + LogcatLevel.Warning -> Color.Yellow + LogcatLevel.Info -> Color.White + LogcatLevel.Debug -> Color.Green + LogcatLevel.Verbose -> Color.Gray + null -> Color.Transparent + } + + Row(modifier = Modifier.fillMaxWidth()) { + item.level?.identifier?.let { value -> + Text( + value, + modifier = Modifier + .padding(2.dp) + .rounded() + .background(colors.onBackground) + .padding(4.dp), + color = colors.surfaceVariant + ) + } + Text( + item.date.toHumanReadable(), + modifier = Modifier + .padding(2.dp) + .rounded() + .background(colors.surfaceVariant) + .padding(4.dp), + color = colors.onBackground + ) + Text( + item.tag, + modifier = Modifier + .padding(2.dp) + .rounded() + .background(colors.surfaceVariant) + .padding(4.dp), + color = colors.onBackground + ) + } + Row( + modifier = Modifier + .height(IntrinsicSize.Min) + .fillMaxWidth() + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { + clipboardHelper(txt("Logcat"), item.toString()) + }) + .rounded() + .ripple(interactionSource) + .padding(5.dp) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .width(4.dp) + .circle() + .background(color) + ) + Spacer(modifier = Modifier.width(5.dp)) + Text( + item.message, + color = colors.onBackground, + fontSize = 14.sp, + lineHeight = 15.sp, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt new file mode 100644 index 000000000..df1dc7077 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt @@ -0,0 +1,140 @@ +package com.lagradost.cloudstream3.ui.settings.logcat + +import androidx.compose.runtime.Immutable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.Closeable +import java.io.InputStream +import kotlin.time.Instant +import kotlinx.io.Buffer +import kotlinx.io.asSource +import kotlinx.io.readByteArray +import kotlinx.io.readIntLe +import kotlinx.io.readString +import kotlinx.io.readUShortLe +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +fun Instant.toHumanReadable(): String { + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).apply { + timeZone = TimeZone.getDefault() + } + return formatter.format(Date(this.toEpochMilliseconds())) +} +@Immutable +data class LogcatItem( + val date: Instant, + val pid: Int, + val tid: Int, + val level: LogcatLevel?, + val tag: String, + val message: String, +) { + override fun toString(): String { + return "${date.toHumanReadable()} $pid-$tid $tag ${level?.identifier ?: "?"} $message" + } +} + +enum class LogcatLevel(val identifier: String) { + Fatal("WTF"), + Error("E"), + Warning("W"), + Info("I"), + Debug("D"), + Verbose("V"), +} + +/**https://github.com/brudaswen/android-logcat/blob/main/library/logcat-core/src/main/kotlin/de/brudaswen/android/logcat/core/parser/LogcatBinaryParser.kt */ +class LogcatBinaryParser( + private val input: InputStream, +) : Closeable by input { + val source = input.asSource() + private val buffer = Buffer() + + /** + * Parse one [LogcatItem] from the current [input] stream. + * + * @return The parsed [LogcatItem] or `null` if stream reached EOF. + */ + suspend fun parseItem(): LogcatItem? = withContext(Dispatchers.IO) { + val firstByte = input.read() + if (firstByte == -1) return@withContext null + + // Read v1 header + buffer.writeByte(firstByte.toByte()) + buffer.write(source = source, byteCount = 19) + + val len = buffer.readUShortLe().toInt() + val headerSize = buffer.readUShortLe().toInt() + val pid = buffer.readIntLe() + val tid = buffer.readIntLe() + val sec = buffer.readIntLe() + val nsec = buffer.readIntLe() + + // Read additional header fields + buffer.write(source = source, byteCount = headerSize - 20L) + + val additionalHeaderBytes = (headerSize - 20).coerceAtLeast(0) + buffer.readByteArray(byteCount = additionalHeaderBytes) + + // Read payload + buffer.write(source = source, byteCount = len.toLong()) + + val priority = buffer.readByte() + + val payload = buffer.readString() + val texts = payload.split('\u0000', limit = 2) + val tag = texts.getOrNull(0).orEmpty() + val message = texts.getOrNull(1).orEmpty().removeSuffix("\u0000").trim() + + // Clear buffer + buffer.clear() + + // Convert raw values to item + LogcatItem( + sec = sec, + nsec = nsec, + priority = priority, + pid = pid, + tid = tid, + tag = tag, + message = message, + ) + } + + private fun LogcatItem( + sec: Int, + nsec: Int, + priority: Byte, + pid: Int, + tid: Int, + tag: String, + message: String, + ): LogcatItem { + val date = Instant.fromEpochSeconds( + epochSeconds = sec.toLong(), + nanosecondAdjustment = nsec, + ) + + val level = when (priority) { + 2.toByte() -> LogcatLevel.Verbose + 3.toByte() -> LogcatLevel.Debug + 4.toByte() -> LogcatLevel.Info + 5.toByte() -> LogcatLevel.Warning + 6.toByte() -> LogcatLevel.Error + 7.toByte() -> LogcatLevel.Fatal + else -> null + } + + return LogcatItem( + date = date, + pid = pid, + tid = tid, + level = level, + tag = tag, + message = message, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 8d322fd04..e0661721a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -86,6 +86,7 @@ import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.downloader.DownloadObjects +import com.lagradost.cloudstream4.AppSettings import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import okhttp3.Cache @@ -481,14 +482,14 @@ object AppContextUtils { fun Context.filterSearchResultByFilmQuality(data: List): List { // Filter results omitting entries with certain quality if (data.isNotEmpty()) { - val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this) + val filteredSearchQuality = AppSettings(this).ui.filterQuality.get() /*PreferenceManager.getDefaultSharedPreferences(this) ?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf()) ?.mapNotNull { entry -> entry.toIntOrNull() ?: return@mapNotNull null - } ?: listOf() + } ?: listOf()*/ if (filteredSearchQuality.isNotEmpty()) { return data.filter { item -> - val searchQualVal = item.quality?.ordinal ?: -1 + val searchQualVal = item.quality //?.ordinal ?: -1 //Log.i("filterSearch", "QuickSearch item => ${item.toJson()}") !filteredSearchQuality.contains(searchQualVal) } @@ -500,17 +501,17 @@ object AppContextUtils { fun Context.filterHomePageListByFilmQuality(data: HomePageList): HomePageList { // Filter results omitting entries with certain quality if (data.list.isNotEmpty()) { - val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this) + val filteredSearchQuality = AppSettings(this).ui.filterQuality.get() /*PreferenceManager.getDefaultSharedPreferences(this) ?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf()) ?.mapNotNull { entry -> entry.toIntOrNull() ?: return@mapNotNull null - } ?: listOf() + } ?: listOf()*/ if (filteredSearchQuality.isNotEmpty()) { return HomePageList( name = data.name, isHorizontalImages = data.isHorizontalImages, list = data.list.filter { item -> - val searchQualVal = item.quality?.ordinal ?: -1 + val searchQualVal = item.quality //?.ordinal ?: -1 //Log.i("filterSearch", "QuickSearch item => ${item.toJson()}") !filteredSearchQuality.contains(searchQualVal) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 0a561b471..d0c79c43c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.app.Activity import android.content.Context import android.net.Uri import android.widget.Toast @@ -282,7 +283,7 @@ object BackupUtils { } } - fun FragmentActivity.restorePrompt() { + fun Activity.restorePrompt() { runOnUiThread { try { restoreFileSelector?.launch( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt new file mode 100644 index 000000000..3c3f55adb --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt @@ -0,0 +1,43 @@ +package com.lagradost.cloudstream3.utils + +import android.content.res.Configuration +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLandscape +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding +import com.lagradost.cloudstream4.compose.Screen +import com.lagradost.cloudstream4.compose.createComposeView + +abstract class BaseComposeFragment : Fragment(), Screen { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = createComposeView(inflater, container, savedInstanceState) + + override fun onConfigurationChanged(newConfig: Configuration) { + this.view?.let { view -> + fixSystemBarsPadding( + view, + padLeft = isLayout(TV or EMULATOR), + padBottom = isLandscape() + ) + } + super.onConfigurationChanged(newConfig) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + fixSystemBarsPadding( + view, + padLeft = isLayout(TV or EMULATOR), + padBottom = isLandscape() + ) + super.onViewCreated(view, savedInstanceState) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt index e3c7d68df..af3795834 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt @@ -63,7 +63,7 @@ object BatteryOptimizationChecker { return isRestricted && isOptimizedNotShown && isLayout(PHONE) } - private fun Context.showRequestIgnoreBatteryOptDialog() { + fun Context.showRequestIgnoreBatteryOptDialog() { try { val intent = Intent().apply { action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt index df9d56217..687f746e3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.AnimeLoadResponse import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvSeriesLoadResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.app @@ -25,6 +26,7 @@ import java.security.MessageDigest class AnimeSkipAuth : AuthAPI() { override val name = "AnimeSkip" + override val icon: Int = R.drawable.animeskip override val inAppLoginRequirement: AuthLoginRequirement = AuthLoginRequirement(password = true, username = true) override val idPrefix = "anime-skip" diff --git a/app/src/main/res/drawable/article_24px.xml b/app/src/main/res/drawable/article_24px.xml new file mode 100644 index 000000000..5b0f45801 --- /dev/null +++ b/app/src/main/res/drawable/article_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/aspect_ratio_24px.xml b/app/src/main/res/drawable/aspect_ratio_24px.xml new file mode 100644 index 000000000..429ed7237 --- /dev/null +++ b/app/src/main/res/drawable/aspect_ratio_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/autorenew_24px.xml b/app/src/main/res/drawable/autorenew_24px.xml new file mode 100644 index 000000000..4ac69c08f --- /dev/null +++ b/app/src/main/res/drawable/autorenew_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/battery_alert_24px.xml b/app/src/main/res/drawable/battery_alert_24px.xml new file mode 100644 index 000000000..3675de44d --- /dev/null +++ b/app/src/main/res/drawable/battery_alert_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/cast.xml b/app/src/main/res/drawable/cast.xml new file mode 100644 index 000000000..7600cb706 --- /dev/null +++ b/app/src/main/res/drawable/cast.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/colors_24px.xml b/app/src/main/res/drawable/colors_24px.xml new file mode 100644 index 000000000..d86304cf9 --- /dev/null +++ b/app/src/main/res/drawable/colors_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/construction_24px.xml b/app/src/main/res/drawable/construction_24px.xml new file mode 100644 index 000000000..00c976c90 --- /dev/null +++ b/app/src/main/res/drawable/construction_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/copy_all_24px.xml b/app/src/main/res/drawable/copy_all_24px.xml new file mode 100644 index 000000000..8e4db679b --- /dev/null +++ b/app/src/main/res/drawable/copy_all_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/dns_24px.xml b/app/src/main/res/drawable/dns_24px.xml new file mode 100644 index 000000000..2f9b4c8bf --- /dev/null +++ b/app/src/main/res/drawable/dns_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/extension_24px.xml b/app/src/main/res/drawable/extension_24px.xml new file mode 100644 index 000000000..ce2cfc260 --- /dev/null +++ b/app/src/main/res/drawable/extension_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/extention_download.xml b/app/src/main/res/drawable/extention_download.xml new file mode 100644 index 000000000..97f3dbd67 --- /dev/null +++ b/app/src/main/res/drawable/extention_download.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/extention_renew.xml b/app/src/main/res/drawable/extention_renew.xml new file mode 100644 index 000000000..a9e6e07f9 --- /dev/null +++ b/app/src/main/res/drawable/extention_renew.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/extention_renew2.xml b/app/src/main/res/drawable/extention_renew2.xml new file mode 100644 index 000000000..d34e91355 --- /dev/null +++ b/app/src/main/res/drawable/extention_renew2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/face_24px.xml b/app/src/main/res/drawable/face_24px.xml new file mode 100644 index 000000000..152c278e7 --- /dev/null +++ b/app/src/main/res/drawable/face_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/filter_alt_24px.xml b/app/src/main/res/drawable/filter_alt_24px.xml new file mode 100644 index 000000000..0afa9b51d --- /dev/null +++ b/app/src/main/res/drawable/filter_alt_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/folder_24px.xml b/app/src/main/res/drawable/folder_24px.xml new file mode 100644 index 000000000..a1e978b69 --- /dev/null +++ b/app/src/main/res/drawable/folder_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/hard_drive_24px.xml b/app/src/main/res/drawable/hard_drive_24px.xml new file mode 100644 index 000000000..45fecb4d2 --- /dev/null +++ b/app/src/main/res/drawable/hard_drive_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/high_res_24px.xml b/app/src/main/res/drawable/high_res_24px.xml new file mode 100644 index 000000000..183f2ae69 --- /dev/null +++ b/app/src/main/res/drawable/high_res_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/history_toggle_off_24px.xml b/app/src/main/res/drawable/history_toggle_off_24px.xml new file mode 100644 index 000000000..30186016c --- /dev/null +++ b/app/src/main/res/drawable/history_toggle_off_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml b/app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml new file mode 100644 index 000000000..2e564cea4 --- /dev/null +++ b/app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/label_24px.xml b/app/src/main/res/drawable/label_24px.xml new file mode 100644 index 000000000..fb1d1b506 --- /dev/null +++ b/app/src/main/res/drawable/label_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/language_korean_latin_24px.xml b/app/src/main/res/drawable/language_korean_latin_24px.xml new file mode 100644 index 000000000..55bf0cf21 --- /dev/null +++ b/app/src/main/res/drawable/language_korean_latin_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/light_mode_24px.xml b/app/src/main/res/drawable/light_mode_24px.xml new file mode 100644 index 000000000..8d278ec46 --- /dev/null +++ b/app/src/main/res/drawable/light_mode_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/match_word_24px.xml b/app/src/main/res/drawable/match_word_24px.xml new file mode 100644 index 000000000..d2a7334f8 --- /dev/null +++ b/app/src/main/res/drawable/match_word_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/memory_24px.xml b/app/src/main/res/drawable/memory_24px.xml new file mode 100644 index 000000000..7e4dd5e9b --- /dev/null +++ b/app/src/main/res/drawable/memory_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/memory_alt_24px.xml b/app/src/main/res/drawable/memory_alt_24px.xml new file mode 100644 index 000000000..21807a9e9 --- /dev/null +++ b/app/src/main/res/drawable/memory_alt_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/mobile_arrow_down_24px.xml b/app/src/main/res/drawable/mobile_arrow_down_24px.xml new file mode 100644 index 000000000..caab6850e --- /dev/null +++ b/app/src/main/res/drawable/mobile_arrow_down_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/mobile_code_24px.xml b/app/src/main/res/drawable/mobile_code_24px.xml new file mode 100644 index 000000000..9b78af094 --- /dev/null +++ b/app/src/main/res/drawable/mobile_code_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/mobile_wrench_24px.xml b/app/src/main/res/drawable/mobile_wrench_24px.xml new file mode 100644 index 000000000..5cc975528 --- /dev/null +++ b/app/src/main/res/drawable/mobile_wrench_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/movie_edit_24px.xml b/app/src/main/res/drawable/movie_edit_24px.xml new file mode 100644 index 000000000..41df964e6 --- /dev/null +++ b/app/src/main/res/drawable/movie_edit_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/movie_info_24px.xml b/app/src/main/res/drawable/movie_info_24px.xml new file mode 100644 index 000000000..7d7dfe9e9 --- /dev/null +++ b/app/src/main/res/drawable/movie_info_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/notifications_active_24px.xml b/app/src/main/res/drawable/notifications_active_24px.xml new file mode 100644 index 000000000..b3982c522 --- /dev/null +++ b/app/src/main/res/drawable/notifications_active_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/palette_24px.xml b/app/src/main/res/drawable/palette_24px.xml new file mode 100644 index 000000000..f701027cc --- /dev/null +++ b/app/src/main/res/drawable/palette_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/picture_in_picture_center_24px.xml b/app/src/main/res/drawable/picture_in_picture_center_24px.xml new file mode 100644 index 000000000..60d7e133b --- /dev/null +++ b/app/src/main/res/drawable/picture_in_picture_center_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/pip_24px.xml b/app/src/main/res/drawable/pip_24px.xml new file mode 100644 index 000000000..5fba5b85e --- /dev/null +++ b/app/src/main/res/drawable/pip_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/play_arrow_24px.xml b/app/src/main/res/drawable/play_arrow_24px.xml new file mode 100644 index 000000000..b7b67fccc --- /dev/null +++ b/app/src/main/res/drawable/play_arrow_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/plugin_lang.xml b/app/src/main/res/drawable/plugin_lang.xml new file mode 100644 index 000000000..ac33489ac --- /dev/null +++ b/app/src/main/res/drawable/plugin_lang.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/quick_novel_icon.xml b/app/src/main/res/drawable/quick_novel_icon.xml index b19aaafea..8f0b3cf3e 100644 --- a/app/src/main/res/drawable/quick_novel_icon.xml +++ b/app/src/main/res/drawable/quick_novel_icon.xml @@ -1,31 +1,23 @@ - - - - - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="1000" + android:viewportHeight="1000"> + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/drawable/responsive_layout_24px.xml b/app/src/main/res/drawable/responsive_layout_24px.xml new file mode 100644 index 000000000..4d3ea085f --- /dev/null +++ b/app/src/main/res/drawable/responsive_layout_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/restore_page_24px.xml b/app/src/main/res/drawable/restore_page_24px.xml new file mode 100644 index 000000000..65a7d274f --- /dev/null +++ b/app/src/main/res/drawable/restore_page_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/save_as_24px.xml b/app/src/main/res/drawable/save_as_24px.xml new file mode 100644 index 000000000..0c3c01414 --- /dev/null +++ b/app/src/main/res/drawable/save_as_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/save_clock_24px.xml b/app/src/main/res/drawable/save_clock_24px.xml new file mode 100644 index 000000000..c86924cc1 --- /dev/null +++ b/app/src/main/res/drawable/save_clock_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/screen_rotation_alt_24px.xml b/app/src/main/res/drawable/screen_rotation_alt_24px.xml new file mode 100644 index 000000000..a1d01a5c6 --- /dev/null +++ b/app/src/main/res/drawable/screen_rotation_alt_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/shuffle_24px.xml b/app/src/main/res/drawable/shuffle_24px.xml new file mode 100644 index 000000000..d25985f8b --- /dev/null +++ b/app/src/main/res/drawable/shuffle_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/skip_next_24px.xml b/app/src/main/res/drawable/skip_next_24px.xml new file mode 100644 index 000000000..3beb9c760 --- /dev/null +++ b/app/src/main/res/drawable/skip_next_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/subtitles_gear_24px.xml b/app/src/main/res/drawable/subtitles_gear_24px.xml new file mode 100644 index 000000000..d84e9f883 --- /dev/null +++ b/app/src/main/res/drawable/subtitles_gear_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/swipe_24px.xml b/app/src/main/res/drawable/swipe_24px.xml new file mode 100644 index 000000000..9b2183713 --- /dev/null +++ b/app/src/main/res/drawable/swipe_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/swipe_vertical_24px.xml b/app/src/main/res/drawable/swipe_vertical_24px.xml new file mode 100644 index 000000000..48048f334 --- /dev/null +++ b/app/src/main/res/drawable/swipe_vertical_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/tooltip_24px.xml b/app/src/main/res/drawable/tooltip_24px.xml new file mode 100644 index 000000000..5770d07b4 --- /dev/null +++ b/app/src/main/res/drawable/tooltip_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/touch_double_24px.xml b/app/src/main/res/drawable/touch_double_24px.xml new file mode 100644 index 000000000..f008bd374 --- /dev/null +++ b/app/src/main/res/drawable/touch_double_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/visibility_off_24px.xml b/app/src/main/res/drawable/visibility_off_24px.xml new file mode 100644 index 000000000..50ba19a57 --- /dev/null +++ b/app/src/main/res/drawable/visibility_off_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/wifi_proxy_24px.xml b/app/src/main/res/drawable/wifi_proxy_24px.xml new file mode 100644 index 000000000..92798c29c --- /dev/null +++ b/app/src/main/res/drawable/wifi_proxy_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 27f186a00..970850882 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -137,7 +137,7 @@ Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. - In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + \n\nIn case of copyright infringement, please directly contact the responsible parties or the streaming websites. - The app is purely for educational and personal use. + \n\nThe app is purely for educational and personal use. - CloudStream does not host any content on the app, and has no control over what media is put up or taken down. + \n\nCloudStream does not host any content on the app, and has no control over what media is put up or taken down. CloudStream functions like any other search engine, such as Google. CloudStream does not host, upload or - manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + manage any videos, films or content. It simply crawls, aggregates and displays links in a convenient, user-friendly interface. - It merely scrapes 3rd-party websites that are publicly accessible via any regular web browser. It is the + \n\nIt merely scrapes 3rd-party websites that are publicly accessible via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream at your own risk. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d530b59f..87e447ccb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,6 +139,7 @@ No Plot Found No Description Found Show Logcat 🐈 + Logcat 🐈 Log Picture-in-picture Continues playback in a miniature player on top of other apps @@ -458,8 +459,8 @@ Random Coming soon… Cam - Cam - Cam + CamRip + CamHD HQ HD TS diff --git a/build.gradle.kts b/build.gradle.kts index f58fb584f..dc98269b5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,4 +7,6 @@ plugins { alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.compose.multiplatform) apply false } diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts new file mode 100644 index 000000000..0008a854d --- /dev/null +++ b/desktopApp/build.gradle.kts @@ -0,0 +1,55 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.compose.compiler) +} + +kotlin { + jvm {} + sourceSets { + jvmMain.dependencies { + implementation(libs.bundles.compose) + implementation(compose.desktop.currentOs) { + // compose.desktop.currentOs imports the wrong material 2, so we exclude it + exclude(group = "org.jetbrains.compose.material", module = "material") + } + implementation(project(":shared")) + } + } +} + +// java.lang.System::load has been called by org.jetbrains.skiko.LibraryLoader in an unnamed module +tasks.withType { + jvmArgs("--enable-native-access=ALL-UNNAMED") +} + +compose.desktop { + application { + mainClass = "com.lagradost.cloudstream4.MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "CloudStream" + packageVersion = "1.0.0" + + val iconsRoot = project.file("desktop-icons") + macOS { + // iconFile.set(iconsRoot.resolve("icon-mac.icns")) + } + windows { + iconFile.set(iconsRoot.resolve("icon-windows.ico")) + // menuGroup = "Compose Examples" + // see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html + // upgradeUuid = "" + } + linux { + iconFile.set(iconsRoot.resolve("icon-linux.png")) + } + } + + //buildTypes.release.proguard { + // configurationFiles.from(project.file("rules.pro")) + //} + } +} \ No newline at end of file diff --git a/desktopApp/src/desktop-icons/icon-linux.png b/desktopApp/src/desktop-icons/icon-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..394459001fdb5237f04624560e66550660044d05 GIT binary patch literal 8334 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 literal 0 HcmV?d00001 diff --git a/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt b/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt new file mode 100644 index 000000000..f40cc9f8b --- /dev/null +++ b/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt @@ -0,0 +1,59 @@ +package com.lagradost.cloudstream4 + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Button +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import com.lagradost.cloudstream4.compose.Colors +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.app_name +import com.lagradost.cloudstream4.generated.resources.default_icon +import com.lagradost.cloudstream4.generated.resources.preview +import com.lagradost.cloudstream4.theme.CloudStreamTheme +import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors +import com.lagradost.cloudstream4.theme.CloudStreamThemeMode +import com.mihon.presentation.settings.widget.SwitchPreferenceWidget +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = stringResource(Res.string.app_name), + icon = painterResource(Res.drawable.default_icon) + ) { + CloudStreamTheme(mode = CloudStreamThemeMode.Dark) { + Scaffold( + containerColor = colors.background, + contentColor = colors.onBackground + ) { + Column { + Text("Hello, World!") + Row { + Button(onClick = {}, colors = Colors.whiteButton) { + Text("Hello in White") + } + Button(onClick = {}, colors = Colors.blackButton) { + Text("Hello in Black") + } + } + var checked by remember { mutableStateOf(false) } + SwitchPreferenceWidget( + title = "hello", subtitle = "world", icon = painterResource( + Res.drawable.preview + ), + checked = checked, + onCheckedChanged = { checked = !checked } + ) + } + } + } + } +} \ No newline at end of file diff --git a/desktopApp/src/readme.md b/desktopApp/src/readme.md new file mode 100644 index 000000000..0bebb8638 --- /dev/null +++ b/desktopApp/src/readme.md @@ -0,0 +1 @@ +Based on https://github.com/JetBrains/compose-multiplatform/tree/master/examples/imageviewer/desktopApp \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e38895451..4652cf938 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ # https://docs.gradle.org/current/userguide/plugins.html#sec:version_catalog_plugin_application # https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version [versions] -activityKtx = "1.13.0" +activity = "1.13.0" androidGradlePlugin = "9.1.1" animeDb = "1.0.2" annotation = "1.10.0" @@ -55,8 +55,14 @@ tvprovider = "1.1.0" video = "1.0.0" workRuntimeKtx = "2.11.2" zipline = "1.27.0" +composeMaterial3 = "1.12.0-alpha03" +composeMultiplatform = "1.12.0" +lifecycleViewmodelCompose = "2.11.0" -jvmTarget = "1.8" +# jvmTarget = "11" is needed for compose, otherwise you will get +# "Cannot inline bytecode built with JVM target 11 into bytecode" or +# "Error was captured in composition. java.lang.AbstractMethodError" +jvmTarget = "11" jdkToolchain = "17" minSdk = "23" compileSdk = "37" @@ -66,7 +72,8 @@ versionCode = "68" versionName = "4.8.0" [libraries] -activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } +activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } +activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity" } anime-db = { module = "com.github.recloudstream:anime-db", version.ref = "animeDb" } annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -136,6 +143,16 @@ video = { module = "com.google.android.mediahome:video", version.ref = "video" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } zipline = { module = "app.cash.zipline:zipline-android", version.ref = "zipline" } +compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" } +compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMaterial3" } +compose-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" } +compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" } +compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" } +compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" } +compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" } +lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } +coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } + [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" } @@ -146,6 +163,9 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm" , version.ref = "kotlinGradlePlug kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlinGradlePlugin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinGradlePlugin" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinGradlePlugin" } +compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } + [bundles] coil = ["coil", "coil-network-okhttp"] cryptography = ["cryptography-core", "cryptography-provider-optimal"] @@ -153,3 +173,4 @@ lifecycle = ["lifecycle-livedata-ktx", "lifecycle-viewmodel-ktx"] media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"] navigation = ["navigation-fragment-ktx", "navigation-ui-ktx"] nextlib = ["nextlib-media3ext", "nextlib-mediainfo"] +compose = ["compose-foundation", "compose-material3", "compose-resources", "compose-runtime", "compose-ui", "compose-ui-tooling", "compose-ui-tooling-preview", "lifecycle-viewmodel-compose", "coil-compose"] \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 73bf5a195..dd92448b0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,4 +18,4 @@ dependencyResolutionManagement { } rootProject.name = "CloudStream" -include(":app", ":library", ":docs") +include(":app", ":shared", ":library", ":docs", ":desktopApp") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts new file mode 100644 index 000000000..1b7f0f29e --- /dev/null +++ b/shared/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.lint) + alias(libs.plugins.android.multiplatform.library) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.compose.compiler) +} + +kotlin { + android { + // Must be unique + namespace = "com.lagradost.cloudstream4" + compileSdk = libs.versions.compileSdk.get().toInt() + minSdk = libs.versions.minSdk.get().toInt() + + androidResources { + enable = true + } + } + + jvm() + + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + + sourceSets { + all { + languageSettings { + optIn("com.lagradost.cloudstream3.InternalAPI") + optIn("com.lagradost.cloudstream3.Prerelease") + } + } + + commonMain.dependencies { + implementation(libs.bundles.compose) + implementation(project(":library")) + } + + androidMain.dependencies { + implementation(libs.activity.compose) + implementation(libs.preference.ktx) + } + } +} + +dependencies { + androidRuntimeClasspath(libs.compose.ui.tooling) +} + +compose.resources { + publicResClass = true + packageOfResClass = "com.lagradost.cloudstream4.generated.resources" + generateResClass = auto +} \ No newline at end of file diff --git a/shared/readme.md b/shared/readme.md new file mode 100644 index 000000000..084244cf3 --- /dev/null +++ b/shared/readme.md @@ -0,0 +1,112 @@ +## Motivation + +This readme is documentation for humans to understand ***why***, and ***not how*** the core architecture +for CloudStream will be designed going forward. + +## Design decisions + +The following design decisions has been tried in the sister project QuickNovel with great success, and a +seamless porting process. For inspiration and code in a similar codebase, take a look at QuickNovel. + +### File structure + +The `shared` directory is supposed to be for shared compose UI between platforms, and can act as a glue for business logic. +This is separate from the `library` that includes the primitives for a headless, cross-platform plugin system. +Note that we do not want a shared screen layout, because each platform has its own design language and quirks. +Instead, the ViewModels should be entirely detached from the rendering process, and each UI component should be shared +between every platform. This ensures ViewModels cant cheat with dirty escape hatches, a leaky api or +with UI elements that hijack business logic. + +**Include**: +*Viewmodels*, *UI components* and *Theming* + +**Avoid**: +*Entire Screens*, *Plugin APIs* and *Android Layout XML* + +Please also note that while it would be nice with an e.g. cross-platform downloader or video player. +It is simply infeasible and undesirable to write such a system. Therefore, each platform should have +a seperate implemenation of platform specific behavior that closly interop with native systems. + +### MVI + +MVI (Model View Intent) is the core structure for building cross-platform UI we will use. +The reason for this decision is that current MVVM architecture created a lot of boilerplate, +and is very unwieldy when working with compose. We will instead use a single immutable UI state, using +kotlin immutable collections, as that creates thread-safe and performant UI. Additionally, because the viewmodel +is shared between platforms, the Viewmodel must be provided with an implementation specific +database/preference/downloaded by using dependency injection and composition. + +**Use**: +*PersistentList*, *val*, *dependency injection* and *composition* + +**Avoid**: +*List*, *var*, *globals* and *inheritance* + +Please note that this process aims to replace the current UI in a backwards compatible way. Only +the navigation graph should change to point to the new fragments. Then we can roll back if some +critical issue is found with the compose rewrite, as well as compare functionality without a checkout. + +### Resources and assets + +**Images**: For this project the accepted image format is `XML Drawable resources` instead of +`ImageVector` or `.svg`. This is primarily due to make the review, cross-platform, and edit +processing easier. ImageVector is undesirable because file must be reviewed to not contain malicious +code, and does not have a built-in preview. SVG files on the other hand are currently not natively +supported on Android. + +**Translation**: CloudStream already has a huge translation, however to make the porting process easier, +we should limit any copy-paste from the android project until everything is converted to compose. +This is in part because weblate is not set up target the cross-platform compose yet. + +**Styling/Themes**: In compose there is no `styles.xml`/`colors.xml`/`dimens.xml`. Therefore, +styling is done entierly in code, but should use the exact same colors to avoid a styling mismatch +when porting. + +**UI/UX**: The intended experience when using CloudStream should be the same, but the UI should be +refreshed to a more Material 3 style, such as rounded corners and a greater focus on light theme +support. However, no glassmorphic, blurred or gradient UI is accepted. + +### Settings and Mihon + +The settings system has been entirely forked from Tachiyomi, now Mihon project. This is because +Mihon has one of the best FOSS codebases for Android, and has a great declarative preference system +unmatched by any other tested Compose settings system. Unfortunately, this is not provided as a +library and is therefore instead forked by importing the exact files used. + +### Why compose? + +During the lifetime of CloudStream, the app has been redesigned and rewritten several times. + +**C# Downloader → Xamarin Native → Xamarin Cross platform → Android Native XML → ViewBinding → Compose Multiplatform** + +We have experimented with different UI technologies such as **Blazor**, **Electron**, **PWA**, +**WPF**, **UWP**, **Windows Forms**, and **CLI/Console** for CloudStream. +We have also investigated projectes written in **Flutter** and **React Native**. + +However, none of these frameworks provided a good developer experience, that was also performant and +cross-platform. Compose was the only real option, but did not exist until after we started on +CloudStream 3. Android Native XML was simply the least bad option when building an Android app. + +### CloudStream 4? + +The current android app is named CloudStream 3, and this Compose rewrite will be named +CloudStream 4. However, the android app will be upgraded, not replaced with Compose. It aims to be a +seamless and gradual rollout of updates for everyone using the Android app. Therefore, CloudStream 4 +is used to refer to the new compose UI, features, and cross-platform support. + +## AI Usage + +Due to the prevalence and popularity of LLMs, CloudStream 3 has been rewritten entirely 4+ times by +independent actors using Compose to target desktop with different LLM models. These have not been merged, due to the +absolute mess of the AI generated codebase, both in code quality and performance. + +Therefore, I (LagradOst) will not accept or allow any pull requests concering the Compose rewrite +before I put the core structures are in place. The compose rewrite is supposed to be a refactor and +quality improvement for everyone involved, to solve the massive tech debt we already have. XML and +old plans forced bad design decissions, which we can improve greatly upon. However, in no part do I +want AI to lay the groundwork for something this important. + +The idea is to provide a solid staring point so it is easy for first time contributors and LLMs to +understand and work with the code, not ship a product as fast as possible. This is not a project +with only one person involved. A messy AI generated codebase makes reviews and contributions very hard +for everyone involved. diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt new file mode 100644 index 000000000..04ba9dd36 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt @@ -0,0 +1,19 @@ +package com.lagradost.cloudstream4 + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.mihon.common.preference.AndroidPreferenceStore + + +@Composable +actual fun rememberAppSettings() : AppSettings { + val context = LocalContext.current + val value = remember(context) { AppSettings(context) } + return value +} + +fun AppSettings(context: Context) : AppSettings { + return AppSettings(preferences = AndroidPreferenceStore(context)) +} diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/BackHandler.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/BackHandler.kt new file mode 100644 index 000000000..8f9d096c4 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/BackHandler.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream4.compose + +import androidx.activity.OnBackPressedCallback +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.lifecycle.compose.LocalLifecycleOwner + +/** https://dev.to/pawegio/handling-back-presses-in-jetpack-compose-50d5 */ +@Composable +fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { + object : OnBackPressedCallback(enabled) { + override fun handleOnBackPressed() { + currentOnBack() + } + } + } + // On every successful composition, update the callback with the `enabled` value + SideEffect { + backCallback.isEnabled = enabled + } + val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.onBackPressedDispatcher + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner, backDispatcher) { + // Add callback to the backDispatcher + backDispatcher.addCallback(lifecycleOwner, backCallback) + // When the effect leaves the Composition, remove the callback + onDispose { + backCallback.remove() + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt new file mode 100644 index 000000000..9a91e3e16 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt @@ -0,0 +1,51 @@ +package com.lagradost.cloudstream4.compose + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.ViewCompositionStrategy +import com.lagradost.cloudstream4.rememberAppSettings +import com.lagradost.cloudstream4.theme.CloudStreamTheme +import com.lagradost.cloudstream4.theme.perfToColor +import com.lagradost.cloudstream4.theme.perfToMode +import com.mihon.presentation.LocalBackPress +import com.mihon.presentation.settings.collectAsState + +/** Backwards compatible fragment for compose, before we switch entirely to compose navigation */ +fun Screen.createComposeView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, +): View = ComposeView(inflater.context).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + + setContent { + val settings = rememberAppSettings() + val mode by settings.ui.theme.collectAsState() + val primaryColor by settings.ui.primaryColor.collectAsState() + val layout by settings.ui.layout.collectAsState() + val layoutFlag = DeviceLayout.layoutToFlag(LocalContext.current, layout) + + CloudStreamTheme( + mode = perfToMode(mode), + primaryColor = perfToColor(primaryColor), + ) { + val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.onBackPressedDispatcher + + CompositionLocalProvider( + LocalBackPress provides backDispatcher::onBackPressed, + DeviceLayout.LocalLayout provides layoutFlag + ) { + this@createComposeView.Content() + } + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt new file mode 100644 index 000000000..96838ae5e --- /dev/null +++ b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt @@ -0,0 +1,48 @@ +package com.lagradost.cloudstream4.compose + +import android.app.UiModeManager +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.staticCompositionLocalOf +import com.lagradost.cloudstream4.compose.DeviceLayout.Companion.LocalLayout + +@JvmInline +@Immutable +value class DeviceLayout(private val value: Int) { + infix fun or(other: DeviceLayout) = + DeviceLayout(value or other.value) + + internal infix fun and(other: DeviceLayout) = (value and other.value) != 0 + + companion object { + val LocalLayout: ProvidableCompositionLocal = staticCompositionLocalOf { DeviceLayout(-1) } + fun isAutoTv(context: Context): Boolean { + val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager? + // AFT = Fire TV + val model = Build.MODEL.lowercase() + return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Build.MODEL.contains( + "AFT" + ) || model.contains("firestick") || model.contains("fire tv") || model.contains("chromecast") + } + internal fun layoutToFlag(context : Context, from : Int): DeviceLayout { + return when(from) { + -1 -> if (isAutoTv(context)) TV else PHONE + 0 -> PHONE + 1 -> TV + 2 -> EMULATOR + else -> PHONE + } + } + } +} + +val PHONE = DeviceLayout(0b00001) +val TV = DeviceLayout(0b00010) +val EMULATOR = DeviceLayout(0b00100) + +@Composable +fun isLayout(flags: DeviceLayout) : Boolean = LocalLayout.current and flags \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt new file mode 100644 index 000000000..17d715372 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt @@ -0,0 +1,72 @@ +package com.lagradost.cloudstream4.theme + +import android.content.Context +import android.os.Build +import androidx.preference.PreferenceManager + +/** TODO: This should be replaced with a better system */ + +fun perfToMode(perf: String?) = + when (perf) { + "System" -> CloudStreamThemeMode.FollowSystem + "Black" -> CloudStreamThemeMode.Dark + "Light" -> CloudStreamThemeMode.Light + "Amoled" -> CloudStreamThemeMode.Amoled + "AmoledLight" -> CloudStreamThemeMode.AmoledLight + "Dracula" -> CloudStreamThemeMode.Dracula + "Lavender" -> CloudStreamThemeMode.Lavender + "SilentBlue" -> CloudStreamThemeMode.SilentBlue + "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + CloudStreamThemeMode.Dynamic + } else { + CloudStreamThemeMode.Dark + } + + else -> CloudStreamThemeMode.Dark + } + +fun perfToColor(perf : String?) = when (perf) { + "Normal" -> CloudStreamPrimaryColor.NORMAL + "Blue" -> CloudStreamPrimaryColor.BLUE + "Purple" -> CloudStreamPrimaryColor.PURPLE + "Green" -> CloudStreamPrimaryColor.GREEN + "GreenApple" -> CloudStreamPrimaryColor.GREEN_APPLE + "Red" -> CloudStreamPrimaryColor.RED + "Banana" -> CloudStreamPrimaryColor.BANANA + "Party" -> CloudStreamPrimaryColor.PARTY + "Pink" -> CloudStreamPrimaryColor.PINK + "CarnationPink" -> CloudStreamPrimaryColor.CARNATION_PINK + "Maroon" -> CloudStreamPrimaryColor.MAROON + "DarkGreen" -> CloudStreamPrimaryColor.DARK_GREEN + "NavyBlue" -> CloudStreamPrimaryColor.NAVY_BLUE + "Grey" -> CloudStreamPrimaryColor.GREY + "White" -> CloudStreamPrimaryColor.WHITE + "Brown" -> CloudStreamPrimaryColor.BROWN + "Orange" -> CloudStreamPrimaryColor.ORANGE + "DandelionYellow" -> CloudStreamPrimaryColor.DANDELION_YELLOW + "CoolBlue" -> CloudStreamPrimaryColor.COOL_BLUE + "Lavender" -> CloudStreamPrimaryColor.LAVENDER + "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + CloudStreamPrimaryColor.DYNAMIC + } else { + CloudStreamPrimaryColor.NORMAL + } + + "Monet2" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + CloudStreamPrimaryColor.DYNAMIC_TWO + } else { + CloudStreamPrimaryColor.NORMAL + } + + else -> CloudStreamPrimaryColor.NORMAL +} + +fun Context.loadThemeMode(): CloudStreamThemeMode { + val prefs = PreferenceManager.getDefaultSharedPreferences(this) + return perfToMode(prefs.getString("theme_key", "AmoledLight")) +} + +fun Context.loadPrimaryColor(): CloudStreamPrimaryColor { + val prefs = PreferenceManager.getDefaultSharedPreferences(this) + return perfToColor(prefs.getString("primary_color_key", "Normal")) +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt new file mode 100644 index 000000000..73b188c25 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt @@ -0,0 +1,73 @@ +package com.lagradost.cloudstream4.theme + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource + +internal actual object DeviceTheme { + @Composable + @ReadOnlyComposable + @RequiresApi(Build.VERSION_CODES.S) + private fun buildMonetScheme(): CloudStreamColorScheme { + return if (true) { // isSystemInDarkTheme() disabled until everything is Compose, as otherwise you get bugs + CloudStreamColorScheme( + background = colorResource(android.R.color.system_neutral1_900), + surfaceVariant = colorResource(android.R.color.system_neutral1_800), + surface = colorResource(android.R.color.system_neutral1_800), + surfaceContainer = colorResource(android.R.color.system_neutral1_800), + onBackground = colorResource(android.R.color.system_neutral1_100), + onSurfaceVariant = colorResource(android.R.color.system_neutral2_400), + icon = colorResource(android.R.color.system_neutral1_100), + primary = colorResource(android.R.color.system_accent1_200), + ongoing = CloudStreamPalette.Ongoing, + isLight = false, + ) + } else { + CloudStreamColorScheme( + background = colorResource(android.R.color.system_neutral1_10), + surfaceVariant = colorResource(android.R.color.system_neutral1_100), + surface = colorResource(android.R.color.system_neutral1_100), + surfaceContainer = colorResource(android.R.color.system_neutral1_100), + onBackground = colorResource(android.R.color.system_neutral1_900), + onSurfaceVariant = colorResource(android.R.color.system_neutral2_600), + icon = colorResource(android.R.color.system_neutral1_900), + primary = colorResource(android.R.color.system_accent1_600), + ongoing = CloudStreamPalette.Ongoing, + isLight = true, + ) + } + } + + @Composable + @ReadOnlyComposable + actual fun resolveDynamicTheme(): CloudStreamColorScheme { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + buildMonetScheme() + } else { + darkScheme() + } + } + + @Composable + @ReadOnlyComposable + actual fun resolveDynamicPrimaryColor(): Color { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + colorResource(android.R.color.system_accent1_200) + } else { + CloudStreamPrimaryColor.NORMAL.color + } + } + + @Composable + @ReadOnlyComposable + actual fun resolveDynamicSecondaryColor(): Color { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + colorResource(android.R.color.system_accent2_200) + } else { + CloudStreamPrimaryColor.NORMAL.color + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt new file mode 100644 index 000000000..b766dd4b3 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt @@ -0,0 +1,235 @@ +package com.mihon.common.preference + +import android.content.SharedPreferences +import android.content.SharedPreferences.Editor +import androidx.core.content.edit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +sealed class AndroidPreference( + private val preferences: SharedPreferences, + private val keyFlow: Flow, + private val key: String, + private val defaultValue: T, +) : PreferenceData { + + abstract fun read(preferences: SharedPreferences, key: String, defaultValue: T): T + + abstract fun write(key: String, value: T): Editor.() -> Unit + + override fun key(): String { + return key + } + + override fun get(): T { + return try { + read(preferences, key, defaultValue) + } catch (e: ClassCastException) { + //logcat { "Invalid value for $key; deleting" } + delete() + defaultValue + } + } + + override fun set(value: T) { + preferences.edit(action = write(key, value)) + } + + override fun isSet(): Boolean { + return preferences.contains(key) + } + + override fun delete() { + preferences.edit { + remove(key) + } + } + + override fun defaultValue(): T { + return defaultValue + } + + override fun changes(): Flow { + return keyFlow + .filter { it == key || it == null } + .onStart { emit("ignition") } + .map { get() } + .conflate() + } + + override fun stateIn(scope: CoroutineScope): StateFlow { + return changes().stateIn(scope, SharingStarted.Eagerly, get()) + } + + class StringPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: String, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read( + preferences: SharedPreferences, + key: String, + defaultValue: String, + ): String { + return preferences.getString(key, defaultValue) ?: defaultValue + } + + override fun write(key: String, value: String): Editor.() -> Unit = { + putString(key, value) + } + } + + class LongPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Long, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long { + return preferences.getLong(key, defaultValue) + } + + override fun write(key: String, value: Long): Editor.() -> Unit = { + putLong(key, value) + } + } + + class IntPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Int, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int { + return preferences.getInt(key, defaultValue) + } + + override fun write(key: String, value: Int): Editor.() -> Unit = { + putInt(key, value) + } + } + + class FloatPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Float, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float { + return preferences.getFloat(key, defaultValue) + } + + override fun write(key: String, value: Float): Editor.() -> Unit = { + putFloat(key, value) + } + } + + class BooleanPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Boolean, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read( + preferences: SharedPreferences, + key: String, + defaultValue: Boolean, + ): Boolean { + return preferences.getBoolean(key, defaultValue) + } + + override fun write(key: String, value: Boolean): Editor.() -> Unit = { + putBoolean(key, value) + } + } + + class StringSetPrimitive( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Set, + ) : AndroidPreference>(preferences, keyFlow, key, defaultValue) { + override fun read( + preferences: SharedPreferences, + key: String, + defaultValue: Set, + ): Set { + return preferences.getStringSet(key, defaultValue) ?: defaultValue + } + + override fun write(key: String, value: Set): Editor.() -> Unit = { + putStringSet(key, value) + } + } + + class ObjectAsString( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: T, + private val serializer: (T) -> String, + private val deserializer: (String) -> T, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T { + return try { + preferences.getString(key, null)?.let(deserializer) ?: defaultValue + } catch (e: Exception) { + defaultValue + } + } + + override fun write(key: String, value: T): Editor.() -> Unit = { + putString(key, serializer(value)) + } + } + + class ObjectAsInt( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: T, + private val serializer: (T) -> Int, + private val deserializer: (Int) -> T, + ) : AndroidPreference(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T { + return try { + if (preferences.contains(key)) preferences.getInt(key, 0).let(deserializer) else defaultValue + } catch (e: Exception) { + defaultValue + } + } + + override fun write(key: String, value: T): Editor.() -> Unit = { + putInt(key, serializer(value)) + } + } + + class ObjectSetAsStringSet( + preferences: SharedPreferences, + keyFlow: Flow, + key: String, + defaultValue: Set, + private val serializer: (T) -> String, + private val deserializer: (String) -> T?, + ) : AndroidPreference>(preferences, keyFlow, key, defaultValue) { + override fun read(preferences: SharedPreferences, key: String, defaultValue: Set): Set { + return try { + preferences.getStringSet(key, null)?.mapNotNull(deserializer)?.toSet() ?: defaultValue + } catch (_: Exception) { + defaultValue + } + } + + override fun write(key: String, value: Set): Editor.() -> Unit = { + putStringSet(key, value.map(serializer).toSet()) + } + } +} diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt new file mode 100644 index 000000000..192bc77ed --- /dev/null +++ b/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt @@ -0,0 +1,102 @@ +package com.mihon.common.preference + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow + +class AndroidPreferenceStore(private val sharedPreferences: SharedPreferences) : PreferenceStore { + constructor(context: Context) : this(PreferenceManager.getDefaultSharedPreferences(context)) + + private val keyFlow = sharedPreferences.keyFlow + + override fun getString(key: String, defaultValue: String): PreferenceData { + return AndroidPreference.StringPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getLong(key: String, defaultValue: Long): PreferenceData { + return AndroidPreference.LongPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getInt(key: String, defaultValue: Int): PreferenceData { + return AndroidPreference.IntPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getFloat(key: String, defaultValue: Float): PreferenceData { + return AndroidPreference.FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getBoolean(key: String, defaultValue: Boolean): PreferenceData { + return AndroidPreference.BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getStringSet(key: String, defaultValue: Set): PreferenceData> { + return AndroidPreference.StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue) + } + + override fun getObjectFromString( + key: String, + defaultValue: T, + serializer: (T) -> String, + deserializer: (String) -> T, + ): PreferenceData { + return AndroidPreference.ObjectAsString( + preferences = sharedPreferences, + keyFlow = keyFlow, + key = key, + defaultValue = defaultValue, + serializer = serializer, + deserializer = deserializer, + ) + } + + override fun getObjectFromInt( + key: String, + defaultValue: T, + serializer: (T) -> Int, + deserializer: (Int) -> T, + ): PreferenceData { + return AndroidPreference.ObjectAsInt( + preferences = sharedPreferences, + keyFlow = keyFlow, + key = key, + defaultValue = defaultValue, + serializer = serializer, + deserializer = deserializer, + ) + } + + override fun getObjectSetFromStringSet( + key: String, + defaultValue: Set, + serializer: (T) -> String, + deserializer: (String) -> T?, + ): PreferenceData> { + return AndroidPreference.ObjectSetAsStringSet( + preferences = sharedPreferences, + keyFlow = keyFlow, + key = key, + defaultValue = defaultValue, + serializer = serializer, + deserializer = deserializer, + ) + } + + override fun getAll(): Map { + return sharedPreferences.all ?: emptyMap() + } +} + +private val SharedPreferences.keyFlow + get() = callbackFlow { + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? -> + trySend( + key, + ) + } + registerOnSharedPreferenceChangeListener(listener) + awaitClose { + unregisterOnSharedPreferenceChangeListener(listener) + } + } \ No newline at end of file diff --git a/shared/src/commonMain/composeResources/drawable/arrow_back.xml b/shared/src/commonMain/composeResources/drawable/arrow_back.xml new file mode 100644 index 000000000..196b4c087 --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/arrow_back.xml @@ -0,0 +1,10 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/arrow_downward.xml b/shared/src/commonMain/composeResources/drawable/arrow_downward.xml new file mode 100644 index 000000000..061a6db1b --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/arrow_downward.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/arrow_upward.xml b/shared/src/commonMain/composeResources/drawable/arrow_upward.xml new file mode 100644 index 000000000..fbf89a796 --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/arrow_upward.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/cancel.xml b/shared/src/commonMain/composeResources/drawable/cancel.xml new file mode 100644 index 000000000..570986990 --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/cancel.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/check.xml b/shared/src/commonMain/composeResources/drawable/check.xml new file mode 100644 index 000000000..6976b0df2 --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/check.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/close.xml b/shared/src/commonMain/composeResources/drawable/close.xml new file mode 100644 index 000000000..14beedf9b --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/close.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/default_icon.png b/shared/src/commonMain/composeResources/drawable/default_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..394459001fdb5237f04624560e66550660044d05 GIT binary patch literal 8334 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 + + diff --git a/shared/src/commonMain/composeResources/drawable/info.xml b/shared/src/commonMain/composeResources/drawable/info.xml new file mode 100644 index 000000000..32d30b88a --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/info.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/drawable/preview.xml b/shared/src/commonMain/composeResources/drawable/preview.xml new file mode 100644 index 000000000..d56fbde6e --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/preview.xml @@ -0,0 +1,9 @@ + + + diff --git a/shared/src/commonMain/composeResources/font/productsans_black.ttf b/shared/src/commonMain/composeResources/font/productsans_black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5864e2690837d17e7d01f323345e821eb96d3fa6 GIT binary patch literal 91660 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≤gS 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

X*Z`O$j36P^=p+8tIZ#B$0JU!)9i35SLug3 ze63+i+7)uf+dU<6lwKE*<{ziKypildJ>453M)AK}K znJ%P15nmvy$rvr@B{j3QKypCu`K4%BOuN@HqxzmX(cE{a!ZOmjgXr|Y( z(o9OP3ubhWq(1%q{NjD5A9~fATA?LR(V7DPBbkMzNT|;b7xH|}*AZ_wop!3b>Kzex(C+d(oqm%(^zitw*&J%~``f}6I!h~SNOTy1_Xs#wc>;*mVY}kAb6a-e6HIy6>1S{~t6MWu{6Wo`N#k4zX>@jJ#n$l^>sm#%lj{RW zcwWG?Z(-b$L!`6p+94)Ym9aDA+ZZ%#XO*^n+DZ0$+7nKn%WAhpTRfB%Y_}qYHIelg z-A=RF=`vY^L8~ngI6WD58U1Omuf@=iw7HXhqtTyq+ma2279Xs?#y{B1CcDjSwtd)b zv$^SKF@KMus*uFry;B9i+tw$?IMM!h)UtB>lXPRo9km?xw_J4cP$EgfpO!vd5`*i^ z7UJ-kI-Shn*#PHopZD~HVZSc1xZC6M2T#G{Vp(33o?$@6MOnEZ=MKVToq2O^;n~A} zK6FKBWb;BzLJW3K`HQbFIzDj^E}fMz+yV?=j2hY+IUh=WAV6}tjD-hB4M_xktB3sq zah&$>!rsN$J4}Wat=*=##WKF^xNeQoYb8YhwhybLp^vE+9{q3 zjWn}M^P!tFc8zm^k_Pl)y1la69U?P9eX zb2x{iYMjG4sFQZ{!jb+Xli7IJ-5WkLtywUjS0;b+=fRP-ioC?li=#C+9l zWYMl)<7gr}joOVmB-t4h8(lO?9V52V{xGmc1@w9|3qPRU9^10iM?#_^> zEo!eiwW@g+_7vH}(153a z$xuJIVp)$ZR_^T0NEPRMr4;w+fESG%fz{Lknt2&*a?$8!JlTXR;I=tI5Vr@;2B*y( za3eQ(EoM)-r>|tb22A5cAdS5cIn|?o;ps@NX0! zfz7@N+zd;U$}FUS`A6CA4T3w=UI>K*w??*0({-AQH7cJ4e>7#!Q9i>Gmi(%?QBvS| zf{SF5L*ClEJ7;W~`YV<)^(Sl^+jK7hzosX5juOIRe`l;aNv>T}I@DY12>AI5Ut6em zUB~kCSGQc|PIrg%Bi@#tkiR*AxDVK6$RF_rw`*rnC(Y{j);sj}sJY-p94M}1-l%HM z%jT?dP#z*)rDFE5A>n{4SwG4?in5+S)428EHqs28T=W$odXSaVOZ(9@%DSK10nrM`W@Uly1j0i|U`l16$ZCc@6|of8xi#K(xnxhmszNwsDZDv#3~ZfN%O0## zQb<&6j;GwIuAnnUk(!lw=d!13A!&#|Z~%Q4cF82p%ih7T&S(|=-X={B`G|lA;J!cs zybXU~g{=W8T;^)-8s)srVHB&pf(s`}o;fggmZ{|5H zPwX;vVm;FPFWs3+D-fJivE5oBXKezTz{Qr@KE= zkGtxX~R`S+wS$z7P`&PG2D=%mr1*;HkK>E36Byt@Ri$ zTsVng;X)Qa`IZJ<y?1*Dv8-v zf1$NjzEar1TbHUl4562a6K_F?A{c@5+395_MF&XogU3rmdEAK0>(aHl0);w-Ia)BA zU9DOJ!7jIwucRzBs75N67P(T4M0r8NwRtbm#`5ilgyI)U!4hTqv);31DM4lAk@_w1ph;@p?{Dznjbbda3Pqq<*acidfJVG)FyKuD@ml#HF?A4C+G4+2ZhK z0Rv_TB?=)!4Z&&mw`GM zH!W0bAC!fJN_mA+T`qbH3Rf$(Bs*x_UlXt4F}xmIUPwfQT)}B7IGsWSAr--x268h( zG7}eyaEQ5>7j+(B+JDmXSIN%u-u5$Xr5Oz z{8&LX(1wK#LP5A%_>}M!;cJL4|E=)u!8H#FU#&lKBkmQ@O^(!m{mH`DzkZ~?ZKTlF zcBFpe`Gt)ekJR6KXW`ackJQJaAi$CO?wLY&_mTP?7ZrBwI8y(?dkY`@;F0>repPtv zu_FXJc%-zv75()syp!cvmFiYOR+eL)5VqJ6hVTLyxq*et;%sTBMCCzC|DJ*n;Iu#( zY-c~?aUhjgt6WFUj9#Ql0;L;$Ktv!wt~z!h$dod|y~5fGt~H2G_W1hS=Z1&pZeM@a z+uLVjJ4fh_*RuNj<;lVJxG~#M{42XmRqtr@b-qJfm)|~~N{w$v0Il>I=xlPinmPmW ztGK=Ni_+`DFKo!n^dxn=)y{C*J3LiqTbXI!x+0ZYv9&$3(pGmta%^ilkc&GU@mwI# z(d2YCbuh&2vE^OcS0s}ohyq)ck7yc-A8FdyvfQ~v*O(t~uJ4FC+M7JgG<4v->({^c zz|c@;HqzR*X5aGVNR%}?{l@IJgMnmdbZp4i-IH9gqq}>@%0yx%y(0Pqr30d|-jvgc z5(3fQlxxpvzfru)-_qqtnIc~E>~!1}Yg)0rt82%~rkFEbj8E(t39|LBR8KV8n|8U< zz0qh-%C$+MdE@s@2+i!>u_7K@*5e4=x;h!|Gd0CrY|b6DCVCLMi2Y19+9}TW6p!UC zL}P_$R3H{D>peBzf-)v1M^&aMyBd!B7uNQzQIGD<4#aUMA-&1ZA=PgqD?RO?nxUP7 zhl3@~D7`M=5YuVNFO31o5g0=f)*#r0qkchGur|TjE% zzswV~-YKm5hZP!FJIgiVa@evTK71G-g4SNpTEslPhzt%dsF(zIS`V~5|$ra2u` zGU0RVC3ZGqEoqF5)|qmQa_Y||Zd}J+`o%9e-(IgUGmGLQh?`lm4&e9vO-=qlQy(W3MmF4Ib2bG6P1^diULV#(9B3NQQ_chL zlL&-*RcLRxPopqsDLscPmRbqL5N9q2H59*6{LJ&>9ZzezfZo)nVV!r2-zuIc-mPH+ z#YYfd{CTz-&y(txpw5eo=ih+vzIp-Fc9sY(X{{CC0iw_mZ;JFkU=x=@;JEeehslZV zNYmtb{oO0fFurX>@`Qb*4Vm+y=y!A&R>&h3G@~ny`eO_#hvaA=Z8Ef zH_BpIn6ONPIIUc_kHNod&S3zBh$8l@12Kc!X;G^?-B^yQFhW$*9kwzYb5ac5goTfe^0(9}1ZZJX}& z544M+Y%{RY72Cx+@7|<#^H5tjdh|Yzj**U2>meu z#Tz^%{{e;pe@A&%e)+u!9vYV41Mg3`ReoOwY{uv0_evxndgb>j!NE4l?+rqR{Qxv$ zbqJ>Cf28*eZ58t8kYCt!(&q~Fv`T(o2YvfK`MnbMpLzMc3Mq?C@_U1@9Wkl+vl>-x z4hXx1i-euRVO$#43Oj@gghTkn+<{#e?K~V>yW;}<-!Q(k4_^xj6ZqN=yx)rk!tjB8 zyFwF(ckJ7X|H(qf$>47{timhd<)6jBy$d=hJ-(&<#G2=2TQiyN-j%bnD|_Yssrwe{ zrqs)+1{^9salsg?Claa|@CIQwW`77%Ie?|}ilJOo>98~$Fx|F#O}q5A{)cOQn+3aTCu_T#tb@X3-9n zmTb@A!v_!bw6$Gw$tA7l9XN2_zTK@m5A1I{_rL{*54B;5c3*I4ciTZ}T@UeP#fB8- z|Bjz7;5;nxN2FvI#zck^WZ)cB^+mj*8hI94Xtp}=x*jqMxO~XBu;3K6A&TFDcte-q z2H$y+@9RenLlC45BX%bW$=?V&P6B6L6F4^wq~{jQxD7m$MgKWO?RFr-Cl5Zo~M}^M`=U|0y#!mPiR_nXMW5Rz5j|)!- z-$#w-bA=~`9|%80*6(@3%fcKyd9eVUg3PK>^`jGN3pgS@>M*9 zjdxhM7@Y6|a+EI@E)%X0t`e?9HucAZBe*MEBU~%|NqABCGE*@%lJ8K5o#}*=LXkBf z3&6mP!aPbunHbJ2#+a4an4LM0r0HUA=3!puL&jo2_=oU13$hRkvj~f_7;8jCdV(cc z6H6hZs+qO0R^gw*Q>=|;7%tx|#{izmI$54|v2NBQ{95=8>t%hcpDhOf+aMca!)%0& zvN5)Ttz_fE=Y@ON1e-*D=`@>RtB?b`nyq1Hu(fO*ZaKeW>k*s3k!@m|*%r2yZDVI5 zci?Qc9l*OtS!cW0Zgws^kL_W55y$%#c0Sw3_Cq>7Cp^y%u!HPEHpdRJ!|WnwJ?y>gee8C2g1w)8fc+c$Ao~#eF#8DmDEk=uIJ<*=g8e)DB>NP* zlYN?fhTX;PW}juBWB-qRp54RlWnWoB^c5#Pz zj<{3YCGJM%-Ff03aj#eq&lmS0f9L}701zH8gaz-Acv!qhyjZ*h6;v++BIOn0mEu+6 z)#4E#cVCN4)MMgx;`QPU;&Jh9;@ia=)fZi`HY)SY9k^ik`RX0gt8QfH-npF@?LT+l?n`w$EAQ(^b{#mp zV<*J#;rgBBx2n;dJMd`LF6m_y{p>ib9+NxVEq6F3b-0_q)Q^>0*Y7UBRgcNT*e$)P z#-u0i<}bRHm4WEatGusYS$;tMdF8i!;@S3g`8B5-ue8wZsl2ZnKWE2W-5&g-nmD|7 z->%)Nz5GQzA&-BrJpKu3{Cg#WO~@nND-&$8@KI@*nuZuS}_gm*jHEQao zXU#jT!C0a}rOO&r);90324foKH;oPD2gioy>~#GYcm3FD{TSD%ZRhaR*idz1>ey&? zYO-xSnQPu-)Fu+tx?E4Xx|{bHwF!&%8tie6=I@OqF#mX*Hcq=Xxp=e1Yuy;FEjJGs zvPq4a24rL=Wn}i*$V}PD>~lSuay{B-J(|*}uy0~yY^X3L*S3Ah{QIvcV6^_E)J3pWN>jgoWLZ_=VU)j^Ht z?>s(QJw9yd@i?tmO2r-*_quq!i%nrly(o8YDol=#jUR72GBP+Zczooz z=33T|zA#+X{!(p?5)gk^qLM)m4{woV3<^s5G-t~zVJd73psZ9 zNHyF(74Db{9~(iLm4k5CNPf@ocwz6z(6MTMpgJ|jag*OQJd)qYch~U|BeTA@Rq%O^ z+8vog_ZQpbl@WPOEu5e^g-QR~{+RQQ9DIpfx^{KW9MvAwZWIIf+Wq;rmN{slVrEMNF2jElru1)t}q zy@tP5ht!74RcfY18*9?2^%Y%grdaMPyS!7TJ~R6YKe)}NbQ*l6)8H$e zrhJu7-_De?(rHRu=`zvSqb zT>ndse#y}zvSqb9Q~4`Uv~7%j(*wozwGFj9sRPS?`}L5H#t+@g`dyBGm!se1=yy5#U5>uHRaLqi{a#1E z*U{hL@*9fH2Zko9tfyYG=zz}R%`a&*?|?Vac`x}?^FafAStHTTQm0MbrOx%*8)EGZ zv2ship=s09gia7-R5Yc|4ay3!c8%BpZ_uuh*RGU`W(uWJu`7S{=@Gu8LUeP_=FM%a z{ML+39UGiro||Z&d}(~mk-;%0k1@%&Nj_0K%=#tHFFZOvHIW2gpal_pVPZ{nbW)A4 zyg`LqD%xa0Y@8NNsHLI_l~@Or*u+rmRV5;?gGg*WE%sXbwWiFawWiFa?j9W!Vv|gF zkKya?F?`)UCf4pARTAI~e|L}J@9r`D-90A3?jBVR;Eez79+PNykBvZgk19v0+x4T{ z^`pDDgp?2SJzC)V^kKaY>vcr0&*}Afy^iX266j&R&+&bp?__?0)_k8nN@_AcNj&-V z5*cOb(!6DjM&lL@Su|?Vphoj_IVA(p7A;NIJX?KXzRZc8rOr-%yDDsx`a)H% z3%)2F70*uv?@k{coB=#ENAZcgh_^$$GJk{D<$b(N0{e!= zxrHu%&Bf;|4)?hDU5&%=fATkI4|Dke7ysVk!XAs8mw*+_%_Wy#>+&Y4T=Nc>KkVY8 zD!DvfstWyU!xOa`p7-EkdI}%VbNFyx#9Q-YJVt`Ig=>y_xeg?*Rpcx9b_CIEr8J2SB7bNze_CD4t8F&OQb_ zb)2?<2e1dlDE;hR%Aax zJeOXS!e{aS4iNhBy>3Mhc58ZonQU&V{O-Dveq`IiJg_9V0Y6e0+@;rTVVU%5y*@Gv z2$_lcgGpu${y35OwF58d|KX`#jc>9aZ}ckkpYMgQmA@dby_iHO(Ff;J0b|dQETD^mUqkRMSf}{fMTQ zXnG6$LMhx#43%&bF%-f+Vz7rBiNPFhAO>r=o*0bbI%2Se4-*R}?gU{slDb7x_i0Mx zxro*)!-s%c1oVR%w~-Gu7t&7|xdlN-ct2$wVK3M9VHej$is=DTxA1KaTEiaVo5QtQ zl0&v_z;xipzZpM#73fdE)f&8w-?trISgks>j+tmz?YtPujI}K|7(S-)?(l%dyTbh% z?+o{8ydxaYczd{4<1OJKXm1ZipAUvlaNQL?&UI(FhwHX*H`guU9b9*ZtGUWAU;M{> zuq1q1+iwe>q4Z$*6xVHhrB8>oq^c#NJKOl3KrC+s-?<92fSD>Uy34$gSFIMCl6ko~ zyqRkbEzdCq0xzR*6fG6~xA|M~TZ(@7$4#ipie49v}3tl1hr>?|WN=X=8502&3OW8U8vGFTyf0pa7r%&ru@UNdf6ZB1=3|<8$ zhumZaiGOQaf%e|UuQ+c8?}e*^@V*a(dv*r@2Xg0JRsMJMJmDO%3nHy=|3hC>pXsyH zr{VG(edRNKhW>NaG^HufE0{5gN>zRxjChoPV@!XPzq9GGtCd4KC#HWSQjve3`nEo` zw9zlPVFVeu^he7hS*GhQZw@UwDHvtskdo6TEvX+tN<`-_F9+G!>SSe_eKyuN-%d)d z4o@Z}Pj_~)%ptRb{H9Q|qjR++^-X*8-D=M$>iytKaW%WKOHzj)XNOl#!)m#bJ5J1l zr9BsM`PEO=&k4puE&o@Nv+sf&YWv4reS$IjlyHXcX_J%QTR0-p?$CNymKpSY`DRO! z)&%Nhy`l4PEv1XgH$_Ul- z9+e!4Qr{>6FQ=gO1>sB4s_D->y6KOYjn4a=#M2*Bf7(mWb3J`B-5cM6)~7`ab+jAw z1dTq$b3?|*X-cd!%-V2pJ|(wv(_d4j^LZ@_&t+{nPbz9H((+Lruip9U0C#o9NKl8l7*JylJn z5pHeqC2|p;pNyZ564%6>XKfWS!Fw%*%a<40XtCJfr>vjQT(0j=vu3B96(QeKT;HNT zvV$z@;3;@5qK#lPYd+(9hb^5{Ite@?KkpNwO+pSv<3f^bEaxo#vdd%yvd1tBUS%b# zW3f6J{jXFus+aUm`gfXH<9&h1YVXUm*It7}QmojdP9$J0DHB=As6@E5m6MRipsj=x zX(={mtA@{L&#Ia7)6@ShBSAPNbyC*oV>lgs!RL7aue4D-tgh2|Qd}9F_I+A6Q8}Ij zmp;uK2b9_>B;qH@nb!gz$NWND=P9L$WUNMW5vD|$@sSz{ZT}%sIfKs2h)H~6`gLi= zxSc@ipH6>BtR8N|m35`wL8uOhjadI8D^W~52ZJ}_K&hXSeqsMQ*477iwHZN1(T%cE zj@xH(GP|-!RuM0=Y$vlQZRDEdgY|>nL*&YrXO@fo*z9nztvo#3oQDUuCiSI0mKCYg z!CT@^mgHZ!wjgb^7b=+~Jmoc7W%#9?r<9J<6w-AUm6QKWY4FMBsR1y>ZiMOp8uy|O z)D^_Nwv=gnBWNp++a%#?`(Sy-EOBRW?HPeu=p?2qnHIW!MRz6XTo`ww-e<^4=Eh8o zy)Xq@rRE#*8MB1VkqJyZUyApoc4oj^V(T%k)HSe_1fII&Z1k$Gkt;xl+^Q-K!I}$C z4qtuh&FWtaPi=>Xu-Hr0V!3AX=vzg&oE?i4vh2;5IVsI%7OjD)9l3~d<`~AcmXfiP zQL=BcHtKPPCM4lzllHh~1KS8j@0lsxZrpQ^Ba80Lt$gcbTys}&)*>2}f|C2X*kFw{ zC1L6%ovyvOoat448?RUOHCLnLGI6!~^70B-jh+*Aj&e3Ar)|#k$+$b$S^i8oTz#W< zHn(1iw)p#kTFfO{ugW4rT3)Z5tZL?>S$6r7!GwC2ylS|*Wc-)XkU2nB$UM!Y?>nsS z-JMTj{mQ*38jpC6$eN{$lk9uUY`J9pD=TDKx2NV!hg(+2Hv1>mTxp8h*4@E{bK^ce z7iM$gM?|EOhJYu26PX29i8(ZlLvVZiUbfJQ*7>UUHB?=nQ%^9iOnr8Y7VUW&OnBw$ zSAGifX*`DMo=GmvXv3V?MwVQ(JF|ZB&;Z#9`UzMTUL=x7x5T0TUt!a>P4+cV>tlZrt1@E7{Qia)>nP$=KR#KWt zS;|v06SBdVrto5u@&-uFn5N+cGXB)p`4RJafi*G5b02ZqTHk_Nfxx%QRc3b4_fu%Q zdA9Ls3q9&LP;)_@hkx@s3cl3r;oC(sjn>Azc&_7~fkC|-N$Z5Z_<*y#AEFzPpEHT& zo2Tn}gYOm{-2(5=Cp6FBZPm-yK7+4M+WEHeFVHduDgDUczk&9`QD%+jidZnuX5C&x zG(DCzl`~`;^+s1+;#ZC|a|T42)utV$xtRbB3LA1It3aKxFHfKAb*BMCTHKH;R;U7y zeuw8mn0%e5uM(O`uY9AL*nWYiDI|*>$~++>cnS9)opWTK&^w~780%?BNw!q7O9i@7 zO$PQBwV0ZxGcU6Y__#>!b9JA3%E{R)30@0Xn@_@~)7g;OrRkP`7`beTUA_M4ESe#z zcox;lOPED7kp6)e&3wn1wX9^n)HnZx!Ozs9x3Md#nf+mN2%Fh0HJ2T5VzCR@^EjVf zO09&=l(n%dmA!!M(P}5;+1Y$eu#UZQi`X%>n6QpLRUPd8x|Xn)-F(+ke?4J7`=o5A z)N*z)-9UJhJ%KkuZ3UrKPt4oL&dyue-+LQDcBZZZ-|d7B_S>xn-yH-w|M5=tK;1>S z4qomCx16ZAj5GD_1*@FKcq3=)6~QX!>n&p!a|P^Ogq7?cUQ4>0a5H;CdpM!5mv9UF zobQA3{e;`tYy1F|A0*tad%#w68sEcEUPriF{^tOBZ6FM=JFJg>ZzkNy34L4O=n=v_ z^z2dgU~VN8*@v|aKKcn|PVUpp62%;SO=iGkMJ-!_OrY2 z0AUO9W8gkW=x68iAuv5o*ugI3C&2Y2VHf+ep90g!FQ48KM+>4|MWffuKgpS68wVG2k&G*>IX>X zL&815FF8A~$gawh$mCa?uhzraYQIMNJ|aBGX=|sD#=j6A;uOPwg~NX%>}EISCrJA@ zgx#E0_`kseoCUZjSjuUDi-V$^{1$X__S=G>kMrN!gH4MM^ zB^_Z0I~cDGuMO65Hr$e+i}T@@25UJ5?)G3SXB4asx;YJw|9s*^w_U*+PHWrE-rYUy z$t-bd+g{QG;Xv>pr?>3`a(}o#=;ai*1HpZqRV{Fj)S^PQsOzEjAhU<;s$8HJb*I`= zr`nS2rtU)n#Fp}EOB>Plt;`f+KTT>s?Tr3*PQ(=ZxlQe7H9EhW5;+I+F14J6YB_T1 z{C?Vs<=mr|vsf*spqA6DmNQ2!XRcb#wQ4!@)N+=n&1_XW`5wEd$KX<|&1x;* z;{=2knL*?<)0JvD8`N?h;T+39Mw&k&m=$eNE9z1!YEdhiuU3>}&*q!-_${ocPc0{> zmXlMG1rMp^bjTTs>=qWAS%=L$N%|?Y{4P1okl8atKMLr!_&(Yh-D}X)7Bp=o zIhp^ji?rbxG)m^Mz06>PjIhjJ`*l1HFdh?(t;}18RQiwWy!8YUe}_>$!OSs*EPlfb zB{Rec`n)*!J9@h`_&HqO5qt=LhdJFehuoUrr48D0#>7K9!w!S}r;N-AX0Kne-#*VO zORlGbeD3w>bGg6Z`*YS<`5e1@DLG4tr0xtUPV!JFX(#n>D9l-E z&Ty3FD?x|ghZZ>X${yj|?|}In{FeGXsT`*$2e~{agvtq^uX66^8=Us}x18wtpSjP2 zuHJ!Xxj2d7Q4!(RbrVYkP}SJz-iWsl=~L%54m-@A0XJC&^Zn7T!G^qyAMH}k^^MSEk^ep9mXmAxd+%Rc#~84flqtG^8_XC zH$Hs$`mUyVc}U9}=Cbb%uMe@jEd+mX>fYB>8Zx$#RH)TA0UKMCmsF$JG$O98#}t?N z{>RXkiXkMQm2WiRuPCMte0__&4f{&b%cBb8<1*6 zR+4!wm~5gtJnbT>D4$3u#>|=+Xl8wmVP}(?5ufYrY;XxoJ-)Q{020mZv%xtFe6v9l z!3e&1eD%I0C9+>KA$c3uM&lGA(}a$1LJO3F!zCEv9(W41Is-)Fx5f+NOT3vO+Gm`2 zyq>QV-{_w&*En&f($R|E^hiOaGAZudUDX4~dd8<4#WftHz-n8A?*gm4RMX6i+;fr@ z@mMEIE$wM1<}wH5YI3=EdEydZUCvhv^Hd?j=*~*jaQmtJ|i@AKon>ZJbg`B%)|C=HI@p2R4I=#xdTQ}jITTJaL z(zoHQklJNDG>?FywQR0t_eDfOqf^7 ZjE|EYKE{{aYUV^#nF literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_italic.ttf b/shared/src/commonMain/composeResources/font/productsans_italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5b44037be5907cc01dcbfc07f300ef417127c59e GIT binary patch literal 95744 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

G+NYd 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 IAAbM;0R!oOJ^%m! literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_light.ttf b/shared/src/commonMain/composeResources/font/productsans_light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..33de1c39f2dd872e5e3d55a8b80161bdcbdbbe44 GIT binary patch literal 92564 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

+7M-@pUL<&Vo;R0WMe65Wct)gX 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 literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_medium.ttf b/shared/src/commonMain/composeResources/font/productsans_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fd818d6f5b2febcbd5e919f146fdb38a5cd5c73e GIT binary patch literal 92312 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 F>3?g0VA}uy literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_mediumitalic.ttf b/shared/src/commonMain/composeResources/font/productsans_mediumitalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ec70cd38411657291dc13fec6c7274ce44471f66 GIT binary patch literal 95656 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 dLHeE;D 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 literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_regular.ttf b/shared/src/commonMain/composeResources/font/productsans_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e2c69c3fb94651237de80e67a69d458c55b45649 GIT binary patch literal 109128 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 B-RJ-S literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_thin.ttf b/shared/src/commonMain/composeResources/font/productsans_thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5e046b53295fdd1501c2d6cf22f1dac2cfd42793 GIT binary patch literal 92836 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 Ru7BM5!iPFv`?Bkoe*ui=y`}&F literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/font/productsans_thinitalic.ttf b/shared/src/commonMain/composeResources/font/productsans_thinitalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7b0495518876d68c69e024ae51032a5e4345a04f GIT binary patch literal 96540 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 literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 000000000..3e6f4f768 --- /dev/null +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,9 @@ + + CloudStream + None + Ok + Cancel + Navigate up + Lorem ipsum + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt new file mode 100644 index 000000000..e7d8900e1 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt @@ -0,0 +1,202 @@ +package com.lagradost.cloudstream4 + +import androidx.compose.runtime.Composable +import com.lagradost.cloudstream3.AllLanguagesName +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.TvType +import com.mihon.common.preference.PreferenceStore +import com.mihon.common.preference.getEnumSet + +@Composable +expect fun rememberAppSettings(): AppSettings + +/** + * App settings for every setting we have, that way we can just inject AppSettings into a viewmodel, + * or similar. + * + * We use an internal constructor to force the user to construct it using the plantform specific + * initalizer + * */ +class AppSettings internal constructor( + preferences: PreferenceStore, + // Todo add database key-value interface here, possibly using the same PreferenceStore interface +) { + val general = GeneralPreferences(preferences) + val player = PlayerPreferences(preferences) + val provider = ProviderPreferences(preferences) + val ui = UIPreferences(preferences) + val security = SecurityPreferences(preferences) + val updates = UpdatePreferences(preferences) + val backup = BackupPreferences(preferences) + val plugins = PluginPreferences(preferences) +} + +class PluginPreferences(preferences: PreferenceStore) { + val autoUpdate = preferences.getBoolean("auto_update_plugins", true) + val autoDownload = preferences.getInt("auto_download_plugins_key2", 0) +} + +class BackupPreferences(preferences: PreferenceStore) { + val frequency = preferences.getInt("automatic_backup_key", 0) + val path = preferences.getString("backup_path_key") + val visualPath = preferences.getString("backup_dir_key") +} +class UpdatePreferences(preferences: PreferenceStore) { + val apkInstaller = preferences.getInt("apk_installer_key",1) + val showAppUpdates = preferences.getBoolean("auto_update", true) +} + +class SecurityPreferences(preferences: PreferenceStore) { + val biometrics = preferences.getBoolean( + "biometric_key", false + ) + val skipAccountSelection = preferences.getBoolean( + "skip_startup_account_select_key", false + ) +} + +class UIPreferences(preferences: PreferenceStore) { + val primaryColor = preferences.getString( + "primary_color_key", "Normal" + ) + val theme = preferences.getString( + "app_theme_key", "AmoledLight" + ) + val layout = preferences.getInt( + "app_layout_key", -1 + ) + val bottomTitle = preferences.getBoolean( + "bottom_title_key", true + ) + val advancedSearch = preferences.getBoolean( + "advanced_search", true + ) + val searchSuggestions = preferences.getBoolean( + "search_suggestions_enabled", true + ) + val kitsuPostersEnabled = preferences.getBoolean( + "show_kitsu_posters_key", true + ) + val trailersEnabled = preferences.getBoolean( + "show_trailers_key", true + ) + val castEnabled = preferences.getBoolean( + "show_cast_in_details_key", true + ) + val fillersEnabled = preferences.getBoolean( + "show_fillers_key", false + ) + val showMetadataOverlay = preferences.getBoolean( + "show_player_metadata_key", true + ) + val overscanDp = preferences.getInt( + "overscan_key", 0 + ) + val posterSize = preferences.getInt( + "poster_size_key", 0 + ) + val posterShowHd = preferences.getBoolean( + "show_hd_key", true + ) + val posterShowDub = preferences.getBoolean( + "show_dub_key", true + ) + val posterShowSub = preferences.getBoolean( + "show_sub_key", true + ) + val posterShowRating = preferences.getBoolean( + "show_rating_key", true + ) + val posterShowTitle = preferences.getBoolean( + "show_title_key", true + ) + val posterShowEpisode = preferences.getBoolean( + "show_episode_text_key", true + ) + val showClock = preferences.getBoolean( + "tv_layout_clock_key", false + ) + + val randomButtonEnabled = preferences.getBoolean( + "random_button_key", false + ) + + val confirmExit = preferences.getInt( + "confirm_exit_key", -1 + ) + + /** This had to be refactored to use enumSet because the old system is prone to bugs */ + val filterQuality = preferences.getEnumSet( + "pref_filter_search_quality_key2", emptySet() + ) +} + +class ProviderPreferences(preferences: PreferenceStore) { + companion object { + private val defaultPreferredMedia = + TvType.entries.filter { it != TvType.NSFW }.map { it.ordinal.toString() }.toSet() + private val defaultDub = DubStatus.entries.map { it.name }.toSet() + } + + val preferredMedia = preferences.getStringSet( + "prefer_media_type_key_2", defaultPreferredMedia + ) + + val extensionLanguages = preferences.getStringSet( + "provider_lang_key", setOf(AllLanguagesName) + ) + + val displayDubSub = preferences.getStringSet( + "display_sub_key", defaultDub + ) +} + +class PlayerPreferences(preferences: PreferenceStore) { + val episodeSync = preferences.getBoolean("episode_sync_enabled_key", true) + val defaultPlayer = preferences.getString("player_default_key", "") + val limitPlayerTitle = preferences.getInt("prefer_limit_title_key", 0) + val hidePlayerControlNames = preferences.getBoolean("hide_player_control_names_key", false) + val showName = preferences.getBoolean("show_name", true) + val showResolution = preferences.getBoolean("show_resolution", true) + val showMediaInfo = preferences.getBoolean("show_media_info", false) + val pipEnabled = preferences.getBoolean("pip_enabled_key", true) + val resizeEnabled = preferences.getBoolean("player_resize_enabled_key", true) + val speedEnabled = preferences.getBoolean("playback_speed_enabled_key", false) + val tiktokEnabled = preferences.getBoolean("speedup_key", false) + val autoPlayEnabled = preferences.getBoolean("autoplay_next_key", true) + val skipOpEnabled = preferences.getBoolean("enable_skip_op_from_database", true) + val autoRotateEnabled = preferences.getBoolean("auto_rotate_video_key", true) + val rotateButtonEnabled = preferences.getBoolean("rotate_video_key", false) + val previewBarEnabled = preferences.getBoolean("preview_seekbar_key", true) + val softwareDecoding = preferences.getInt("software_decoding_key2", -1) + val extraBrightnessEnabled = preferences.getBoolean("extra_brightness_key", false) + val swipeHorizontalEnabled = preferences.getBoolean("swipe_enabled_key", true) + val swipeVerticalEnabled = preferences.getBoolean("swipe_vertical_enabled_key", true) + val doubleTapToSeekEnabled = preferences.getBoolean("double_tap_enabled_key", false) + val doubleTapToPauseEnabled = preferences.getBoolean("double_tap_pause_enabled_key", false) + val doubleTapTime = preferences.getInt("double_tap_seek_time_key2", 10) + val bufferDiskMB = preferences.getInt("video_buffer_disk_key", 0) + val bufferRamMB = preferences.getInt("video_buffer_size_key", 0) + val bufferTimeSec = preferences.getInt("video_buffer_length_key", 0) + val tvSeekOnTime = preferences.getInt("android_tv_interface_on_seek_key", 10) + val tvSeekOffTime = preferences.getInt("android_tv_interface_off_seek_key", 10) +} + +class GeneralPreferences(preferences: PreferenceStore) { + val locale = preferences.getString("app_locale", "") + val bananas = preferences.getInt("benene_count", 0) + val parallelDownloads = preferences.getInt("download_parallel_key", 3) + val concurrentConnections = preferences.getInt("download_concurrent_key", 3) + val batterOptimization = preferences.getBoolean("battery_optimisation_key", false) + + /** Please note that it used R.array.dns_pref before, this was a bug and caused this setting to be language sensitive **/ + val dns = preferences.getInt("dns_key", 0) + + /** Very weird setKey based usage, just having this setting might not do it */ + val jsdelivrProxy = preferences.getBoolean("jsdelivr_proxy_key", false) + + /** This should honesty be refactored to a single setting */ + val downloadPath = preferences.getString("download_path_key", "") + val downloadPathVisual = preferences.getString("download_path_key_visual", "") +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt new file mode 100644 index 000000000..d39bffa66 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt @@ -0,0 +1,24 @@ +package com.lagradost.cloudstream4.compose + +import androidx.compose.material3.ButtonColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors + +object Colors { + val blackButton + @Composable @ReadOnlyComposable get() = ButtonColors( + containerColor = colors.surfaceVariant, + contentColor = colors.onBackground, + disabledContainerColor = colors.surface.copy(alpha = 0.9f), + disabledContentColor = colors.onBackground.copy(alpha = 0.9f) + ) + + val whiteButton + @Composable @ReadOnlyComposable get() = ButtonColors( + containerColor = colors.onBackground, + contentColor = colors.surfaceVariant, + disabledContainerColor = colors.onBackground.copy(alpha = 0.9f), + disabledContentColor = colors.surface.copy(alpha = 0.9f) + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt new file mode 100644 index 000000000..9dbed5f42 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt @@ -0,0 +1,491 @@ +package com.lagradost.cloudstream4.compose + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.AlertDialogDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component1 +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component2 +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.check +import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors +import com.mihon.material.padding +import org.jetbrains.compose.resources.painterResource + +@Composable +fun ActionDialog( + title: String, + text: String, + icon: Painter? = null, + + confirmText: String, + dismissText: String, + + dismiss: () -> Unit, + confirm: () -> Unit +) { + AlertDialog( + containerColor = colors.background, + onDismissRequest = dismiss, + title = { + if (icon == null) { + Text(text = title) + } else { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(painter = icon, modifier = Modifier.size(36.dp), contentDescription = null) + Spacer(modifier = Modifier.size(MaterialTheme.padding.medium)) + Text(text = title) + } + } + }, + text = { Text(text = text) }, + confirmButton = { + WhiteButton(text = confirmText, onClick = confirm) + }, + dismissButton = { + BlackButton(text = dismissText, onClick = dismiss) + } + ) +} + +@Composable +fun WhiteButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + BaseButton( + text = text, + onClick = onClick, + buttonColors = Colors.whiteButton, + modifier = modifier + ) +} + +@Composable +fun BlackButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + BaseButton( + text = text, + onClick = onClick, + buttonColors = Colors.blackButton, + modifier = modifier + ) +} + +@Composable +fun BaseButton( + text: String, onClick: () -> Unit, buttonColors: ButtonColors, + modifier: Modifier = Modifier, +) { + var hasFocus by remember { mutableStateOf(false) } + Button( + onClick = onClick, + colors = buttonColors, + modifier = modifier.onFocusChanged { newFocus -> + hasFocus = newFocus.hasFocus + }, + border = if (hasFocus) BorderStroke(1.dp, colors.onBackground) else null + ) { + AnimatedVisibility(hasFocus) { + Icon( + painter = painterResource(Res.drawable.check), + modifier = Modifier.padding(end = 5.dp), + contentDescription = null + ) + } + Text(text = text) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SingleSelectDialog( + modifier: Modifier = Modifier, + shape: Shape = AlertDialogDefaults.shape, + containerColor: Color = colors.background, + iconContentColor: Color = AlertDialogDefaults.iconContentColor, + tonalElevation: Dp = AlertDialogDefaults.TonalElevation, + icon: @Composable (() -> Unit)? = null, + title: String? = null, + properties: DialogProperties = DialogProperties(), + /* visual */ + entries: List, + /* Selected keys used in entries */ + selectedIndex: Int, + confirmText: String? = null, + dismissText: String? = null, + dismiss: () -> Unit, + confirm: (Int) -> Unit +) { + SingleSelectDialog( + modifier = modifier, + shape = shape, + containerColor = containerColor, + iconContentColor = iconContentColor, + tonalElevation = tonalElevation, + icon = icon, + title = title, + properties = properties, + entries = entries.mapIndexed { index, string -> index to string }.associate { it }, + selectedKey = selectedIndex, + confirmText = confirmText, + dismissText = dismissText, + dismiss = dismiss, + confirm = confirm, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SingleSelectDialog( + modifier: Modifier = Modifier, + shape: Shape = AlertDialogDefaults.shape, + containerColor: Color = colors.background, + iconContentColor: Color = AlertDialogDefaults.iconContentColor, + tonalElevation: Dp = AlertDialogDefaults.TonalElevation, + icon: @Composable (() -> Unit)? = null, + title: String? = null, + properties: DialogProperties = DialogProperties(), + /* key - visual */ + entries: Map, + /* Selected keys used in entries */ + selectedKey: T, + confirmText: String? = null, + dismissText: String? = null, + dismiss: () -> Unit, + confirm: (T) -> Unit, + iconProvider: (@Composable (key: T, value: String) -> Unit)? = null +) { + var selected by remember { mutableStateOf(selectedKey) } + val selectedPainter = painterResource(Res.drawable.check) + val (dismissFocus, confirmFocus) = remember { FocusRequester.createRefs() } + + AlertDialog( + properties = properties, + tonalElevation = tonalElevation, + modifier = modifier, + icon = icon, + shape = shape, + iconContentColor = iconContentColor, + containerColor = containerColor, + onDismissRequest = dismiss, + title = title?.let { { Text(text = it) } }, + text = { + LazyColumn { + entries.forEach { (key, value) -> + item(key = key) { + val isSelected = selected == key + SingleSelectionItem( + isSelected = isSelected, + key = key, + text = value, + iconProvider = iconProvider, + selectedPainter = selectedPainter, + modifier = Modifier.focusProperties { + start = dismissFocus + end = confirmFocus + }) { + if (confirmText == null) { + confirm(key) + } else { + selected = key + } + } + } + } + } + }, + confirmButton = { + if (confirmText != null) { + WhiteButton( + text = confirmText, + onClick = { confirm(selected) }, + modifier = Modifier.focusRequester(confirmFocus) + ) + } + }, + dismissButton = { + if (dismissText != null) { + BlackButton( + text = dismissText, + onClick = dismiss, + modifier = Modifier.focusRequester(dismissFocus) + ) + } + } + ) +} + +/* +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SinglePairSelectDialog( + modifier: Modifier = Modifier, + shape: Shape = AlertDialogDefaults.shape, + containerColor: Color = colors.background, + iconContentColor: Color = AlertDialogDefaults.iconContentColor, + tonalElevation: Dp = AlertDialogDefaults.TonalElevation, + icon: @Composable (() -> Unit)? = null, + title: String? = null, + properties: DialogProperties = DialogProperties(), + /* key - visual */ + entries: Map, String>, + /* Selected keys used in entries */ + selectedKey: T, + confirmText: String? = null, + dismissText: String? = null, + dismiss: () -> Unit, + confirm: (T) -> Unit, + iconProvider: (@Composable (key: T, value: String) -> Unit)? = null +) { + val selected = remember { mutableStateOf(selectedKey) } + + AlertDialog( + properties = properties, + tonalElevation = tonalElevation, + modifier = modifier, + icon = icon, + shape = shape, + iconContentColor = iconContentColor, + containerColor = containerColor, + onDismissRequest = dismiss, + title = title?.let { { Text(text = it) } }, + text = { + LazyColumn { + entries.forEach { (keyPair, value) -> + item(key = keyPair) { + val (a, b) = keyPair + val isSelected = selected.value == a || selected.value == b + + val painter = if (a == b) { + R.drawable.ic_baseline_check_24_listview + } else if(selected.value == a) { + R.drawable.ic_baseline_arrow_downward_24 + } else { + R.drawable.ic_baseline_arrow_upward_24 + } + + val nextKey = if (selected.value != a) a else b + SingleSelectionItem( + isSelected = isSelected, + key = nextKey, + text = value, + iconProvider = iconProvider, + selectIcon = painter + ) { + if (confirmText == null) { + confirm(nextKey) + } else { + selected.value = nextKey + } + } + } + } + } + }, + confirmButton = { + if (confirmText != null) { + Button( + onClick = { + confirm(selected.value) + }, + colors = Colors.whiteButton + ) { Text(text = confirmText) } + } + }, + dismissButton = { + if (dismissText != null) { + Button( + onClick = dismiss, colors = Colors.blackButton + ) { Text(text = dismissText) } + } + } + ) +}*/ + +@Composable +fun SingleSelectionItem( + isSelected: Boolean, + key: T, + text: String, + modifier: Modifier = Modifier, + iconProvider: (@Composable (key: T, value: String) -> Unit)? = null, + selectedPainter: Painter, + onClick: () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + Row( + modifier = modifier + .fillMaxWidth() + .combinedClickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick, + onLongClick = { + } + ) + .rounded() + .ripple(interactionSource), + verticalAlignment = Alignment.CenterVertically + ) { + if (isSelected) { + Icon( + painter = selectedPainter, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = colors.onBackground + ) + iconProvider?.invoke(key, text) + Text( + text = text, + style = MaterialTheme.typography.bodyLarge, + color = colors.onBackground, + modifier = Modifier.padding(15.dp) + ) + } else { + Spacer(modifier = Modifier.size(24.dp)) + iconProvider?.invoke(key, text) + Text( + text = text, + style = MaterialTheme.typography.bodyLarge, + color = colors.onSurfaceVariant, + modifier = Modifier.padding(15.dp) + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MultiSelectDialog( + modifier: Modifier = Modifier, + shape: Shape = AlertDialogDefaults.shape, + containerColor: Color = colors.background, + iconContentColor: Color = AlertDialogDefaults.iconContentColor, + tonalElevation: Dp = AlertDialogDefaults.TonalElevation, + icon: @Composable (() -> Unit)? = null, + title: String? = null, + properties: DialogProperties = DialogProperties(), + /* key - visual */ + entries: Map, + /* Selected keys used in entries */ + selectedKeys: Set, + confirmText: String? = null, + dismissText: String? = null, + dismiss: () -> Unit, + confirm: (Set) -> Unit, + iconProvider: (@Composable (key: T, value: String) -> Unit)? = null +) { + val selected = remember { + entries.keys + .filter { selectedKeys.contains(it) } + .toMutableStateList() + } + + val selectedPainter = painterResource(Res.drawable.check) + val (dismissFocus, confirmFocus) = remember { FocusRequester.createRefs() } + + AlertDialog( + properties = properties, + tonalElevation = tonalElevation, + modifier = modifier, + icon = icon, + shape = shape, + iconContentColor = iconContentColor, + containerColor = containerColor, + onDismissRequest = { + if (confirmText == null) { + confirm(selected.toSet()) + } else { + dismiss() + } + }, + title = title?.let { { Text(text = it) } }, + text = { + LazyColumn { + entries.forEach { (key, value) -> + item(key = key) { + val isSelected = selected.contains(key) + SingleSelectionItem( + isSelected = isSelected, + key = key, + text = value, + iconProvider = iconProvider, + selectedPainter = selectedPainter, + modifier = Modifier.focusProperties { + start = dismissFocus + end = confirmFocus + } + ) { + if (isSelected) { + selected.remove(key) + } else { + selected.add(key) + } + } + } + } + } + }, + confirmButton = { + if (confirmText != null) { + WhiteButton( + onClick = { + confirm(selected.toSet()) + }, + text = confirmText, + modifier = Modifier.focusRequester(confirmFocus) + ) + } + }, + dismissButton = { + if (dismissText != null) { + BlackButton( + onClick = dismiss, + text = dismissText, + modifier = Modifier.focusRequester(dismissFocus) + ) + } + } + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt new file mode 100644 index 000000000..d2bc48b46 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt @@ -0,0 +1,63 @@ +package com.lagradost.cloudstream4.compose + +import androidx.compose.foundation.border +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors + +@Composable +fun Modifier.ripple( + interactionSource: MutableInteractionSource, + bounded: Boolean = true, +): Modifier = indication( + interactionSource = interactionSource, + indication = ripple(bounded = bounded, color = colors.onBackground), +) + +// no dimens.xml allowed in compose :/ +fun RoundedShape() = RoundedCornerShape(10.dp) + +@Composable +fun Modifier.rounded(): Modifier = + clip(RoundedShape()) + + +@Composable +fun Modifier.circle(): Modifier = + clip(CircleShape) + +@Composable +fun Modifier.focusOutline(shape: Shape = RoundedShape()): Modifier { + var hasFocus by remember { mutableStateOf(false) } + + return this.onFocusChanged { newFocus -> + hasFocus = newFocus.isFocused + }.whiteOutline(hasFocus = hasFocus, shape = shape) +} + +@Composable +fun Modifier.whiteOutline(hasFocus: Boolean, shape: Shape = RoundedShape()): Modifier { + return if (hasFocus) { + this.border( + width = 1.dp, + color = MaterialTheme.colorScheme.onBackground, + shape = shape + ) + } else { + this + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt new file mode 100644 index 000000000..a99b623e8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt @@ -0,0 +1,8 @@ +package com.lagradost.cloudstream4.compose +import androidx.compose.runtime.Composable + +// https://github.com/adrielcafe/voyager +interface Screen { + @Composable + fun Content() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt new file mode 100644 index 000000000..8255b4260 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt @@ -0,0 +1,145 @@ +package com.lagradost.cloudstream4.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlin.time.Duration.Companion.milliseconds + +/** + * Use with the DefaultStateContainer + * + * `X : ViewModel(), StateContainer by DefaultStateContainer(XState)` + */ +interface StateContainer { + val state: StateFlow + fun updateState(reducer: State.() -> State) +} + +/** + * Use with the DefaultEffectContainer + * + * `X : ViewModel(), EffectContainer by DefaultEffectContainer()` + */ +interface EffectContainer { + val effect: Flow + suspend fun postEffect(builder: () -> Effect) +} + +/** + * Use an interface on the viewmodel directly + * + * `X : ViewModel(), ActionHandler` + */ +interface ActionHandler { + fun onAction(action: Action) +} + +/** + * The default implementation of a state container to allow by delegate. + * + * All state should be updated with updateState + copy to allow atomic operations + * */ +class DefaultStateContainer(initialState: State) : StateContainer { + private val _state = MutableStateFlow(initialState) + override val state: StateFlow = _state.asStateFlow() + + /** Do not do expensive operations in updateState, as this can get rerun twice or more if we have concurrent writers */ + override fun updateState(reducer: State.() -> State) { + _state.update(reducer) + } +} + +/** + * The default implementation of an effect container to allow by delegate + * */ +class DefaultEffectContainer : EffectContainer { + private val _effect = Channel() // To ensure events are sent + override val effect: Flow = _effect.receiveAsFlow() + + override suspend fun postEffect(builder: () -> Effect) { + _effect.send(builder()) + } +} + +/** + * Observe the viewmodel EffectContainer in the UI on the Main thread for e.g. toasts + * */ +@Composable +fun ObserveEffect(flow: Flow, onEvent: (T) -> Unit) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(flow, lifecycleOwner.lifecycle) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + withContext(Dispatchers.Main.immediate) { // To ensure events are sent + flow.collect(onEvent) + } + } + } +} + +/** Single active job, replacing the old job by cancellation if launched again */ +data class SingleActiveQuery( + val dispatcher: CoroutineDispatcher, + private var job: Job? = null, + private val mutex: Mutex = Mutex(), +) { + suspend fun launch(block: suspend CoroutineScope.() -> Unit) { + val currentScope = CoroutineScope(currentCoroutineContext()) + val obj: suspend CoroutineScope.() -> Unit = { + try { + withContext(dispatcher) { + block() + } + } catch (_: Throwable) { + //logError(t) + } + } + mutex.withLock { + job?.cancel() + job?.join() + job = currentScope.launch(block = obj) + } + } +} + +/** Debounce Query to handle e.g. user search without spamming the endpoint */ +data class DebounceQuery( + private val pipe: MutableSharedFlow = MutableSharedFlow( + extraBufferCapacity = 64, + replay = 64 + ) +) { + @OptIn(FlowPreview::class) + suspend fun launch(collector: FlowCollector) { + pipe.debounce(200L.milliseconds).distinctUntilChanged() + .flowOn(Dispatchers.Default) + .collect(collector) + } + + suspend fun emit(query: String) { + this.pipe.emit(query) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt new file mode 100644 index 000000000..72da2f789 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt @@ -0,0 +1,17 @@ +package com.lagradost.cloudstream4.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.graphics.Color + +internal expect object DeviceTheme { + @Composable + @ReadOnlyComposable + fun resolveDynamicTheme(): CloudStreamColorScheme + @Composable + @ReadOnlyComposable + fun resolveDynamicPrimaryColor(): Color + @Composable + @ReadOnlyComposable + fun resolveDynamicSecondaryColor(): Color +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt new file mode 100644 index 000000000..c81519e6d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt @@ -0,0 +1,71 @@ +package com.lagradost.cloudstream4.theme + +import androidx.compose.material3.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.em +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.productsans_black +import com.lagradost.cloudstream4.generated.resources.productsans_blackitalic +import com.lagradost.cloudstream4.generated.resources.productsans_bold +import com.lagradost.cloudstream4.generated.resources.productsans_bolditalic +import com.lagradost.cloudstream4.generated.resources.productsans_italic +import com.lagradost.cloudstream4.generated.resources.productsans_light +import com.lagradost.cloudstream4.generated.resources.productsans_lightitalic +import com.lagradost.cloudstream4.generated.resources.productsans_medium +import com.lagradost.cloudstream4.generated.resources.productsans_mediumitalic +import com.lagradost.cloudstream4.generated.resources.productsans_regular +import com.lagradost.cloudstream4.generated.resources.productsans_thin +import com.lagradost.cloudstream4.generated.resources.productsans_thinitalic +import org.jetbrains.compose.resources.Font + +object AppFont { + val googleSans @Composable get() = FontFamily( + Font(Res.font.productsans_thin, weight = FontWeight.W100, style = FontStyle.Normal), + Font(Res.font.productsans_thinitalic, weight = FontWeight.W100, style = FontStyle.Italic), + + Font(Res.font.productsans_light, weight = FontWeight.W300, style = FontStyle.Normal), + Font(Res.font.productsans_lightitalic, weight = FontWeight.W300, style = FontStyle.Italic), + + Font(Res.font.productsans_regular, weight = FontWeight.W400, style = FontStyle.Normal), + Font(Res.font.productsans_italic, weight = FontWeight.W400, style = FontStyle.Italic), + + Font(Res.font.productsans_medium, weight = FontWeight.W500, style = FontStyle.Normal), + Font(Res.font.productsans_mediumitalic, weight = FontWeight.W500, style = FontStyle.Italic), + + Font(Res.font.productsans_bold, weight = FontWeight.W700, style = FontStyle.Normal), + Font(Res.font.productsans_bolditalic, weight = FontWeight.W700, style = FontStyle.Italic), + + Font(Res.font.productsans_black, weight = FontWeight.W900, style = FontStyle.Normal), + Font(Res.font.productsans_blackitalic, weight = FontWeight.W900, style = FontStyle.Italic)) + + private val defaultTypography = androidx.compose.material3.Typography() + val typography @Composable get() = + googleSans.let { fontFamily -> + val lineHeight = 1.3.em + Typography( + displayLarge = defaultTypography.displayLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight), + displayMedium = defaultTypography.displayMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight), + displaySmall = defaultTypography.displaySmall.copy(fontFamily = fontFamily, lineHeight = lineHeight), + + headlineLarge = defaultTypography.headlineLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight), + headlineMedium = defaultTypography.headlineMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight), + headlineSmall = defaultTypography.headlineSmall.copy(fontFamily = fontFamily, lineHeight = lineHeight), + + titleLarge = defaultTypography.titleLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight), + titleMedium = defaultTypography.titleMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight), + titleSmall = defaultTypography.titleSmall.copy(fontFamily = fontFamily, lineHeight = lineHeight), + + bodyLarge = defaultTypography.bodyLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight), + bodyMedium = defaultTypography.bodyMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight), + bodySmall = defaultTypography.bodySmall.copy(fontFamily = fontFamily, lineHeight = lineHeight), + + labelLarge = defaultTypography.labelLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight), + labelMedium = defaultTypography.labelMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight), + labelSmall = defaultTypography.labelSmall.copy(fontFamily = fontFamily, lineHeight = lineHeight) + ) + } +} + diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt new file mode 100644 index 000000000..c6332808f --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt @@ -0,0 +1,84 @@ +package com.lagradost.cloudstream4.theme + +import androidx.compose.ui.graphics.Color + +enum class CloudStreamPrimaryColor(val color: Color) { + NORMAL(Color(0xFF3D50FA)), + BLUE(Color(0xFF5664B7)), + PURPLE(Color(0xFF6200EA)), + GREEN(Color(0xFF00BFA5)), + GREEN_APPLE(Color(0xFF48E484)), + RED(Color(0xFFD50000)), + BANANA(Color(0xFFE4D448)), + PARTY(Color(0xFFEA596E)), + PINK(Color(0xFFFF1493)), + CARNATION_PINK(Color(0xFFBD5DA5)), + MAROON(Color(0xFF451010)), + DARK_GREEN(Color(0xFF004500)), + NAVY_BLUE(Color(0xFF000080)), + GREY(Color(0xFF515151)), + WHITE(Color(0xFFFFFFFF)), + BROWN(Color(0xFF622C00)), + ORANGE(Color(0xFFCE8500)), + DANDELION_YELLOW(Color(0xFFF5BB00)), + COOL_BLUE(Color(0xFF408CAC)), + LAVENDER(Color(0xFF6F55AF)), + DYNAMIC(Color(0xFF3D50FA)), + DYNAMIC_TWO(Color(0xFF3D50FA)), +} + + +internal object CloudStreamPalette { + // Default dark (AppTheme / Black) + val Primary = Color(0xFF3D50FA) + val PrimaryDark = Color(0xFF3700B3) + val Ongoing = Color(0xFFF53B66) + + val DarkPrimaryGrayBg = Color(0xFF2B2C30) + val DarkBlackBg = Color(0xFF111111) + val DarkIconGrayBg = Color(0xFF1C1C20) + val DarkBoxItemBg = Color(0xFF161616) + val DarkText = Color(0xFFE9EAEE) + val DarkGrayText = Color(0xFF9BA0A4) + val DarkIcon = Color(0xFF9BA0A6) + + // Amoled + val AmoledBlack = Color(0xFF000000) + val AmoledNearBlack = Color(0xFF121212) + + // Light + val LightPrimaryGrayBg = Color(0xFFF1F1F1) + val LightBlackBg = Color(0xFFFFFFFF) + val LightIconGrayBg = Color(0xFFEEEEEE) + val LightBoxItemBg = Color(0xFFEEEEEE) + val LightText = Color(0xFF202125) + val LightGrayText = Color(0xFF5F6267) + val LightIcon = Color(0xFF5F6267) + + // Dracula + val DraculaPrimaryGrayBg = Color(0xFF414450) + val DraculaBlackBg = Color(0xFF282A36) + val DraculaIconGrayBg = Color(0xFF44475A) + val DraculaBoxItemBg = Color(0xFF373844) + val DraculaText = Color(0xFFF8F8F2) + val DraculaGrayText = Color(0xFF6272A4) + val DraculaIcon = Color(0xFF6272A4) + + // Lavender Dreams + val LavenderPrimaryGrayBg = Color(0xFFF7EEFC) + val LavenderBlackBg = Color(0xFFFDF0FB) + val LavenderIconGrayBg = Color(0xFFB794F6) + val LavenderBoxItemBg = Color(0xFFF8F5FF) + val LavenderText = Color(0xFF2D1B47) + val LavenderGrayText = Color(0xFF9AB3FF) + val LavenderIcon = Color(0xFF7C3AED) + + // Silent Blue + val SilentBluePrimaryGrayBg = Color(0xFF282F49) + val SilentBlueBlackBg = Color(0xFF151A30) + val SilentBlueIconGrayBg = Color(0xFF3A446A) + val SilentBlueBoxItemBg = Color(0xFF3A446A) + val SilentBlueText = Color(0xFFE0E1F3) + val SilentBlueGrayText = Color(0xFF7B83B0) + val SilentBlueIcon = Color(0xFF7B83B0) +} diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt new file mode 100644 index 000000000..d51c75d08 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt @@ -0,0 +1,162 @@ +package com.lagradost.cloudstream4.theme + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color + +/** + * Maps to the XML custom attrs declared in attrs.xml: + * TODO: Remove this comment when we migrate fully + * and attrs.xml will no longer be used at all. + * + * | XML ?attr | Here | + * |--------------------------|--------------------| + * | primaryBlackBackground | [background] | + * | primaryGrayBackground | [surfaceVariant] | + * | iconGrayBackground | [surface] | + * | boxItemBackground | [surfaceContainer] | + * | textColor | [onBackground] | + * | grayTextColor | [onSurfaceVariant] | + * | iconColor | [icon] | + * | colorPrimary | [primary] | + * | colorOngoing | [ongoing] | + * + * All fields are [MutableState] so Compose recomposes automatically + * if the scheme is swapped at runtime (e.g. user changes theme without restart). + */ +@Stable +class CloudStreamColorScheme( + background: Color, + surfaceVariant: Color, + surface: Color, + surfaceContainer: Color, + onBackground: Color, + onSurfaceVariant: Color, + icon: Color, + primary: Color, + ongoing: Color, + isLight: Boolean, +) { + /** primaryBlackBackground */ + var background by mutableStateOf(background) + + /** primaryGrayBackground */ + var surfaceVariant by mutableStateOf(surfaceVariant) + + /** iconGrayBackground */ + var surface by mutableStateOf(surface) + + /** boxItemBackground */ + var surfaceContainer by mutableStateOf(surfaceContainer) + + /** textColor */ + var onBackground by mutableStateOf(onBackground) + + /** grayTextColor */ + var onSurfaceVariant by mutableStateOf(onSurfaceVariant) + + /** iconColor */ + var icon by mutableStateOf(icon) + + /** colorPrimary */ + var primary by mutableStateOf(primary) + + /** colorOngoing */ + var ongoing by mutableStateOf(ongoing) + var isLight by mutableStateOf(isLight) + + fun copy( + background: Color = this.background, + surfaceVariant: Color = this.surfaceVariant, + surface: Color = this.surface, + surfaceContainer: Color = this.surfaceContainer, + onBackground: Color = this.onBackground, + onSurfaceVariant: Color = this.onSurfaceVariant, + icon: Color = this.icon, + primary: Color = this.primary, + ongoing: Color = this.ongoing, + isLight: Boolean = this.isLight, + ) = CloudStreamColorScheme( + background, surfaceVariant, surface, surfaceContainer, + onBackground, onSurfaceVariant, icon, primary, ongoing, isLight, + ) +} + +internal fun darkScheme() = CloudStreamColorScheme( + background = CloudStreamPalette.DarkBlackBg, + surfaceVariant = CloudStreamPalette.DarkPrimaryGrayBg, + surface = CloudStreamPalette.DarkIconGrayBg, + surfaceContainer = CloudStreamPalette.DarkBoxItemBg, + onBackground = CloudStreamPalette.DarkText, + onSurfaceVariant = CloudStreamPalette.DarkGrayText, + icon = CloudStreamPalette.DarkIcon, + primary = CloudStreamPalette.Primary, + ongoing = CloudStreamPalette.Ongoing, + isLight = false, +) + + +internal fun amoledScheme() = darkScheme().copy( + background = CloudStreamPalette.AmoledBlack, + surface = CloudStreamPalette.AmoledBlack, + surfaceVariant = CloudStreamPalette.AmoledBlack, + surfaceContainer = CloudStreamPalette.AmoledBlack, +) + +internal fun amoledLightScheme() = amoledScheme().copy( + surfaceVariant = CloudStreamPalette.AmoledNearBlack, +) + +internal fun lightScheme() = CloudStreamColorScheme( + background = CloudStreamPalette.LightBlackBg, + surfaceVariant = CloudStreamPalette.LightPrimaryGrayBg, + surface = CloudStreamPalette.LightIconGrayBg, + surfaceContainer = CloudStreamPalette.LightBoxItemBg, + onBackground = CloudStreamPalette.LightText, + onSurfaceVariant = CloudStreamPalette.LightGrayText, + icon = CloudStreamPalette.LightIcon, + primary = CloudStreamPalette.Primary, + ongoing = CloudStreamPalette.Ongoing, + isLight = true, +) + +internal fun draculaScheme() = CloudStreamColorScheme( + background = CloudStreamPalette.DraculaBlackBg, + surfaceVariant = CloudStreamPalette.DraculaPrimaryGrayBg, + surface = CloudStreamPalette.DraculaIconGrayBg, + surfaceContainer = CloudStreamPalette.DraculaBoxItemBg, + onBackground = CloudStreamPalette.DraculaText, + onSurfaceVariant = CloudStreamPalette.DraculaGrayText, + icon = CloudStreamPalette.DraculaIcon, + primary = CloudStreamPalette.Primary, + ongoing = CloudStreamPalette.Ongoing, + isLight = false, +) + +internal fun lavenderScheme() = CloudStreamColorScheme( + background = CloudStreamPalette.LavenderBlackBg, + surfaceVariant = CloudStreamPalette.LavenderPrimaryGrayBg, + surface = CloudStreamPalette.LavenderIconGrayBg, + surfaceContainer = CloudStreamPalette.LavenderBoxItemBg, + onBackground = CloudStreamPalette.LavenderText, + onSurfaceVariant = CloudStreamPalette.LavenderGrayText, + icon = CloudStreamPalette.LavenderIcon, + primary = CloudStreamPalette.Primary, + ongoing = CloudStreamPalette.Ongoing, + isLight = true, +) + +internal fun silentBlueScheme() = CloudStreamColorScheme( + background = CloudStreamPalette.SilentBlueBlackBg, + surfaceVariant = CloudStreamPalette.SilentBluePrimaryGrayBg, + surface = CloudStreamPalette.SilentBlueIconGrayBg, + surfaceContainer = CloudStreamPalette.SilentBlueBoxItemBg, + onBackground = CloudStreamPalette.SilentBlueText, + onSurfaceVariant = CloudStreamPalette.SilentBlueGrayText, + icon = CloudStreamPalette.SilentBlueIcon, + primary = CloudStreamPalette.Primary, + ongoing = CloudStreamPalette.Ongoing, + isLight = false, +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt new file mode 100644 index 000000000..ab1ea1e95 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt @@ -0,0 +1,132 @@ +package com.lagradost.cloudstream4.theme + +import androidx.compose.animation.core.InfiniteTransition +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color + +enum class CloudStreamThemeMode { + /** "Black" standard dark, #111111 backgrounds */ + Dark, + + /** "Amoled" / "AmoledLight" pure black (#000000) */ + Amoled, + + /** "AmoledLight" pure black (#000000) */ + AmoledLight, + + /** "Light" white/gray backgrounds, dark text */ + Light, + + /** "Dracula" */ + Dracula, + + /** "Lavender" */ + Lavender, + + /** "SilentBlue" */ + SilentBlue, + + /** "System" resolved on each platform via [isSystemInDarkTheme] */ + FollowSystem, + + /** + * Uses platform dynamic color system, Material You on Android 12+, + * falls back to [Dark] on unsupported platforms. + */ + Dynamic, +} + +@Composable +fun modeToTheme(mode : CloudStreamThemeMode, primaryColor: CloudStreamPrimaryColor) : CloudStreamColorScheme { + val dynamicTheme = DeviceTheme.resolveDynamicTheme() + val dynamicPrimary = DeviceTheme.resolveDynamicPrimaryColor() + val dynamicSecondary = DeviceTheme.resolveDynamicSecondaryColor() + val systemDark = isSystemInDarkTheme() + val color = remember(mode, primaryColor, systemDark, dynamicTheme, dynamicPrimary, dynamicSecondary) { + val base = when (mode) { + CloudStreamThemeMode.Dark -> darkScheme() + CloudStreamThemeMode.Amoled -> amoledScheme() + CloudStreamThemeMode.AmoledLight -> amoledLightScheme() + CloudStreamThemeMode.Light -> lightScheme() + CloudStreamThemeMode.Dracula -> draculaScheme() + CloudStreamThemeMode.Lavender -> lavenderScheme() + CloudStreamThemeMode.SilentBlue -> silentBlueScheme() + CloudStreamThemeMode.FollowSystem -> if (systemDark) darkScheme() else lightScheme() + CloudStreamThemeMode.Dynamic -> dynamicTheme + } + when { + mode == CloudStreamThemeMode.Dynamic -> base + primaryColor == CloudStreamPrimaryColor.DYNAMIC -> base.copy(primary = dynamicPrimary) + primaryColor == CloudStreamPrimaryColor.DYNAMIC_TWO -> base.copy(primary = dynamicSecondary) + else -> base.copy(primary = primaryColor.color) + } + } + return color +} + +private fun CloudStreamColorScheme.toMaterial3ColorScheme() = if (isLight) { + lightColorScheme( + primary = primary, + background = background, + surface = surface, + surfaceVariant = surfaceVariant, + surfaceContainer = surfaceContainer, + onBackground = onBackground, + onSurface = onBackground, + onSurfaceVariant = onSurfaceVariant, + onPrimary = Color.White, + ) +} else { + darkColorScheme( + primary = primary, + background = background, + surface = surface, + surfaceVariant = surfaceVariant, + surfaceContainer = surfaceContainer, + onBackground = onBackground, + onSurface = onBackground, + onSurfaceVariant = onSurfaceVariant, + onPrimary = Color.White, + ) +} + +val LocalSharedInfiniteTransition = staticCompositionLocalOf { throw NotImplementedError() } +val LocalCloudStreamColors = staticCompositionLocalOf { darkScheme() } + +object CloudStreamTheme { + val colors: CloudStreamColorScheme @Composable @ReadOnlyComposable get() = LocalCloudStreamColors.current + /** Global synchronized animation, so many items in e.g. a LazyList can animate at the same time, + * even if they appeared at different times */ + val transition: InfiniteTransition @Composable @ReadOnlyComposable get() = LocalSharedInfiniteTransition.current +} + +@Composable +fun CloudStreamPreviewTheme(content: @Composable () -> Unit) { + CloudStreamTheme(content = content) +} + +@Composable +fun CloudStreamTheme( + mode: CloudStreamThemeMode = CloudStreamThemeMode.FollowSystem, + primaryColor: CloudStreamPrimaryColor = CloudStreamPrimaryColor.NORMAL, + content: @Composable () -> Unit, +) { + val csColors = modeToTheme(mode, primaryColor) + val globalTransition = rememberInfiniteTransition(label = "GlobalSharedTransition") + CompositionLocalProvider(LocalCloudStreamColors provides csColors, LocalSharedInfiniteTransition provides globalTransition) { + MaterialTheme( + colorScheme = csColors.toMaterial3ColorScheme(), + content = content, + typography = AppFont.typography + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/LICENSE b/shared/src/commonMain/kotlin/com/mihon/LICENSE new file mode 100644 index 000000000..d9a10c0d8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt b/shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt new file mode 100644 index 000000000..5c604e540 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt @@ -0,0 +1,125 @@ +package com.mihon.common.preference + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.stateIn + +/** + * Local-copy implementation of PreferenceStore mostly for test and preview purposes + */ +class InMemoryPreferenceStore( + initialPreferences: Sequence> = sequenceOf(), +) : PreferenceStore { + + private val preferences: Map> = + initialPreferences.toList().associateBy { it.key() } + + override fun getString(key: String, defaultValue: String): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: String? = preferences[key]?.get() as? String + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + override fun getLong(key: String, defaultValue: Long): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: Long? = preferences[key]?.get() as? Long + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + override fun getInt(key: String, defaultValue: Int): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: Int? = preferences[key]?.get() as? Int + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + override fun getFloat(key: String, defaultValue: Float): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: Float? = preferences[key]?.get() as? Float + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + override fun getBoolean(key: String, defaultValue: Boolean): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: Boolean? = preferences[key]?.get() as? Boolean + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + override fun getStringSet(key: String, defaultValue: Set): PreferenceData> { + val default = InMemoryPreference(key, null, defaultValue) + + @Suppress("UNCHECKED_CAST") + val data: Set? = preferences[key]?.get() as? Set + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + @Suppress("UNCHECKED_CAST") + override fun getObjectFromString( + key: String, + defaultValue: T, + serializer: (T) -> String, + deserializer: (String) -> T, + ): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: T? = preferences[key]?.get() as? T + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + @Suppress("UNCHECKED_CAST") + override fun getObjectFromInt( + key: String, + defaultValue: T, + serializer: (T) -> Int, + deserializer: (Int) -> T, + ): PreferenceData { + val default = InMemoryPreference(key, null, defaultValue) + val data: T? = preferences[key]?.get() as? T + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + @Suppress("UNCHECKED_CAST") + override fun getObjectSetFromStringSet( + key: String, + defaultValue: Set, + serializer: (T) -> String, + deserializer: (String) -> T?, + ): PreferenceData> { + val default = InMemoryPreference(key, null, defaultValue) + val data: Set? = preferences[key]?.get() as? Set + return if (data == null) default else InMemoryPreference(key, data, defaultValue) + } + + override fun getAll(): Map { + return preferences + } + + class InMemoryPreference( + private val key: String, + private var data: T?, + private val defaultValue: T, + ) : PreferenceData { + override fun key(): String = key + + override fun get(): T = data ?: defaultValue() + + override fun isSet(): Boolean = data != null + + override fun delete() { + data = null + } + + override fun defaultValue(): T = defaultValue + + override fun changes(): Flow = flow { data } + + override fun stateIn(scope: CoroutineScope): StateFlow { + return changes().stateIn(scope, SharingStarted.Eagerly, get()) + } + + override fun set(value: T) { + data = value + } + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt b/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt new file mode 100644 index 000000000..16aeb41bc --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt @@ -0,0 +1,71 @@ +package com.mihon.common.preference + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface PreferenceData { + + fun key(): String + + fun get(): T + + fun set(value: T) + + fun isSet(): Boolean + + fun delete() + + fun defaultValue(): T + + fun changes(): Flow + + fun stateIn(scope: CoroutineScope): StateFlow + + companion object { + /** + * A preference that should not be exposed in places like backups without user consent. + */ + fun isPrivate(key: String): Boolean { + return key.startsWith(PRIVATE_PREFIX) + } + fun privateKey(key: String): String { + return "$PRIVATE_PREFIX$key" + } + + /** + * A preference used for internal app state that isn't really a user preference + * and therefore should not be in places like backups. + */ + fun isAppState(key: String): Boolean { + return key.startsWith(APP_STATE_PREFIX) + } + fun appStateKey(key: String): String { + return "$APP_STATE_PREFIX$key" + } + + private const val APP_STATE_PREFIX = "__APP_STATE_" + private const val PRIVATE_PREFIX = "__PRIVATE_" + } +} + +inline fun PreferenceData.getAndSet(crossinline block: (T) -> R) = set( + block(get()), +) + +operator fun PreferenceData>.plusAssign(item: T) { + set(get() + item) +} + +operator fun PreferenceData>.plusAssign(items: Iterable) { + set(get() + items) +} + +operator fun PreferenceData>.minusAssign(item: T) { + set(get() - item) +} + +fun PreferenceData.toggle(): Boolean { + set(!get()) + return get() +} diff --git a/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt b/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt new file mode 100644 index 000000000..6075fb45c --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt @@ -0,0 +1,87 @@ +package com.mihon.common.preference + +interface PreferenceStore { + + fun getString(key: String, defaultValue: String = ""): PreferenceData + + fun getLong(key: String, defaultValue: Long = 0): PreferenceData + + fun getInt(key: String, defaultValue: Int = 0): PreferenceData + + fun getFloat(key: String, defaultValue: Float = 0f): PreferenceData + + fun getBoolean(key: String, defaultValue: Boolean = false): PreferenceData + + fun getStringSet(key: String, defaultValue: Set = emptySet()): PreferenceData> + + fun getObjectFromString( + key: String, + defaultValue: T, + serializer: (T) -> String, + deserializer: (String) -> T, + ): PreferenceData + + fun getObjectFromInt( + key: String, + defaultValue: T, + serializer: (T) -> Int, + deserializer: (Int) -> T, + ): PreferenceData + + fun getObjectSetFromStringSet( + key: String, + defaultValue: Set, + serializer: (T) -> String, + deserializer: (String) -> T?, + ): PreferenceData> + + fun getAll(): Map +} + +fun PreferenceStore.getLongArray( + key: String, + defaultValue: List, +): PreferenceData> { + return getObjectFromString( + key = key, + defaultValue = defaultValue, + serializer = { it.joinToString(",") }, + deserializer = { it.split(",").mapNotNull { l -> l.toLongOrNull() } }, + ) +} + +inline fun > PreferenceStore.getEnum( + key: String, + defaultValue: T, +): PreferenceData { + return getObjectFromString( + key = key, + defaultValue = defaultValue, + serializer = { it.name }, + deserializer = { + try { + enumValueOf(it) + } catch (e: IllegalArgumentException) { + defaultValue + } + }, + ) +} + +inline fun > PreferenceStore.getEnumSet( + key: String, + defaultValue: Set, +): PreferenceData> { + return getObjectSetFromStringSet( + key = key, + defaultValue = defaultValue, + serializer = { it.name }, + deserializer = { + try { + enumValueOf(it) + } catch (_: IllegalArgumentException) { + null + } + }, + ) +} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt b/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt new file mode 100644 index 000000000..52fe5af75 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt @@ -0,0 +1,403 @@ +package com.mihon.material + +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.action_bar_up_description +import com.lagradost.cloudstream4.generated.resources.arrow_back +import com.lagradost.cloudstream4.generated.resources.cancel +import com.lagradost.cloudstream4.generated.resources.close +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + +@Composable +fun AppBar( + title: String?, + + modifier: Modifier = Modifier, + backgroundColor: Color? = null, + // Text + subtitle: String? = null, + // Up button + navigateUp: (() -> Unit)? = null, + navigationIcon: Painter? = null, + // Menu + actions: @Composable RowScope.() -> Unit = {}, + // Action mode + actionModeCounter: Int = 0, + onCancelActionMode: () -> Unit = {}, + actionModeActions: @Composable RowScope.() -> Unit = {}, + + scrollBehavior: TopAppBarScrollBehavior? = null, +) { + val isActionMode by remember(actionModeCounter) { + derivedStateOf { actionModeCounter > 0 } + } + + AppBar( + modifier = modifier, + backgroundColor = backgroundColor, + titleContent = { + if (isActionMode) { + AppBarTitle(actionModeCounter.toString()) + } else { + AppBarTitle(title, subtitle = subtitle) + } + }, + navigateUp = navigateUp, + navigationIcon = navigationIcon, + actions = { + if (isActionMode) { + actionModeActions() + } else { + actions() + } + }, + isActionMode = isActionMode, + onCancelActionMode = onCancelActionMode, + scrollBehavior = scrollBehavior, + ) +} + +@Composable +fun AppBar( + // Title + titleContent: @Composable () -> Unit, + + modifier: Modifier = Modifier, + backgroundColor: Color? = null, + // Up button + navigateUp: (() -> Unit)? = null, + navigationIcon: Painter? = null, + // Menu + actions: @Composable RowScope.() -> Unit = {}, + // Action mode + isActionMode: Boolean = false, + onCancelActionMode: () -> Unit = {}, + + scrollBehavior: TopAppBarScrollBehavior? = null, +) { + Column( + modifier = modifier, + ) { + TopAppBar( + navigationIcon = { + if (isActionMode) { + IconButton(onClick = onCancelActionMode) { + Icon( + painter = painterResource(Res.drawable.close), + contentDescription = stringResource(Res.string.cancel), + ) + } + } else { + navigateUp?.let { + IconButton(onClick = it) { + UpIcon(navigationIcon = navigationIcon) + } + } + } + }, + title = titleContent, + actions = actions, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = backgroundColor ?: MaterialTheme.colorScheme.surfaceColorAtElevation( + elevation = if (isActionMode) 3.dp else 0.dp, + ), + ), + scrollBehavior = scrollBehavior, + ) + } +} + +@Composable +fun AppBarTitle( + title: String?, + modifier: Modifier = Modifier, + subtitle: String? = null, +) { + Column(modifier = modifier) { + title?.let { + Text( + text = it, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + subtitle?.let { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.basicMarquee( + repeatDelayMillis = 2_000, + ), + ) + } + } +} + +/* +@Composable +fun AppBarActions( + actions: List, +) { + var showMenu by remember { mutableStateOf(false) } + + actions.filterIsInstance().map { + TooltipBox( + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { + PlainTooltip { + Text(it.title) + } + }, + state = rememberTooltipState(), + focusable = false, + ) { + IconButton( + onClick = it.onClick, + enabled = it.enabled, + ) { + Icon( + imageVector = it.icon, + tint = it.iconTint ?: LocalContentColor.current, + contentDescription = it.title, + ) + } + } + } + + val overflowActions = actions.filterIsInstance() + if (overflowActions.isNotEmpty()) { + TooltipBox( + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { + PlainTooltip { + Text(stringResource(MR.strings.action_menu_overflow_description)) + } + }, + state = rememberTooltipState(), + focusable = false, + ) { + IconButton( + onClick = { showMenu = !showMenu }, + ) { + Icon( + MaterialSymbols.Rounded.MoreVert, + contentDescription = stringResource(MR.strings.action_menu_overflow_description), + ) + } + } + + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + ) { + overflowActions.map { + DropdownMenuItem( + onClick = { + it.onClick() + showMenu = false + }, + text = { Text(it.title, fontWeight = FontWeight.Normal) }, + ) + } + } + } +}*/ + +/* +/** + * @param searchEnabled Set to false if you don't want to show search action. + * @param searchQuery If null, use normal toolbar. + * @param placeholderText If null, [MR.strings.action_search_hint] is used. + */ +@Composable +fun SearchToolbar( + searchQuery: String?, + onChangeSearchQuery: (String?) -> Unit, + modifier: Modifier = Modifier, + titleContent: @Composable () -> Unit = {}, + navigateUp: (() -> Unit)? = null, + searchEnabled: Boolean = true, + placeholderText: String? = null, + onSearch: (String) -> Unit = {}, + onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) }, + actions: @Composable RowScope.() -> Unit = {}, + scrollBehavior: TopAppBarScrollBehavior? = null, + visualTransformation: VisualTransformation = VisualTransformation.None, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val focusRequester = remember { FocusRequester() } + + AppBar( + modifier = modifier, + titleContent = { + if (searchQuery == null) return@AppBar titleContent() + + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current + + val searchAndClearFocus: () -> Unit = f@{ + if (searchQuery.isBlank()) return@f + onSearch(searchQuery) + focusManager.clearFocus() + keyboardController?.hide() + focusManager.moveFocus(FocusDirection.Next) + } + + BasicTextField( + value = searchQuery, + onValueChange = onChangeSearchQuery, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) + .runOnEnterKeyPressed(action = searchAndClearFocus) + .showSoftKeyboard(remember { searchQuery.isEmpty() }) + .clearFocusOnSoftKeyboardHide(), + textStyle = MaterialTheme.typography.titleMedium.copy( + color = MaterialTheme.colorScheme.onBackground, + fontWeight = FontWeight.Normal, + fontSize = 18.sp, + ), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { searchAndClearFocus() }), + singleLine = true, + cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), + visualTransformation = visualTransformation, + interactionSource = interactionSource, + decorationBox = { innerTextField -> + TextFieldDefaults.DecorationBox( + value = searchQuery, + innerTextField = innerTextField, + enabled = true, + singleLine = true, + visualTransformation = visualTransformation, + interactionSource = interactionSource, + placeholder = { + Text( + modifier = Modifier.secondaryItemAlpha(), + text = (placeholderText ?: stringResource(MR.strings.action_search_hint)), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium.copy( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + ), + ) + }, + container = {}, + ) + }, + ) + }, + navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch, + actions = { + key("search") { + val onClick = { onChangeSearchQuery("") } + + if (!searchEnabled) { + // Don't show search action + } else if (searchQuery == null) { + TooltipBox( + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { + PlainTooltip { + Text(stringResource(MR.strings.action_search)) + } + }, + state = rememberTooltipState(), + focusable = false, + ) { + IconButton( + onClick = onClick, + ) { + Icon( + MaterialSymbols.Rounded.Search, + contentDescription = stringResource(MR.strings.action_search), + ) + } + } + } else if (searchQuery.isNotEmpty()) { + TooltipBox( + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { + PlainTooltip { + Text(stringResource(MR.strings.action_reset)) + } + }, + state = rememberTooltipState(), + focusable = false, + ) { + IconButton( + onClick = { + onClick() + focusRequester.requestFocus() + }, + ) { + Icon( + MaterialSymbols.Rounded.Close, + contentDescription = stringResource(MR.strings.action_reset), + ) + } + } + } + } + + key("actions") { actions() } + }, + isActionMode = false, + scrollBehavior = scrollBehavior, + ) +}*/ + +@Composable +fun UpIcon( + modifier: Modifier = Modifier, + navigationIcon: Painter? = null, +) { + val icon = navigationIcon + ?: painterResource(Res.drawable.arrow_back) + Icon( + painter = icon, + contentDescription = stringResource(Res.string.action_bar_up_description), + modifier = modifier, + ) +} +/* +sealed interface AppBar { + sealed interface AppBarAction + + data class Action( + val title: String, + val icon: ImageVector, + val iconTint: Color? = null, + val onClick: () -> Unit, + val enabled: Boolean = true, + ) : AppBarAction + + data class OverflowAction( + val title: String, + val onClick: () -> Unit, + ) : AppBarAction +} +*/ \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Constants.kt b/shared/src/commonMain/kotlin/com/mihon/material/Constants.kt new file mode 100644 index 000000000..2b38b4485 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/Constants.kt @@ -0,0 +1,26 @@ +package com.mihon.material + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.unit.dp + +val topSmallPaddingValues = PaddingValues(top = MaterialTheme.padding.small) + +const val DISABLED_ALPHA = .38f +const val SECONDARY_ALPHA = .78f + +class Padding { + + val extraLarge = 32.dp + + val large = 24.dp + + val medium = 16.dp + + val small = 8.dp + + val extraSmall = 4.dp +} + +val MaterialTheme.padding: Padding + get() = Padding() diff --git a/shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt b/shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt new file mode 100644 index 000000000..123bb34b7 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt @@ -0,0 +1,50 @@ +package com.mihon.material + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp + +@Composable +fun LabeledCheckbox( + label: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + Row( + modifier = modifier + .clip(MaterialTheme.shapes.small) + .fillMaxWidth() + .heightIn(min = 48.dp) + .clickable( + role = Role.Checkbox, + onClick = { + if (enabled) { + onCheckedChange(!checked) + } + }, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + Checkbox( + checked = checked, + onCheckedChange = null, + enabled = enabled, + ) + + Text(text = label) + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Pill.kt b/shared/src/commonMain/kotlin/com/mihon/material/Pill.kt new file mode 100644 index 000000000..44d6c2931 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/Pill.kt @@ -0,0 +1,61 @@ +package com.mihon.material + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp + +@Composable +fun Pill( + text: String, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor: Color = MaterialTheme.colorScheme.onSurface, + style: TextStyle = LocalTextStyle.current, +) { + Surface( + modifier = modifier + .padding(start = 4.dp), + shape = MaterialTheme.shapes.extraLarge, + color = color, + contentColor = contentColor, + ) { + Box( + modifier = Modifier + .padding(6.dp, 1.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = text, + maxLines = 1, + style = style, + ) + } + } +} + +@Composable +fun Pill( + text: String, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor: Color = MaterialTheme.colorScheme.onSurface, + fontSize: TextUnit = LocalTextStyle.current.fontSize, +) { + Pill( + text = text, + modifier = modifier, + color = color, + contentColor = contentColor, + style = LocalTextStyle.current.merge(fontSize = fontSize), + ) +} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt b/shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt new file mode 100644 index 000000000..65c11f193 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt @@ -0,0 +1,85 @@ +package com.mihon.material + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.material3.FabPosition +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScaffoldDefaults +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.contentColorFor +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp + +@Composable +fun Scaffold( + modifier: Modifier = Modifier, + topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior( + rememberTopAppBarState(), + ), + topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {}, + bottomBar: @Composable () -> Unit = {}, + snackbarHost: @Composable () -> Unit = {}, + floatingActionButton: @Composable () -> Unit = {}, + floatingActionButtonPosition: FabPosition = FabPosition.End, + containerColor: Color = MaterialTheme.colorScheme.background, + contentColor: Color = contentColorFor(containerColor), + contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, + content: @Composable (PaddingValues) -> Unit, +) { + var fabHeight by remember { mutableIntStateOf(0) } + val density = LocalDensity.current + + Scaffold( + modifier = modifier.nestedScroll(topBarScrollBehavior.nestedScrollConnection), + topBar = { topBar(topBarScrollBehavior) }, + bottomBar = bottomBar, + snackbarHost = snackbarHost, + floatingActionButton = { + Box(Modifier.onSizeChanged { fabHeight = it.height }) { + floatingActionButton() + } + }, + floatingActionButtonPosition = floatingActionButtonPosition, + containerColor = containerColor, + contentColor = contentColor, + contentWindowInsets = contentWindowInsets, + ) { contentPadding -> + val paddingValues = remember(contentPadding) { + FabAwarePaddingValues(contentPadding) { + if (fabHeight == 0) 0.dp else with(density) { fabHeight.toDp() } + FabSpacing + } + } + content(paddingValues) + } +} + +private class FabAwarePaddingValues( + private val base: PaddingValues, + private val extraBottom: () -> Dp, +) : PaddingValues { + override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = + base.calculateLeftPadding(layoutDirection) + + override fun calculateTopPadding(): Dp = base.calculateTopPadding() + + override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = + base.calculateRightPadding(layoutDirection) + + override fun calculateBottomPadding(): Dp = base.calculateBottomPadding() + extraBottom() +} +private val FabSpacing = 16.dp diff --git a/shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt b/shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt new file mode 100644 index 000000000..ab640e685 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt @@ -0,0 +1,474 @@ +package com.mihon.material + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.FlowRowScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuAnchorType +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream4.compose.whiteOutline +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.arrow_downward +import com.lagradost.cloudstream4.generated.resources.arrow_upward +import com.lagradost.cloudstream4.generated.resources.preview +import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme +import com.mihon.common.preference.PreferenceData +import com.mihon.common.preference.toggle +import com.mihon.presentation.secondaryItemAlpha +import com.mihon.presentation.settings.collectAsState +import com.mihon.presentation.settings.widget.PrefsHorizontalPadding +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + +object SettingsItemsPaddings { + val Horizontal = 24.dp + val Vertical = 10.dp +} + +@Composable +fun HeadingItem(labelRes: StringResource) { + HeadingItem(stringResource(labelRes)) +} + +@Composable +fun HeadingItem(text: String) { + Text( + text = text, + style = MaterialTheme.typography.header, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), + ) +} + +@Composable +fun IconItem(label: String, icon: ImageVector, onClick: () -> Unit) { + BaseSettingsItem( + label = label, + widget = { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + onClick = onClick, + ) +} + +@Composable +fun SortItem(label: String, sortDescending: Boolean?, onClick: () -> Unit) { + val arrowIcon = when (sortDescending) { + true -> painterResource(Res.drawable.arrow_downward) + false -> painterResource(Res.drawable.arrow_upward) + null -> null + } + + BaseSortItem( + label = label, + icon = arrowIcon, + onClick = onClick, + ) +} + +@Composable +fun BaseSortItem(label: String, icon: Painter?, onClick: () -> Unit) { + BaseSettingsItem( + label = label, + widget = { + if (icon != null) { + Icon( + painter = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } else { + Spacer(modifier = Modifier.size(24.dp)) + } + }, + onClick = onClick, + ) +} + +@Composable +fun CheckboxItem(label: String, pref: PreferenceData) { + val checked by pref.collectAsState() + CheckboxItem( + label = label, + checked = checked, + onClick = { pref.toggle() }, + ) +} + +@Composable +fun CheckboxItem(label: String, checked: Boolean, onClick: () -> Unit) { + BaseSettingsItem( + label = label, + widget = { + Checkbox( + checked = checked, + onCheckedChange = null, + ) + }, + onClick = onClick, + ) +} + +@Composable +fun RadioItem(label: String, selected: Boolean, onClick: () -> Unit) { + BaseSettingsItem( + label = label, + widget = { + RadioButton( + selected = selected, + onClick = null, + ) + }, + onClick = onClick, + ) +} + +@Composable +fun SliderItem( + value: Int, + valueRange: IntProgression, + label: String, + onChange: (Int) -> Unit, + steps: Int = with(valueRange) { (last - first) - 1 }, + valueString: String = value.toString(), + labelStyle: TextStyle = MaterialTheme.typography.bodyMedium, + pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, +) { + BaseSliderItem( + value = value, + valueRange = valueRange, + steps = steps, + title = label, + valueString = valueString, + onChange = onChange, + titleStyle = labelStyle, + pillColor = pillColor, + modifier = Modifier.padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), + ) +} + +@Composable +fun BaseSliderItem( + value: Int, + valueRange: IntProgression, + title: String, + onChange: (Int) -> Unit, + modifier: Modifier = Modifier, + subtitle: String? = null, + steps: Int = with(valueRange) { (last - first) - 1 }, + valueString: String = value.toString(), + titleStyle: TextStyle = MaterialTheme.typography.titleLarge, + subtitleStyle: TextStyle = MaterialTheme.typography.bodySmall, + pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + icon: Painter? = null, +) { + var hasFocus by remember { mutableStateOf(false) } + val haptic = LocalHapticFeedback.current + Column( + modifier = Modifier.whiteOutline(hasFocus) + .fillMaxWidth() + .then(modifier), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + if (icon != null) { + Box( + modifier = Modifier.padding(end = PrefsHorizontalPadding) + ) { + Icon( + painter = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onBackground, + ) + } + } + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = titleStyle, + ) + if (subtitle != null) { + Text( + text = subtitle, + style = subtitleStyle, + modifier = Modifier.secondaryItemAlpha(), + ) + } + } + Pill( + text = valueString, + style = MaterialTheme.typography.bodyMedium, + color = pillColor, + ) + } + Slider( + modifier = Modifier.onFocusChanged { newFocus -> + hasFocus = newFocus.hasFocus + }, + value = value, + onValueChange = f@{ + if (it == value) return@f + onChange(it) + haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) + }, + valueRange = valueRange, + steps = steps, + ) + } +} + +@Composable +@PreviewLightDark +fun SliderItemPreview() { + CloudStreamPreviewTheme { + var value by remember { mutableIntStateOf(0) } + Surface { + BaseSliderItem( + value = value, + valueRange = 0..10, + title = "Item per row", + valueString = if (value == 0) "Auto" else value.toString(), + onChange = { value = it }, + modifier = Modifier.padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), + icon = painterResource(Res.drawable.preview) + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SelectItem( + label: String, + options: Array, + selectedIndex: Int, + onSelect: (Int) -> Unit, +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + OutlinedTextField( + modifier = Modifier + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) + .fillMaxWidth() + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), + label = { Text(text = label) }, + value = options[selectedIndex].toString(), + onValueChange = {}, + readOnly = true, + singleLine = true, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon( + expanded = expanded, + ) + }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + + ExposedDropdownMenu( + modifier = Modifier.exposedDropdownSize(), + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + options.forEachIndexed { index, text -> + DropdownMenuItem( + text = { Text(text.toString()) }, + onClick = { + onSelect(index) + expanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } + } + } +} + +/* +@Composable +fun TriStateItem( + label: String, + state: TriState, + enabled: Boolean = true, + onClick: ((TriState) -> Unit)?, +) { + Row( + modifier = Modifier + .clickable( + enabled = enabled && onClick != null, + onClick = { + when (state) { + TriState.DISABLED -> onClick?.invoke(TriState.ENABLED_IS) + TriState.ENABLED_IS -> onClick?.invoke(TriState.ENABLED_NOT) + TriState.ENABLED_NOT -> onClick?.invoke(TriState.DISABLED) + } + }, + ) + .fillMaxWidth() + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large), + ) { + val stateAlpha = if (enabled && onClick != null) 1f else DISABLED_ALPHA + + Icon( + imageVector = when (state) { + TriState.DISABLED -> MaterialSymbols.Rounded.CheckBoxOutlineBlank + TriState.ENABLED_IS -> MaterialSymbols.RoundedFilled.CheckBox + TriState.ENABLED_NOT -> MaterialSymbols.RoundedFilled.DisabledByDefault + }, + contentDescription = null, + tint = if (!enabled || state == TriState.DISABLED) { + MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) + } else { + when (onClick) { + null -> MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA) + else -> MaterialTheme.colorScheme.primary + } + }, + ) + Text( + text = label, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha), + style = MaterialTheme.typography.bodyMedium, + ) + } +}*/ + +@Composable +fun TextItem(label: String, value: String, onChange: (String) -> Unit) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = 4.dp), + label = { Text(text = label) }, + value = value, + onValueChange = onChange, + singleLine = true, + ) +} + +@Composable +fun SettingsChipRow(labelRes: StringResource, content: @Composable FlowRowScope.() -> Unit) { + Column { + HeadingItem(labelRes) + FlowRow( + modifier = Modifier.padding( + start = SettingsItemsPaddings.Horizontal, + top = 0.dp, + end = SettingsItemsPaddings.Horizontal, + bottom = SettingsItemsPaddings.Vertical, + ), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + content = content, + ) + } +} + +@Composable +fun SettingsIconGrid(labelRes: StringResource, content: LazyGridScope.() -> Unit) { + Column { + HeadingItem(labelRes) + LazyVerticalGrid( + columns = GridCells.Adaptive(128.dp), + modifier = Modifier.padding( + start = SettingsItemsPaddings.Horizontal, + end = SettingsItemsPaddings.Horizontal, + bottom = SettingsItemsPaddings.Vertical, + ), + verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + content = content, + ) + } +} + +@Composable +private fun BaseSettingsItem( + label: String, + widget: @Composable RowScope.() -> Unit, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth() + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp), + ) { + widget(this) + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + ) + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Slider.kt b/shared/src/commonMain/kotlin/com/mihon/material/Slider.kt new file mode 100644 index 000000000..105c38ab8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/Slider.kt @@ -0,0 +1,58 @@ +package com.mihon.material + +import androidx.annotation.IntRange +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt + +@Composable +fun Slider( + value: Int, + onValueChange: (Int) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + valueRange: IntProgression = 0..1, + @IntRange(from = 0) steps: Int = with(valueRange) { (last - first) - 1 }, + onValueChangeFinished: (() -> Unit)? = null, + colors: SliderColors = SliderDefaults.colors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + thumb: @Composable (SliderState) -> Unit = { + SliderDefaults.Thumb( + interactionSource = interactionSource, + colors = colors, + enabled = enabled, + thumbSize = DpSize(4.0.dp, 34.0.dp) + ) + }, + track: @Composable (SliderState) -> Unit = { sliderState -> + SliderDefaults.Track( + modifier = Modifier.height(10.dp), + colors = colors, + enabled = enabled, + sliderState = sliderState + ) + }, +) { + Slider( + value = value.toFloat(), + onValueChange = { onValueChange(it.roundToInt()) }, + modifier = modifier, + enabled = enabled, + valueRange = with(valueRange) { first.toFloat()..last.toFloat() }, + steps = steps, + onValueChangeFinished = onValueChangeFinished, + colors = colors, + interactionSource = interactionSource, + thumb = thumb, + track = track, + ) +} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Typography.kt b/shared/src/commonMain/kotlin/com/mihon/material/Typography.kt new file mode 100644 index 000000000..789f81539 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/material/Typography.kt @@ -0,0 +1,14 @@ +package com.mihon.material + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight + +val Typography.header: TextStyle + @Composable + get() = bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontWeight = FontWeight.SemiBold, + ) diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt new file mode 100644 index 000000000..7a60342e9 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt @@ -0,0 +1,120 @@ +package com.mihon.presentation + +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import com.mihon.material.SECONDARY_ALPHA + +@Composable +fun Modifier.selectedBackground(isSelected: Boolean): Modifier { + if (!isSelected) return this + val alpha = if (isSystemInDarkTheme()) 0.16f else 0.22f + val color = MaterialTheme.colorScheme.secondary.copy(alpha = alpha) + return this.drawBehind { drawRect(color) } +} + +fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SECONDARY_ALPHA) + +fun Modifier.clickableNoIndication( + onLongClick: (() -> Unit)? = null, + onClick: () -> Unit, +) = this.combinedClickable( + interactionSource = null, + indication = null, + onLongClick = onLongClick, + onClick = onClick, +) + +/** + * For TextField, the provided [action] will be invoked when + * physical enter key is pressed. + * + * Naturally, the TextField should be set to single line only. + */ +fun Modifier.runOnEnterKeyPressed(action: () -> Unit): Modifier = this.onPreviewKeyEvent { + when (it.key) { + Key.Enter, Key.NumPadEnter -> { + // Physical keyboards generate two event types: + // - KeyDown when the key is pressed + // - KeyUp when the key is released + if (it.type == KeyEventType.KeyDown) { + action() + true + } else { + false + } + } + + else -> false + } +} + +/** + * For TextField on AppBar, this modifier will request focus + * to the element the first time it's composed. + */ +@Composable +fun Modifier.showSoftKeyboard(show: Boolean): Modifier { + if (!show) return this + val focusRequester = remember { FocusRequester() } + var openKeyboard by rememberSaveable { mutableStateOf(show) } + LaunchedEffect(focusRequester) { + if (openKeyboard) { + focusRequester.requestFocus() + openKeyboard = false + } + } + return this.focusRequester(focusRequester) +} + +/* +/** + * For TextField, this modifier will clear focus when soft + * keyboard is hidden. + */ +@Composable +fun Modifier.clearFocusOnSoftKeyboardHide( + onFocusCleared: (() -> Unit)? = null, +): Modifier { + var isFocused by remember { mutableStateOf(false) } + var keyboardShowedSinceFocused by remember { mutableStateOf(false) } + if (isFocused) { + val imeVisible = WindowInsets.isImeVisible + val focusManager = LocalFocusManager.current + LaunchedEffect(imeVisible) { + if (imeVisible) { + keyboardShowedSinceFocused = true + } else if (keyboardShowedSinceFocused) { + focusManager.clearFocus() + onFocusCleared?.invoke() + } + } + } + + return this.onFocusChanged { + if (isFocused != it.isFocused) { + if (isFocused) { + keyboardShowedSinceFocused = false + } + isFocused = it.isFocused + } + } +} +*/ \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt new file mode 100644 index 000000000..5cdb8aada --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt @@ -0,0 +1,9 @@ +package com.mihon.presentation + +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.staticCompositionLocalOf + +/** + * For invoking back press to the parent activity + */ +val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt new file mode 100644 index 000000000..11ad8b984 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt @@ -0,0 +1,198 @@ +package com.mihon.presentation.settings + +import androidx.annotation.IntRange +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.painter.Painter +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.none +import com.mihon.common.preference.PreferenceData +import org.jetbrains.compose.resources.stringResource + +sealed class Preference { + abstract val title: String + abstract val enabled: Boolean + + sealed class PreferenceItem : Preference() { + abstract val subtitle: String? + abstract val icon: Painter? + abstract val onValueChanged: suspend (value: T) -> R + + /** + * A basic [PreferenceItem] that only displays texts. + */ + data class TextPreference( + override val title: String, + override val subtitle: String? = null, + override val enabled: Boolean = true, + val widget: @Composable (() -> Unit)? = null, + val onClick: (() -> Unit)? = null, + override val icon: Painter? = null + ) : PreferenceItem() { + override val onValueChanged: suspend (value: String) -> Unit = {} + } + + /** + * A [PreferenceItem] that provides a two-state toggleable option. + */ + data class SwitchPreference( + val preference: PreferenceData, + override val title: String, + override val subtitle: String? = null, + override val enabled: Boolean = true, + override val onValueChanged: suspend (value: Boolean) -> Boolean = { true }, + override val icon: Painter? = null + ) : PreferenceItem() + + /** + * A [PreferenceItem] that provides a slider to select an integer number. + */ + data class SliderPreference( + val value: Int, + override val title: String, + override val subtitle: String? = null, + val valueString: String? = null, + val valueRange: IntProgression = 0..1, + @IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 }, + override val enabled: Boolean = true, + override val onValueChanged: suspend (value: Int) -> Unit = {}, + override val icon: Painter? = null + ) : PreferenceItem() + + /** + * A [PreferenceItem] that displays a list of entries as a dialog. + */ + @Suppress("UNCHECKED_CAST") + data class ListPreference( + val preference: PreferenceData, + val entries: Map, + override val title: String, + override val subtitle: String? = "%s", + val subtitleProvider: @Composable (value: T, entries: Map) -> String? = + { v, e -> subtitle?.format(e[v]) }, + override val icon: Painter? = null, + override val enabled: Boolean = true, + override val onValueChanged: suspend (value: T) -> Boolean = { true }, + val iconProvider: (@Composable (key: T, value: String) -> Unit)? = null, + ) : PreferenceItem() { + internal fun internalSet(value: Any) = preference.set(value as T) + internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T) + + @Composable + internal fun internalSubtitleProvider(value: Any?, entries: Map) = + subtitleProvider(value as T, entries as Map) + @Composable + internal fun InternalIconProvider(key: Any?, value: String) = + iconProvider?.invoke(key as T, value) + } + + /** + * [ListPreference] but with no connection to a [PreferenceData] + */ + data class BasicListPreference( + val value: String, + val entries: Map, + override val title: String, + override val subtitle: String? = "%s", + val subtitleProvider: @Composable (value: String, entries: Map) -> String? = + { v, e -> subtitle?.format(e[v]) }, + override val icon: Painter? = null, + override val enabled: Boolean = true, + override val onValueChanged: suspend (value: String) -> Unit = {}, + ) : PreferenceItem() + + /** + * A [PreferenceItem] that displays a list of entries as a dialog. + * Multiple entries can be selected at the same time. + */ + @Suppress("UNCHECKED_CAST") + data class MultiSelectListPreference( + val preference: PreferenceData>, + val entries: Map, + override val title: String, + override val subtitle: String? = "%s", + val subtitleProvider: @Composable (value: Set, entries: Map) -> String? = + { v, e -> + val combined = remember(v, e) { + v.mapNotNull { e[it] } + .joinToString() + .takeUnless { it.isBlank() } + } + ?: stringResource(Res.string.none) + subtitle?.format(combined) + }, + override val icon: Painter? = null, + override val enabled: Boolean = true, + override val onValueChanged: suspend (value: Set) -> Boolean = { true }, + ) : PreferenceItem, Boolean>() { + internal fun internalSet(value: Set) = preference.set(value as Set) + internal suspend fun internalOnValueChanged(value: Set) = onValueChanged(value as Set) + + @Composable + internal fun internalSubtitleProvider(value: Set, entries: Map) = + subtitleProvider(value as Set, entries as Map) + } + + /** + * A [PreferenceItem] that shows a EditText in the dialog. + */ + data class EditTextPreference( + val preference: PreferenceData, + override val title: String, + override val subtitle: String? = "%s", + override val enabled: Boolean = true, + override val onValueChanged: suspend (value: String) -> Boolean = { true }, + ) : PreferenceItem() { + override val icon: Painter? = null + } + + /** + * A [PreferenceItem] for individual tracker. + */ + /*data class TrackerPreference( + val tracker: Tracker, + val login: () -> Unit, + val logout: () -> Unit, + ) : PreferenceItem() { + override val title: String = "" + override val enabled: Boolean = true + override val subtitle: String? = null + override val icon: ImageVector? = null + override val onValueChanged: suspend (value: String) -> Unit = {} + }*/ + + data class InfoPreference( + override val title: String, + ) : PreferenceItem() { + override val enabled: Boolean = true + override val subtitle: String? = null + override val icon: Painter? = null + override val onValueChanged: suspend (value: String) -> Unit = {} + } + + data class CustomPreference( + override val title: String, + val content: @Composable () -> Unit, + ) : PreferenceItem() { + override val enabled: Boolean = true + override val subtitle: String? = null + override val icon: Painter? = null + override val onValueChanged: suspend (value: Unit) -> Unit = {} + } + } + + data class PreferenceGroup( + override val title: String, + override val enabled: Boolean = true, + + val preferenceItems: List>, + ) : Preference() +} + +@Composable +fun PreferenceData.collectAsState(): State { + val flow = remember(this) { changes() } + return flow.collectAsState(initial = get()) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt new file mode 100644 index 000000000..151f3cb69 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt @@ -0,0 +1,188 @@ +package com.mihon.presentation.settings + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.structuralEqualityPolicy +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.mihon.presentation.settings.widget.EditTextPreferenceWidget +import com.mihon.presentation.settings.widget.InfoWidget +import com.mihon.presentation.settings.widget.ListPreferenceWidget +import com.mihon.presentation.settings.widget.MultiSelectListPreferenceWidget +import com.mihon.presentation.settings.widget.PrefsHorizontalPadding +import com.mihon.presentation.settings.widget.PrefsVerticalPadding +import com.mihon.presentation.settings.widget.SwitchPreferenceWidget +import com.mihon.presentation.settings.widget.TextPreferenceWidget +import com.mihon.presentation.settings.widget.TitleFontSize +import kotlinx.coroutines.launch +import com.mihon.material.BaseSliderItem + +val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false } +val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp } + +@Composable +fun StatusWrapper( + item: Preference.PreferenceItem<*, *>, + highlightKey: String?, + content: @Composable () -> Unit, +) { + val enabled = item.enabled + val highlighted = item.title == highlightKey + AnimatedVisibility( + visible = enabled, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut(), + content = { + CompositionLocalProvider( + LocalPreferenceHighlighted provides highlighted, + content = content, + ) + }, + ) +} + +@Composable +internal fun PreferenceItem( + item: Preference.PreferenceItem<*, *>, + highlightKey: String?, +) { + val scope = rememberCoroutineScope() + StatusWrapper( + item = item, + highlightKey = highlightKey, + ) { + when (item) { + is Preference.PreferenceItem.SwitchPreference -> { + val value by item.preference.collectAsState() + SwitchPreferenceWidget( + title = item.title, + subtitle = item.subtitle, + icon = item.icon, + checked = value, + onCheckedChanged = { newValue -> + scope.launch { + if (item.onValueChanged(newValue)) { + item.preference.set(newValue) + } + } + }, + ) + } + is Preference.PreferenceItem.SliderPreference -> { + BaseSliderItem( + value = item.value, + valueRange = item.valueRange, + steps = item.steps, + title = item.title, + subtitle = item.subtitle, + valueString = item.valueString.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(), + onChange = { + scope.launch { + item.onValueChanged(it) + } + }, + titleStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize), + modifier = Modifier.padding( + horizontal = PrefsHorizontalPadding, + vertical = PrefsVerticalPadding, + ), + icon = item.icon + ) + } + is Preference.PreferenceItem.ListPreference<*> -> { + val value by item.preference.collectAsState() + ListPreferenceWidget( + value = value, + title = item.title, + subtitle = item.internalSubtitleProvider(value, item.entries), + icon = item.icon, + entries = item.entries, + iconProvider = item::InternalIconProvider, + onValueChange = { newValue -> + scope.launch { + if (item.internalOnValueChanged(newValue!!)) { + item.internalSet(newValue) + } + } + }, + ) + } + is Preference.PreferenceItem.BasicListPreference -> { + ListPreferenceWidget( + value = item.value, + title = item.title, + subtitle = item.subtitleProvider(item.value, item.entries), + icon = item.icon, + entries = item.entries, + onValueChange = { scope.launch { item.onValueChanged(it) } }, + ) + } + is Preference.PreferenceItem.MultiSelectListPreference<*> -> { + val values by item.preference.collectAsState() + MultiSelectListPreferenceWidget( + values = values, + title = item.title, + subtitle = item.internalSubtitleProvider(values, item.entries), + icon = item.icon, + entries = item.entries, + onValuesChange = { newValues -> + scope.launch { + if (item.internalOnValueChanged(newValues)) { + item.internalSet(newValues) + } + } + }, + ) + } + is Preference.PreferenceItem.TextPreference -> { + TextPreferenceWidget( + title = item.title, + subtitle = item.subtitle, + icon = item.icon, + widget = item.widget, + onPreferenceClick = item.onClick, + ) + } + is Preference.PreferenceItem.EditTextPreference -> { + val values by item.preference.collectAsState() + EditTextPreferenceWidget( + title = item.title, + subtitle = item.subtitle, + icon = item.icon, + value = values, + onConfirm = { + val accepted = item.onValueChanged(it) + if (accepted) item.preference.set(it) + accepted + }, + ) + } + /*is Preference.PreferenceItem.TrackerPreference -> { + val isLoggedIn by item.tracker.let { tracker -> + tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn) + } + TrackingPreferenceWidget( + tracker = item.tracker, + isLoggedIn = isLoggedIn, + onClick = { if (isLoggedIn) item.logout() else item.login() }, + ) + }*/ + is Preference.PreferenceItem.InfoPreference -> { + InfoWidget(text = item.title) + } + is Preference.PreferenceItem.CustomPreference -> { + item.content() + } + } + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt new file mode 100644 index 000000000..4e79407c5 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt @@ -0,0 +1,46 @@ +package com.mihon.presentation.settings + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component1 +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component2 +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import com.mihon.material.AppBar +import com.mihon.material.Scaffold + +@Composable +fun PreferenceScaffold( + title: String, + actions: @Composable RowScope.() -> Unit = {}, + onBackPressed: (() -> Unit)? = null, + itemsProvider: @Composable () -> List, +) { + val (top, bottom) = remember { FocusRequester.createRefs() } + + Scaffold( + topBar = { + AppBar( + modifier = Modifier.focusRequester(top).focusProperties { + down = bottom + }, + title = title, + navigateUp = onBackPressed, + actions = actions, + scrollBehavior = it, + ) + }, + content = { contentPadding -> + PreferenceScreen( + modifier = Modifier.focusRequester(bottom).focusProperties { + up = top + }, + items = itemsProvider(), + contentPadding = contentPadding, + ) + }, + ) +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt new file mode 100644 index 000000000..0e99aa9a3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt @@ -0,0 +1,96 @@ +package com.mihon.presentation.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEachIndexed +import com.mihon.presentation.settings.widget.PreferenceGroupHeader +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.seconds + +/** + * Preference Screen composable which contains a list of [Preference] items + * @param items [Preference] items which should be displayed on the preference screen. An item can be a single [PreferenceItem] or a group ([Preference.PreferenceGroup]) + * @param modifier [Modifier] to be applied to the preferenceScreen layout + */ +@Composable +fun PreferenceScreen( + items: List, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), +) { + val state = rememberLazyListState() + val highlightKey = SearchableSettings.highlightKey + if (highlightKey != null) { + LaunchedEffect(Unit) { + val i = items.findHighlightedIndex(highlightKey) + if (i >= 0) { + delay(0.5.seconds) + state.animateScrollToItem(i) + } + SearchableSettings.highlightKey = null + } + } + + LazyColumn( + modifier = modifier, + state = state, + contentPadding = contentPadding, + ) { + items.fastForEachIndexed { i, preference -> + when (preference) { + // Create Preference Group + is Preference.PreferenceGroup -> { + if (!preference.enabled) return@fastForEachIndexed + + item { + Column { + PreferenceGroupHeader(title = preference.title) + } + } + items(preference.preferenceItems) { item -> + PreferenceItem( + item = item, + highlightKey = highlightKey, + ) + } + item { + if (i < items.lastIndex) { + Spacer(modifier = Modifier.height(12.dp)) + } + } + } + + // Create Preference Item + is Preference.PreferenceItem<*, *> -> item { + PreferenceItem( + item = preference, + highlightKey = highlightKey, + ) + } + } + } + } +} + +private fun List.findHighlightedIndex(highlightKey: String): Int { + return flatMap { + if (it is Preference.PreferenceGroup) { + buildList { + add(null) // Header + addAll(it.preferenceItems.map { groupItem -> groupItem.title }) + add(null) // Spacer + } + } else { + listOf(it.title) + } + }.indexOfFirst { it == highlightKey } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt new file mode 100644 index 000000000..c114f0dd0 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt @@ -0,0 +1,39 @@ +package com.mihon.presentation.settings + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import com.lagradost.cloudstream4.compose.Screen +import com.mihon.presentation.LocalBackPress + +interface SearchableSettings : Screen { + @Composable + @ReadOnlyComposable + fun getTitleRes(): String + + @Composable + fun getPreferences(): List + + @Composable + fun RowScope.AppBarAction() { + } + + @Composable + override fun Content() { + val handleBack = LocalBackPress.current + PreferenceScaffold( + title = getTitleRes(), + onBackPressed = if (handleBack != null) handleBack::invoke else null, + actions = { AppBarAction() }, + itemsProvider = { getPreferences() }, + ) + } + + companion object { + // HACK: for the background blipping thingy. + // The title of the target PreferenceItem + // Set before showing the destination screen and reset after + // See BasePreferenceWidget.highlightBackground + var highlightKey: String? = null + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt new file mode 100644 index 000000000..6537ea9a9 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt @@ -0,0 +1,127 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.StartOffset +import androidx.compose.animation.core.StartOffsetType +import androidx.compose.animation.core.repeatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.lagradost.cloudstream4.compose.focusOutline +import com.mihon.presentation.settings.LocalPreferenceHighlighted +import com.mihon.presentation.settings.LocalPreferenceMinHeight +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.seconds + +@Composable +internal fun BasePreferenceWidget( + modifier: Modifier = Modifier, + title: String? = null, + subcomponent: @Composable (ColumnScope.() -> Unit)? = null, + icon: @Composable (() -> Unit)? = null, + onClick: (() -> Unit)? = null, + widget: @Composable (() -> Unit)? = null, +) { + val highlighted = LocalPreferenceHighlighted.current + val minHeight = LocalPreferenceMinHeight.current + Row( + modifier = modifier + .highlightBackground(highlighted) + .focusOutline() + .sizeIn(minHeight = minHeight) + .clickable(enabled = onClick != null, onClick = { onClick?.invoke() }) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + if (icon != null) { + Box( + modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp), + content = { icon() }, + ) + } + Column( + modifier = Modifier + .weight(1f) + .padding(vertical = PrefsVerticalPadding), + ) { + if (!title.isNullOrBlank()) { + Text( + modifier = Modifier.padding(horizontal = PrefsHorizontalPadding), + text = title, + overflow = TextOverflow.Ellipsis, + maxLines = 2, + style = MaterialTheme.typography.titleLarge, + fontSize = TitleFontSize, + ) + } + subcomponent?.invoke(this) + } + if (widget != null) { + Box( + modifier = Modifier.padding(end = PrefsHorizontalPadding), + content = { widget() }, + ) + } + } +} + +@Composable +internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier { + var highlightFlag by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + if (highlighted) { + highlightFlag = true + delay(3.seconds) + highlightFlag = false + } + } + val highlight by animateColorAsState( + targetValue = if (highlightFlag) { + MaterialTheme.colorScheme.surfaceTint.copy(alpha = .12f) + } else { + Color.Transparent + }, + animationSpec = if (highlightFlag) { + repeatable( + iterations = 5, + animation = tween(durationMillis = 200), + repeatMode = RepeatMode.Reverse, + initialStartOffset = StartOffset( + offsetMillis = 600, + offsetType = StartOffsetType.Delay, + ), + ) + } else { + tween(200) + }, + label = "highlight", + ) + return this.background(color = highlight) +} + +internal val TrailingWidgetBuffer = 16.dp +internal val PrefsHorizontalPadding = 16.dp +internal val PrefsVerticalPadding = 16.dp +internal val TitleFontSize = 16.sp diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt new file mode 100644 index 000000000..137318928 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt @@ -0,0 +1,97 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.window.DialogProperties +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.cancel +import com.lagradost.cloudstream4.generated.resources.error +import com.lagradost.cloudstream4.generated.resources.ok +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + +@Composable +fun EditTextPreferenceWidget( + title: String, + subtitle: String?, + icon: Painter?, + value: String, + onConfirm: suspend (String) -> Boolean, +) { + var isDialogShown by remember { mutableStateOf(false) } + + TextPreferenceWidget( + title = title, + subtitle = subtitle?.format(value), + icon = icon, + onPreferenceClick = { isDialogShown = true }, + ) + + if (isDialogShown) { + val scope = rememberCoroutineScope() + val onDismissRequest = { isDialogShown = false } + var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(value)) + } + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(text = title) }, + text = { + OutlinedTextField( + value = textFieldValue, + onValueChange = { textFieldValue = it }, + trailingIcon = { + if (textFieldValue.text.isBlank()) { + Icon(painter = painterResource(Res.drawable.error), contentDescription = null) + } else { + IconButton(onClick = { textFieldValue = TextFieldValue("") }) { + Icon(painter = painterResource(Res.drawable.cancel), contentDescription = null) + } + } + }, + isError = textFieldValue.text.isBlank(), + singleLine = true, + modifier = Modifier.fillMaxWidth(), + ) + }, + properties = DialogProperties( + usePlatformDefaultWidth = true, + ), + confirmButton = { + TextButton( + enabled = textFieldValue.text != value && textFieldValue.text.isNotBlank(), + onClick = { + scope.launch { + if (onConfirm(textFieldValue.text)) { + onDismissRequest() + } + } + }, + ) { + Text(text = stringResource(Res.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(Res.string.cancel)) + } + }, + ) + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt new file mode 100644 index 000000000..3382ebda5 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt @@ -0,0 +1,53 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.info +import com.lagradost.cloudstream4.generated.resources.lorem_content +import com.lagradost.cloudstream4.generated.resources.lorem_title +import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import com.mihon.presentation.secondaryItemAlpha +import com.mihon.material.padding + +@Composable +internal fun InfoWidget(text: String) { + Column( + modifier = Modifier + .padding( + horizontal = PrefsHorizontalPadding, + vertical = MaterialTheme.padding.medium, + ) + .secondaryItemAlpha(), + verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), + ) { + Icon( + painter = painterResource(Res.drawable.info), + contentDescription = null, + ) + Text( + text = text, + style = MaterialTheme.typography.bodySmall, + ) + } +} + +@PreviewLightDark +@Composable +private fun InfoWidgetPreview() { + CloudStreamPreviewTheme { + Surface { + InfoWidget(text = stringResource(Res.string.lorem_content)) + } + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt new file mode 100644 index 000000000..6a573e7e8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt @@ -0,0 +1,110 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.painter.Painter +import com.lagradost.cloudstream4.compose.SingleSelectDialog +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.cancel +import com.lagradost.cloudstream4.generated.resources.ok +import org.jetbrains.compose.resources.stringResource + +@Composable +fun ListPreferenceWidget( + value: T, + title: String, + subtitle: String?, + icon: Painter?, + entries: Map, + onValueChange: (T) -> Unit, + iconProvider: (@Composable (key: T, value: String) -> Unit)? = null +) { + var isDialogShown by remember { mutableStateOf(false) } + + TextPreferenceWidget( + title = title, + subtitle = subtitle, + icon = icon, + onPreferenceClick = { isDialogShown = true }, + ) + + if (isDialogShown) { + SingleSelectDialog( + dismiss = { isDialogShown = false }, + title = title, + entries = entries, + selectedKey = value, + confirm = { key -> + onValueChange(key) + isDialogShown = false + }, + confirmText = stringResource(Res.string.ok), + dismissText = stringResource(Res.string.cancel), + iconProvider = iconProvider + ) + /*AlertDialog( + onDismissRequest = { isDialogShown = false }, + title = { Text(text = title) }, + text = { + Box { + val state = rememberLazyListState() + LazyColumn(state = state) { + entries.forEach { current -> + val isSelected = value == current.key + item { + DialogRow( + label = current.value, + isSelected = isSelected, + onSelected = { + onValueChange(current.key!!) + isDialogShown = false + }, + ) + } + } + } + if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) + if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) + } + }, + confirmButton = { + TextButton(onClick = { isDialogShown = false }) { + Text(text = stringResource(Res.string.cancel)) + } + }, + )*/ + } +} +/* +@Composable +private fun DialogRow( + label: String, + isSelected: Boolean, + onSelected: () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(MaterialTheme.shapes.small) + .selectable( + selected = isSelected, + onClick = { if (!isSelected) onSelected() }, + ) + .fillMaxWidth() + .minimumInteractiveComponentSize(), + ) { + RadioButton( + selected = isSelected, + onClick = null, + ) + Text( + text = label, + style = MaterialTheme.typography.bodyLarge.merge(), + modifier = Modifier.padding(start = 24.dp), + ) + } +} +*/ \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt new file mode 100644 index 000000000..c0a2d735f --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt @@ -0,0 +1,97 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.window.DialogProperties +import com.lagradost.cloudstream4.compose.MultiSelectDialog +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.cancel +import com.lagradost.cloudstream4.generated.resources.ok +import org.jetbrains.compose.resources.stringResource + +@Composable +fun MultiSelectListPreferenceWidget( + values: Set, + title: String, + subtitle: String?, + icon: Painter?, + entries: Map, + onValuesChange: (Set) -> Unit, +) { + var isDialogShown by remember { mutableStateOf(false) } + + TextPreferenceWidget( + title = title, + subtitle = subtitle, + icon = icon, + onPreferenceClick = { isDialogShown = true }, + ) + + if (isDialogShown) { + MultiSelectDialog( + title = title, + entries = entries, + properties = DialogProperties( + usePlatformDefaultWidth = true, + ), + confirmText = stringResource(Res.string.ok), + confirm = { selection -> + onValuesChange(selection) + isDialogShown = false + }, + dismissText = stringResource(Res.string.cancel), + dismiss = { + isDialogShown = false + }, + selectedKeys = values, + ) + + /* + AlertDialog( + onDismissRequest = { isDialogShown = false }, + title = { Text(text = title) }, + text = { + LazyColumn { + entries.forEach { current -> + item { + val isSelected = selected.contains(current.key) + LabeledCheckbox( + label = current.value, + checked = isSelected, + onCheckedChange = { + if (it) { + selected.add(current.key) + } else { + selected.remove(current.key) + } + }, + ) + } + } + } + }, + properties = DialogProperties( + usePlatformDefaultWidth = true, + ), + confirmButton = { + TextButton( + onClick = { + onValuesChange(selected.toMutableSet()) + isDialogShown = false + }, + ) { + Text(text = stringResource(Res.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { isDialogShown = false }) { + Text(text = stringResource(Res.string.cancel)) + } + }, + )*/ + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt new file mode 100644 index 000000000..b7d1197a9 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt @@ -0,0 +1,28 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun PreferenceGroupHeader(title: String) { + Box( + contentAlignment = Alignment.CenterStart, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp, top = 14.dp), + ) { + Text( + text = title, + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.padding(horizontal = PrefsHorizontalPadding), + style = MaterialTheme.typography.bodyMedium, + ) + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt new file mode 100644 index 000000000..2744cdcdc --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt @@ -0,0 +1,73 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.preview +import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme +import org.jetbrains.compose.resources.painterResource + +@Composable +fun SwitchPreferenceWidget( + modifier: Modifier = Modifier, + title: String, + subtitle: String? = null, + icon: Painter? = null, + checked: Boolean = false, + onCheckedChanged: (Boolean) -> Unit, +) { + TextPreferenceWidget( + modifier = modifier, + title = title, + subtitle = subtitle, + icon = icon, + widget = { + Switch( + checked = checked, + onCheckedChange = null, + modifier = Modifier.padding(start = TrailingWidgetBuffer), + ) + }, + onPreferenceClick = { onCheckedChanged(!checked) }, + ) +} + +@PreviewLightDark +@Composable +private fun SwitchPreferenceWidgetPreview() { + CloudStreamPreviewTheme { + Surface { + Column { + SwitchPreferenceWidget( + title = "Text preference with icon", + subtitle = "Text preference summary", + icon = painterResource(Res.drawable.preview) , + checked = true, + onCheckedChanged = {}, + ) + SwitchPreferenceWidget( + title = "Text preference", + subtitle = "Text preference summary", + checked = false, + onCheckedChanged = {}, + ) + SwitchPreferenceWidget( + title = "Text preference no summary", + checked = false, + onCheckedChanged = {}, + ) + SwitchPreferenceWidget( + title = "Another text preference no summary", + checked = false, + onCheckedChanged = {}, + ) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt new file mode 100644 index 000000000..91c19cd6a --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt @@ -0,0 +1,86 @@ +package com.mihon.presentation.settings.widget + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.preview +import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme +import org.jetbrains.compose.resources.painterResource +import com.mihon.presentation.secondaryItemAlpha + +@Composable +fun TextPreferenceWidget( + modifier: Modifier = Modifier, + title: String? = null, + subtitle: String? = null, + icon: Painter? = null, + iconTint: Color = MaterialTheme.colorScheme.onBackground, + widget: @Composable (() -> Unit)? = null, + onPreferenceClick: (() -> Unit)? = null, +) { + BasePreferenceWidget( + modifier = modifier, + title = title, + subcomponent = if (!subtitle.isNullOrBlank()) { + { + Text( + text = subtitle, + modifier = Modifier + .padding(horizontal = PrefsHorizontalPadding) + .secondaryItemAlpha(), + style = MaterialTheme.typography.bodyMedium, + maxLines = 10, + ) + } + } else { + null + }, + icon = if (icon != null) { + { + Icon( + painter = icon, + modifier = Modifier.size(24.dp), + tint = iconTint, + contentDescription = null, + ) + } + } else { + null + }, + onClick = onPreferenceClick, + widget = widget, + ) +} + +@PreviewLightDark +@Composable +private fun TextPreferenceWidgetPreview() { + CloudStreamPreviewTheme { + Surface { + Column { + TextPreferenceWidget( + title = "Text preference with icon", + subtitle = "Text preference summary", + icon = painterResource(Res.drawable.preview), + onPreferenceClick = {}, + ) + TextPreferenceWidget( + title = "Text preference", + subtitle = "Text preference summary", + onPreferenceClick = {}, + ) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/readme.md b/shared/src/commonMain/kotlin/com/mihon/readme.md new file mode 100644 index 000000000..2984058d0 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/readme.md @@ -0,0 +1,4 @@ +Mihon (tachiyomi) has a great codebase for working with compose. +Because they do not provide these features as a lib, we are forced to fork each file we want instead. + +https://github.com/mihonapp/mihon \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt b/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt new file mode 100644 index 000000000..cca2d70d7 --- /dev/null +++ b/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt @@ -0,0 +1,16 @@ +package com.lagradost.cloudstream4 + +import androidx.compose.runtime.Composable +import com.mihon.common.preference.InMemoryPreferenceStore + +/** As this is mostly for testing so far, we can assume we have a shared global for settings */ +internal val settings = AppSettings() + +@Composable +actual fun rememberAppSettings(): AppSettings { + return settings +} + +fun AppSettings() : AppSettings { + return AppSettings(preferences = InMemoryPreferenceStore()) +} diff --git a/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt b/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt new file mode 100644 index 000000000..dc9f4f1ae --- /dev/null +++ b/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt @@ -0,0 +1,19 @@ +package com.lagradost.cloudstream4.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.graphics.Color + +internal actual object DeviceTheme { + @Composable + @ReadOnlyComposable + actual fun resolveDynamicTheme(): CloudStreamColorScheme = darkScheme() + + @Composable + @ReadOnlyComposable + actual fun resolveDynamicPrimaryColor(): Color = CloudStreamPrimaryColor.NORMAL.color + + @Composable + @ReadOnlyComposable + actual fun resolveDynamicSecondaryColor(): Color = CloudStreamPrimaryColor.NORMAL.color +} \ No newline at end of file diff --git a/shared/src/readme.md b/shared/src/readme.md new file mode 100644 index 000000000..8b37a84a0 --- /dev/null +++ b/shared/src/readme.md @@ -0,0 +1,11 @@ +https://kotlinlang.org/docs/multiplatform/compose-multiplatform-create-first-app.html#examine-the-project-structure + +`jvmMain` contains source files for the desktop target, which uses Kotlin/JVM. + +`androidMain` contains Android source files and targets Kotlin/JVM. + +`iosMain` contains Kotlin code for iOS and targets Kotlin/Native. + +`jsMain` contains JavaScript-specific Kotlin code and targets Kotlin/JS. + +`wasmJsMain` contains Wasm-specific Kotlin code and targets Kotlin/Wasm. \ No newline at end of file From 519c1df279206364c56835378bd9ffc129c7ca96 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:01:00 +0200 Subject: [PATCH 103/105] Logcat fixes (#3161) --- .../ui/settings/SettingsUIScreen.kt | 21 +- .../ui/settings/logcat/LogcatDialog.kt | 205 ++++++++++-------- .../lagradost/cloudstream4/compose/Colors.kt | 4 +- .../lagradost/cloudstream4/compose/Dialog.kt | 4 +- 4 files changed, 136 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt index 3e67ed15e..2fce9c582 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt @@ -20,6 +20,7 @@ import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.ui.clear import com.lagradost.cloudstream3.ui.home.HomeChildItemAdapter import com.lagradost.cloudstream3.ui.home.ParentItemAdapter @@ -104,8 +105,10 @@ object SettingsUIScreen : SearchableSettings { }, onValueChanged = { newValue -> settings.ui.primaryColor.set(newValue) // We need to set before we recreate - activity?.recreate() - return@ListPreference true + safe { + activity?.recreate() + } + return@ListPreference false }), Preference.PreferenceItem.ListPreference( preference = settings.ui.theme, @@ -127,8 +130,10 @@ object SettingsUIScreen : SearchableSettings { }, onValueChanged = { newValue -> settings.ui.theme.set(newValue) // We need to set before we recreate - activity?.recreate() - return@ListPreference true + safe { + activity?.recreate() + } + return@ListPreference false }), Preference.PreferenceItem.ListPreference( preference = settings.ui.layout, @@ -139,9 +144,11 @@ object SettingsUIScreen : SearchableSettings { ).toMap().toPersistentMap(), onValueChanged = { newValue -> settings.ui.layout.set(newValue) // We need to set before we recreate - activity?.updateTv() - activity?.recreate() - return@ListPreference true + safe { + activity?.updateTv() + activity?.recreate() + } + return@ListPreference false }), ) ), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt index a157a0cda..73dc7ea58 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt @@ -15,14 +15,22 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component1 +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component2 +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -35,7 +43,8 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager import com.lagradost.cloudstream3.utils.txt -import com.lagradost.cloudstream4.compose.Colors +import com.lagradost.cloudstream4.compose.BlackButton +import com.lagradost.cloudstream4.compose.WhiteButton import com.lagradost.cloudstream4.compose.circle import com.lagradost.cloudstream4.compose.ripple import com.lagradost.cloudstream4.compose.rounded @@ -54,10 +63,12 @@ import java.util.Locale @Composable fun LogcatDialog(dismiss: () -> Unit) { - val list = remember { mutableStateOf(persistentListOf()) } + var isLoading by remember { mutableStateOf(true) } LaunchedEffect(dismiss) { try { + isLoading = true + // https://developer.android.com/studio/command-line/logcat val process = Runtime.getRuntime().exec("logcat --binary -d") val items = arrayListOf() @@ -71,8 +82,11 @@ fun LogcatDialog(dismiss: () -> Unit) { list.value = items.toPersistentList() } catch (e: Exception) { logError(e) // kinda ironic + } finally { + isLoading = false } } + val (dismissFocus, confirmFocus) = remember { FocusRequester.createRefs() } val context = LocalContext.current val scope = rememberCoroutineScope() @@ -83,98 +97,115 @@ fun LogcatDialog(dismiss: () -> Unit) { Text(text = stringResource(R.string.log_cat)) }, text = { - LazyColumn { + if (isLoading) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.onBackground, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + ) + } + LazyColumn( + modifier = Modifier.focusProperties { + start = dismissFocus + end = confirmFocus + } + ) { items(items = list.value) { item -> - LogcatItem(item) + LogcatItem(item, modifier = Modifier.focusProperties { + start = dismissFocus + end = confirmFocus + }) } } }, confirmButton = { - Button( - onClick = { - scope.launch { - withContext(Dispatchers.IO) { - val date = SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.getDefault()).format(Date(currentTimeMillis())) - var fileStream: OutputStream? - try { - fileStream = VideoDownloadManager.setupStream( - context, - "logcat_${date}", - null, - "txt", - false - ).openNew() - fileStream.bufferedWriter() - .use { writer -> - list.value.forEach { - writer.write(it.toString()) - writer.write("\n\n") - } + WhiteButton( + text = stringResource(R.string.sort_save), + modifier = Modifier.focusRequester(confirmFocus) + ) { + scope.launch { + withContext(Dispatchers.IO) { + val date = SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.getDefault()).format( + Date(currentTimeMillis()) + ) + var fileStream: OutputStream? + try { + fileStream = VideoDownloadManager.setupStream( + context, + "logcat_${date}", + null, + "txt", + false + ).openNew() + fileStream.bufferedWriter() + .use { writer -> + list.value.forEach { + writer.write(it.toString()) + writer.write("\n\n") } - dismiss() - } catch (t: Throwable) { - logError(t) - showToast(t.message) - } - /*try { - val date = SimpleDateFormat( - "yyyy_MM_dd_HH_mm", - Locale.getDefault() - ).format( - Date(System.currentTimeMillis()) - ) - - val file = FileHelper.logcat.createFile(context, "logcat_${date}") - ?: throw ErrorLoadingException("Unable to create file") - val stream = file.openOutputStream(append = false) - ?: throw ErrorLoadingException("Unable to create stream") - - stream.bufferedWriter() - .use { writer -> - list.value.forEach { - writer.write(it.toString()) - writer.write("\n\n") - } - } - dismiss() - showToast( - txt( - R.string.logcat_success, - file.absolutePath ?: file.uri.toString() - ), - Toast.LENGTH_LONG - ) - } catch (t: Throwable) { - logError(t) - showToast(t.message) - }*/ + } + dismiss() + } catch (t: Throwable) { + logError(t) + showToast(t.message) } + /*try { + val date = SimpleDateFormat( + "yyyy_MM_dd_HH_mm", + Locale.getDefault() + ).format( + Date(System.currentTimeMillis()) + ) + + val file = FileHelper.logcat.createFile(context, "logcat_${date}") + ?: throw ErrorLoadingException("Unable to create file") + val stream = file.openOutputStream(append = false) + ?: throw ErrorLoadingException("Unable to create stream") + + stream.bufferedWriter() + .use { writer -> + list.value.forEach { + writer.write(it.toString()) + writer.write("\n\n") + } + } + dismiss() + showToast( + txt( + R.string.logcat_success, + file.absolutePath ?: file.uri.toString() + ), + Toast.LENGTH_LONG + ) + } catch (t: Throwable) { + logError(t) + showToast(t.message) + }*/ } - }, colors = Colors.whiteButton - ) { Text(text = stringResource(R.string.sort_save)) } - Button( - onClick = { - clipboardHelper( - txt("Logcat"), - list.value.joinToString(separator = "\n\n") { it.toString() } - ) - }, colors = Colors.whiteButton - ) { Text(text = stringResource(R.string.sort_copy)) } - Button( - onClick = { - try { - Runtime.getRuntime().exec("logcat -c") - } catch (t: Throwable) { - logError(t) - } - dismiss() - }, colors = Colors.whiteButton - ) { Text(text = stringResource(R.string.sort_clear)) } + } + } + + WhiteButton(text = stringResource(R.string.sort_copy)) { + clipboardHelper( + txt("Logcat"), + list.value.joinToString(separator = "\n\n") { it.toString() } + ) + } + WhiteButton(text = stringResource(R.string.sort_clear)) { + try { + Runtime.getRuntime().exec("logcat -c") + } catch (t: Throwable) { + logError(t) + } + dismiss() + } }, dismissButton = { - Button( - onClick = dismiss, colors = Colors.blackButton - ) { Text(text = stringResource(R.string.sort_close)) } + BlackButton( + text = stringResource(R.string.sort_close), + onClick = dismiss, + modifier = Modifier.focusRequester(dismissFocus) + ) }, properties = DialogProperties(usePlatformDefaultWidth = false) ) @@ -182,7 +213,7 @@ fun LogcatDialog(dismiss: () -> Unit) { @Composable -fun LogcatItem(item: LogcatItem) { +fun LogcatItem(item: LogcatItem, modifier: Modifier = Modifier) { val interactionSource = remember { MutableInteractionSource() } val color = when (item.level) { @@ -227,7 +258,7 @@ fun LogcatItem(item: LogcatItem) { ) } Row( - modifier = Modifier + modifier = modifier .height(IntrinsicSize.Min) .fillMaxWidth() .clickable( diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt index d39bffa66..2e009e4e7 100644 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.ReadOnlyComposable import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors object Colors { - val blackButton + internal val blackButton @Composable @ReadOnlyComposable get() = ButtonColors( containerColor = colors.surfaceVariant, contentColor = colors.onBackground, @@ -14,7 +14,7 @@ object Colors { disabledContentColor = colors.onBackground.copy(alpha = 0.9f) ) - val whiteButton + internal val whiteButton @Composable @ReadOnlyComposable get() = ButtonColors( containerColor = colors.onBackground, contentColor = colors.surfaceVariant, diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt index 9dbed5f42..9a6ab32c8 100644 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt @@ -87,8 +87,8 @@ fun ActionDialog( @Composable fun WhiteButton( text: String, - onClick: () -> Unit, modifier: Modifier = Modifier, + onClick: () -> Unit, ) { BaseButton( text = text, @@ -101,8 +101,8 @@ fun WhiteButton( @Composable fun BlackButton( text: String, - onClick: () -> Unit, modifier: Modifier = Modifier, + onClick: () -> Unit, ) { BaseButton( text = text, From fe981345bdad180338e6cee75d59e77a568b96ee Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:10:00 +0200 Subject: [PATCH 104/105] Minor import fix --- .../jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt b/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt index f40cc9f8b..0e1066df1 100644 --- a/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream4 import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.getValue @@ -11,7 +10,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import com.lagradost.cloudstream4.compose.Colors +import com.lagradost.cloudstream4.compose.BlackButton +import com.lagradost.cloudstream4.compose.WhiteButton import com.lagradost.cloudstream4.generated.resources.Res import com.lagradost.cloudstream4.generated.resources.app_name import com.lagradost.cloudstream4.generated.resources.default_icon @@ -37,11 +37,9 @@ fun main() = application { Column { Text("Hello, World!") Row { - Button(onClick = {}, colors = Colors.whiteButton) { - Text("Hello in White") + WhiteButton("Hello in White") { } - Button(onClick = {}, colors = Colors.blackButton) { - Text("Hello in Black") + BlackButton("Hello in Black") { } } var checked by remember { mutableStateOf(false) } From 6b7a81abcedb985a9e538a9d13a82ea410ce090e Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Sat, 12 Sep 2026 01:03:36 +0200 Subject: [PATCH 105/105] Added search to settings (#3167) --- .../ui/account/AccountSelectActivity.kt | 9 +- .../ui/settings/SettingsAccountScreen.kt | 7 +- .../ui/settings/SettingsFragment2.kt | 8 + .../ui/settings/SettingsFragmentScreen.kt | 430 ++++++++++++++++++ .../ui/settings/SettingsGeneralScreen.kt | 55 ++- .../ui/settings/SettingsProviders2.kt | 1 + .../ui/settings/SettingsProvidersScreen.kt | 7 +- .../ui/settings/SettingsUIScreen.kt | 14 +- .../ui/settings/SettingsUpdatesScreen.kt | 9 +- .../ui/settings/logcat/LogcatDialog.kt | 25 +- .../cloudstream3/utils/BaseComposeFragment.kt | 4 +- .../cloudstream3/utils/DataStoreHelper.kt | 7 + .../main/res/drawable/audio_capture_24px.xml | 10 + app/src/main/res/drawable/build_24px.xml | 10 + app/src/main/res/drawable/close_24px.xml | 10 + .../main/res/drawable/description_24px.xml | 10 + app/src/main/res/drawable/encrypted_24px.xml | 10 + .../main/res/drawable/format_paint_24px.xml | 10 + .../res/drawable/keyboard_arrow_left_24px.xml | 11 + .../main/res/navigation/mobile_navigation.xml | 2 +- .../kotlin/com/lagradost/cloudstream4/Main.kt | 43 +- shared/build.gradle.kts | 1 + .../cloudstream4/compose/BackHandler.kt | 42 -- .../composeResources/values/strings.xml | 1 + .../lagradost/cloudstream4/compose/Colors.kt | 18 +- .../lagradost/cloudstream4/compose/Dialog.kt | 39 +- .../cloudstream4/compose/Modifiers.kt | 24 +- .../com/lagradost/cloudstream4/theme/Theme.kt | 19 +- .../kotlin/com/mihon/material/AppBar.kt | 3 + .../settings/PreferenceScaffold.kt | 3 + .../com/mihon/presentation/settings/Search.kt | 158 +++++++ .../settings/widget/BasePreferenceWidget.kt | 2 +- 32 files changed, 850 insertions(+), 152 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt create mode 100644 app/src/main/res/drawable/audio_capture_24px.xml create mode 100644 app/src/main/res/drawable/build_24px.xml create mode 100644 app/src/main/res/drawable/close_24px.xml create mode 100644 app/src/main/res/drawable/description_24px.xml create mode 100644 app/src/main/res/drawable/encrypted_24px.xml create mode 100644 app/src/main/res/drawable/format_paint_24px.xml create mode 100644 app/src/main/res/drawable/keyboard_arrow_left_24px.xml delete mode 100644 shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/BackHandler.kt create mode 100644 shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index ad323c7d1..3990bcc78 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -54,10 +54,15 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback { false ) + val isFromMainActivity = intent.getBooleanExtra( + "isFromMainActivity", + false + ) + // Sometimes we start this activity when we have already logged in // For example when using cloudstreamsearch:// // In those cases we want to just go to the main activity instantly - if (hasLoggedIn && !isEditingFromMainActivity) { + if (hasLoggedIn && !isEditingFromMainActivity && !isFromMainActivity) { navigateToMainActivity() return } @@ -98,7 +103,7 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback { // Don't show account selection if there is only // one account that exists - if (!isEditingFromMainActivity && skipStartup) { + if (!isFromMainActivity && !isEditingFromMainActivity && skipStartup) { val currentAccount = accounts.firstOrNull { it.keyIndex == selectedKeyIndex } if (currentAccount?.lockPin != null) { CommonActivity.init(this) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt index 47dac104d..97890bbfa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt @@ -85,7 +85,12 @@ object SettingsAccountScreen : SearchableSettings, BiometricAuthenticator.Biomet val activity = LocalActivity.current val context = LocalContext.current val hasSecurity = remember(context) { - deviceHasPasswordPinLock(context) + try { + deviceHasPasswordPinLock(context) + } catch (_ : Throwable) { + // e.g preview + false + } } return persistentListOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt new file mode 100644 index 000000000..24867b898 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt @@ -0,0 +1,8 @@ +package com.lagradost.cloudstream3.ui.settings + +import com.lagradost.cloudstream3.utils.BaseComposeFragment +import com.lagradost.cloudstream4.compose.Screen + + +/** Empty glue code to connect the old navigation graph to Compose */ +class SettingsFragment2 : BaseComposeFragment(), Screen by SettingsFragmentScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt new file mode 100644 index 000000000..066db8be8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt @@ -0,0 +1,430 @@ +package com.lagradost.cloudstream3.ui.settings + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.BackHandler +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.SearchBarState +import androidx.compose.material3.SearchBarValue +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.rememberSearchBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.profileImages +import com.lagradost.cloudstream3.utils.GitInfo.currentCommitHash +import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper +import com.lagradost.cloudstream3.utils.UIHelper.navigate +import com.lagradost.cloudstream3.utils.txt +import com.lagradost.cloudstream4.compose.Screen +import com.lagradost.cloudstream4.compose.TV +import com.lagradost.cloudstream4.compose.circle +import com.lagradost.cloudstream4.compose.focusOutline +import com.lagradost.cloudstream4.compose.isLayout +import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme +import com.mihon.material.padding +import com.mihon.presentation.settings.Preference +import com.mihon.presentation.settings.SearchableSettings +import com.mihon.presentation.settings.SettingSearchResults +import com.mihon.presentation.settings.SettingsData +import com.mihon.presentation.settings.widget.TextPreferenceWidget +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.launch +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +object SettingsFragmentScreen : Screen { + val screens = persistentListOf( + SettingsNavigation( + title = R.string.category_general, + navigation = R.id.action_navigation_global_to_navigation_settings_general, + screen = SettingsGeneralScreen, + icon = R.drawable.build_24px, + ), + SettingsNavigation( + title = R.string.category_player, + navigation = R.id.action_navigation_global_to_navigation_settings_player, + screen = SettingsPlayerScreen, + icon = R.drawable.play_arrow_24px, + ), + /*SettingsNavigation( + title = R.string.category_providers, + navigation = R.id.action_navigation_global_to_navigation_settings_providers, + screen = SettingsProvidersScreen, + icon = R.drawable.build_24px, + ),*/ + SettingsNavigation( + title = R.string.category_ui, + navigation = R.id.action_navigation_global_to_navigation_settings_ui, + screen = SettingsUIScreen, + icon = R.drawable.format_paint_24px, + ), + SettingsNavigation( + title = R.string.category_updates, + navigation = R.id.action_navigation_global_to_navigation_settings_updates, + screen = SettingsUpdatesScreen, + icon = R.drawable.mobile_arrow_down_24px, + ), + SettingsNavigation( + title = R.string.category_account, + navigation = R.id.action_navigation_global_to_navigation_settings_account, + screen = SettingsAccountScreen, + icon = R.drawable.encrypted_24px, + ), + SettingsNavigation( + title = R.string.pref_category_extensions, + navigation = R.id.action_navigation_global_to_navigation_settings_extensions, + screen = null, + icon = R.drawable.extension_24px, + subtitle = R.string.add_repository + ), + ) + + data class SettingsNavigation( + val title: Int, + val navigation: Int, + val screen: SearchableSettings?, + val icon: Int, + val subtitle: Int? = null, + ) + + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") + override fun Content() { + val textFieldState = rememberTextFieldState() + val searchBarState = rememberSearchBarState() + + val screen = screens.mapNotNull { item -> + val contents = item.screen?.getPreferences() ?: return@mapNotNull null + SettingsData( + title = stringResource(item.title), + navigation = item.navigation, + contents = contents + ) + }.toPersistentList() + + val outerListState = rememberScrollState() + + val parentFirstScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + return if (delta < 0 && outerListState.canScrollForward) { + val consumed = outerListState.dispatchRawDelta(-delta) + Offset(0f, -consumed) + } else { + Offset.Zero + } + } + } + } + + Scaffold { _ -> + Column(modifier = Modifier.verticalScroll(outerListState)) { + Spacer(modifier = Modifier.height(MaterialTheme.padding.small)) + + val default = DataStoreHelper.getDefaultAccount( + LocalContext.current + ) + val flow by DataStoreHelper.selectedAccountNumberFlow.collectAsState() + val account = remember(flow) { + DataStoreHelper.getCurrentAccount() ?: default + } + + Row( + modifier = Modifier.fillMaxSize().focusOutline().clickable { + activity.navigate( + R.id.accountSelectActivity, + Bundle().apply { putBoolean("isFromMainActivity", true) } + ) + }.padding( + vertical = MaterialTheme.padding.large, + horizontal = MaterialTheme.padding.medium + ) + ) { + val image = + account.customImage ?: profileImages.getOrNull(account.defaultImageIndex) + ?: profileImages.first() + + Box( + modifier = Modifier + .size(50.dp) + .border( + 2.dp, + MaterialTheme.colorScheme.onBackground.copy(alpha = 0.2f), + CircleShape + ) + .circle(), + ) { + AsyncImage( + contentScale = ContentScale.Crop, + model = image, + modifier = Modifier.fillMaxSize(), + contentDescription = null, + ) + } + Spacer(modifier = Modifier.width(MaterialTheme.padding.medium)) + Column { + Text( + text = account.name, + style = MaterialTheme.typography.titleLarge, + ) + Text( + text = stringResource(R.string.title_settings), + style = MaterialTheme.typography.bodyMedium, + ) + } + } + + Spacer(modifier = Modifier.height(MaterialTheme.padding.small)) + SettingsSearch(searchBarState = searchBarState, textFieldState = textFieldState) + Spacer(modifier = Modifier.height(MaterialTheme.padding.small)) + + SettingSearchResults( + nestedScrollConnection = parentFirstScrollConnection, + searchKey = textFieldState.text.toString(), + items = screen, + onItemClick = { item -> + SearchableSettings.highlightKey = item.highlightKey + activity?.navigate(item.navigation) + }, empty = { + Column( + modifier = Modifier + .nestedScroll(parentFirstScrollConnection) + ) { + screens.forEach { settingsTab -> + SettingsTab(settingsTab) + } + BuildStamp() + } + }) + + } + } + } + + @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) + @Composable + fun SettingsSearch(searchBarState: SearchBarState, textFieldState: TextFieldState) { + val scope = rememberCoroutineScope() + val inputField = + @Composable { + SearchBarDefaults.InputField( + textFieldState = textFieldState, + searchBarState = searchBarState, + modifier = Modifier.onFocusChanged { newFocus -> + if (newFocus.hasFocus) { + scope.launch { + searchBarState.animateToExpanded() + } + } + }, + onSearch = { scope.launch { searchBarState.animateToCollapsed() } }, + placeholder = { + Text(modifier = Modifier.clearAndSetSemantics {}, text = stringResource(R.string.search_hint)) + }, + leadingIcon = { + Crossfade( + targetState = searchBarState.targetValue, + label = "leftsearch", + ) { value -> + when (value) { + SearchBarValue.Expanded -> { + IconButton(onClick = { + textFieldState.edit { replace(0, length, "") } + scope.launch { + searchBarState.animateToCollapsed() + } + }) { + Icon( + painter = painterResource(R.drawable.keyboard_arrow_left_24px), + tint = MaterialTheme.colorScheme.onBackground, + contentDescription = null + ) + } + } + + SearchBarValue.Collapsed -> { + IconButton(onClick = { + scope.launch { + searchBarState.animateToExpanded() + } + }) { + Icon( + painter = painterResource(R.drawable.search_icon), + tint = MaterialTheme.colorScheme.onBackground, + contentDescription = null + ) + } + } + } + } + + /*SampleLeadingIcon(searchBarState, scope)*/ + }, + trailingIcon = { + Crossfade( + targetState = searchBarState.targetValue, + label = "rightsearch", + ) { value -> + when (value) { + SearchBarValue.Expanded -> { + IconButton(onClick = { + textFieldState.edit { replace(0, length, "") } + }) { + Icon( + painter = painterResource(R.drawable.close_24px), + tint = MaterialTheme.colorScheme.onBackground, + contentDescription = null + ) + } + } + + SearchBarValue.Collapsed -> { + } + } + } + }, + ) + } + + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp - 12.dp * searchBarState.progress) + .focusOutline(enabled = isLayout(TV), CircleShape) + .onGloballyPositioned { searchBarState.collapsedCoords = it }, + shape = SearchBarDefaults.inputFieldShape, + color = MaterialTheme.colorScheme.surfaceVariant, + tonalElevation = SearchBarDefaults.TonalElevation, + shadowElevation = SearchBarDefaults.ShadowElevation, + content = inputField, + ) + + BackHandler(enabled = searchBarState.targetValue == SearchBarValue.Expanded) { + textFieldState.edit { replace(0, length, "") } + scope.launch { + searchBarState.animateToCollapsed() + } + } + } + + @Composable + fun SettingsTab(settingsTab: SettingsNavigation) { + val pref = settingsTab.screen?.getPreferences() ?: emptyList() + val groups = pref.filterIsInstance() + .filter { it.enabled } + TextPreferenceWidget( + title = stringResource(settingsTab.title), + icon = painterResource(settingsTab.icon), + subtitle = settingsTab.subtitle?.let { stringResource(it) } + ?: groups.joinToString { it.title }) { + // Clear it if we have already set it but navigated back instantly + SearchableSettings.highlightKey = null + activity?.navigate(settingsTab.navigation) + } + } + + @Composable + fun BuildStamp() { + val (commitHash, buildTimestamp) = remember { + val commitHash = activity?.currentCommitHash() ?: "" + val buildTimestamp = SimpleDateFormat.getDateTimeInstance( + DateFormat.LONG, DateFormat.MEDIUM, + Locale.getDefault() + ).apply { + timeZone = TimeZone.getTimeZone("UTC") + }.format(Date(BuildConfig.BUILD_DATE)).replace("UTC", "") + commitHash to buildTimestamp + } + + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(50.dp) + .focusOutline() + .clickable { + clipboardHelper( + txt(R.string.extension_version), + "${BuildConfig.VERSION_NAME} $commitHash $buildTimestamp" + ) + }, + ) { + ProvideTextStyle(MaterialTheme.typography.bodyMedium) { + Text(text = BuildConfig.VERSION_NAME) + if (commitHash != "") { + Text("•") + Text(text = commitHash) + } + Text("•") + Text(text = buildTimestamp) + } + } + } +} + + +@PreviewLightDark +@Composable +fun Preview() { + CloudStreamPreviewTheme { + SettingsFragmentScreen.Content() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt index 7ce49e8e9..b2f20b017 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt @@ -15,17 +15,23 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.lagradost.cloudstream3.APIHolder +import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.UnsafeSSL import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.insecureApp import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.network.initClient +import com.lagradost.cloudstream3.ui.settings.SettingsProvidersScreen.toStringRes import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.isAppRestricted import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.showRequestIgnoreBatteryOptDialog +import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName +import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji import com.lagradost.cloudstream4.AppSettings import com.lagradost.cloudstream4.compose.ActionDialog import com.lagradost.cloudstream4.compose.PHONE @@ -104,18 +110,43 @@ object SettingsGeneralScreen : SearchableSettings { ) } + val default = AllLanguagesName to stringResource(R.string.all_languages_preference) + val languages = APIHolder.apis.withLock { + APIHolder.apis.map { api -> api.lang }.distinct() + }.sortedBy { fromTagToLanguageName(it) ?: it } + return persistentListOf( - Preference.PreferenceItem.BasicListPreference( - value = locale, - entries = appLanguages.associate { (name, code) -> (code to (name to code).nameNextToFlagEmoji()) }, - title = stringResource(R.string.app_language), - icon = painterResource(R.drawable.language_korean_latin_24px), - onValueChanged = { value -> - settings.general.locale.set(value) - activity?.recreate() - }, - subtitleProvider = { v, e -> e[v] ?: getCurrentLocale(LocalContext.current) } - ), + Preference.PreferenceGroup(title = stringResource(R.string.extension_language), preferenceItems = persistentListOf( + Preference.PreferenceItem.BasicListPreference( + value = locale, + entries = appLanguages.associate { (name, code) -> (code to (name to code).nameNextToFlagEmoji()) }, + title = stringResource(R.string.app_language), + icon = painterResource(R.drawable.language_korean_latin_24px), + onValueChanged = { value -> + settings.general.locale.set(value) + activity?.recreate() + }, + subtitleProvider = { v, e -> e[v] ?: getCurrentLocale(LocalContext.current) } + ), + Preference.PreferenceItem.MultiSelectListPreference( + title = stringResource(R.string.provider_lang_settings), + icon = painterResource(R.drawable.plugin_lang), + entries = mapOf(default) + languages.associateWith { lang -> + (getNameNextToFlagEmoji( + lang + ) ?: lang) + }, + preference = settings.provider.extensionLanguages + ), + Preference.PreferenceItem.MultiSelectListPreference( + title = stringResource(R.string.preferred_media_settings), + icon = painterResource(R.drawable.movie_edit_24px), + preference = settings.provider.preferredMedia, + entries = TvType.entries.associate { + it.ordinal.toString() to stringResource(it.toStringRes()) + }), + )), + Preference.PreferenceGroup( title = stringResource(R.string.title_downloads), preferenceItems = persistentListOf( @@ -233,7 +264,7 @@ object SettingsGeneralScreen : SearchableSettings { Preference.PreferenceItem.TextPreference( title = stringResource(R.string.cs3wiki), subtitle = "https://cloudstream.miraheze.org/", - icon = painterResource(R.drawable.baseline_description_24), + icon = painterResource(R.drawable.description_24px), onClick = { CloudStreamApp.openBrowser("https://cloudstream.miraheze.org/") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt index adb44a94d..a0faeca93 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt @@ -3,4 +3,5 @@ import com.lagradost.cloudstream3.utils.BaseComposeFragment import com.mihon.presentation.settings.SearchableSettings /** Empty glue code to connect the old navigation graph to Compose */ +@Deprecated("This setting was merged into other places") class SettingsProviders2 : BaseComposeFragment(), SearchableSettings by SettingsProvidersScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt index 48c305a32..4186994c7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt @@ -17,6 +17,7 @@ import com.mihon.presentation.settings.Preference import com.mihon.presentation.settings.SearchableSettings import kotlinx.collections.immutable.persistentListOf +@Deprecated("This setting was merged into other places") object SettingsProvidersScreen : SearchableSettings { @Composable override fun getTitleRes(): String = stringResource(R.string.category_providers) @@ -42,7 +43,6 @@ object SettingsProvidersScreen : SearchableSettings { TvType.Video -> R.string.video_singular } - // TODO Move this into general and layout? @Composable override fun getPreferences(): List { val settings = rememberAppSettings() @@ -68,9 +68,10 @@ object SettingsProvidersScreen : SearchableSettings { preference = settings.provider.preferredMedia, entries = TvType.entries.associate { it.ordinal.toString() to stringResource(it.toStringRes()) - }), Preference.PreferenceItem.MultiSelectListPreference( + }), + Preference.PreferenceItem.MultiSelectListPreference( title = stringResource(R.string.display_subbed_dubbed_settings), - icon = painterResource(R.drawable.ic_outline_voice_over_off_24), + icon = painterResource(R.drawable.audio_capture_24px), preference = settings.provider.displayDubSub, entries = mapOf( DubStatus.None.name to stringResource(R.string.none), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt index 2fce9c582..5c56cb515 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -17,6 +18,7 @@ import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality @@ -31,7 +33,6 @@ import com.lagradost.cloudstream4.compose.TV import com.lagradost.cloudstream4.compose.isLayout import com.lagradost.cloudstream4.rememberAppSettings import com.lagradost.cloudstream4.theme.CloudStreamPrimaryColor -import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors import com.lagradost.cloudstream4.theme.modeToTheme import com.lagradost.cloudstream4.theme.perfToColor import com.lagradost.cloudstream4.theme.perfToMode @@ -71,7 +72,7 @@ object SettingsUIScreen : SearchableSettings { modifier = Modifier .padding(start = 15.dp) .size(20.dp) - .border(width = 1.5.dp, shape = CircleShape, color = colors.onBackground) + .border(width = 1.5.dp, shape = CircleShape, color = MaterialTheme.colorScheme.onBackground) .background(color, CircleShape) ) } @@ -241,6 +242,15 @@ object SettingsUIScreen : SearchableSettings { title = stringResource(R.string.pref_filter_search_quality), icon = painterResource(R.drawable.filter_alt_24px), entries = SearchQuality.entries.associateWith { stringResource(it.toStringRes()) }), + Preference.PreferenceItem.MultiSelectListPreference( + title = stringResource(R.string.display_subbed_dubbed_settings), + icon = painterResource(R.drawable.audio_capture_24px), + preference = settings.provider.displayDubSub, + entries = mapOf( + DubStatus.None.name to stringResource(R.string.none), + DubStatus.Dubbed.name to stringResource(R.string.app_dubbed_text), + DubStatus.Subbed.name to stringResource(R.string.app_subbed_text), + )), Preference.PreferenceItem.SwitchPreference( preference = settings.ui.bottomTitle, title = stringResource(R.string.bottom_title_settings), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt index dc03174ff..3c90e8e36 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt @@ -178,7 +178,7 @@ object SettingsUpdatesScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( title = stringResource(R.string.automatic_plugin_download), - subtitle = "%s\n"+stringResource(R.string.automatic_plugin_download_summary), + subtitle = "%s\n" + stringResource(R.string.automatic_plugin_download_summary), icon = painterResource(R.drawable.extention_renew2), entries = AutoDownloadMode.entries.map { it.value }.sorted() .zip(stringArrayResource((R.array.auto_download_plugin))).toMap(), @@ -196,6 +196,13 @@ object SettingsUpdatesScreen : SearchableSettings { } } ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.test_extensions), + subtitle = stringResource(R.string.test_extensions_summary), + icon = painterResource(R.drawable.baseline_network_ping_24), + onClick = { + activity?.navigate(R.id.navigation_test_providers) + }) ) ), Preference.PreferenceGroup( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt index 73dc7ea58..7d5ce2313 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt @@ -46,9 +46,7 @@ import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream4.compose.BlackButton import com.lagradost.cloudstream4.compose.WhiteButton import com.lagradost.cloudstream4.compose.circle -import com.lagradost.cloudstream4.compose.ripple import com.lagradost.cloudstream4.compose.rounded -import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Dispatchers @@ -91,7 +89,7 @@ fun LogcatDialog(dismiss: () -> Unit) { val context = LocalContext.current val scope = rememberCoroutineScope() AlertDialog( - containerColor = colors.background, + containerColor = MaterialTheme.colorScheme.background, onDismissRequest = dismiss, title = { Text(text = stringResource(R.string.log_cat)) @@ -214,8 +212,6 @@ fun LogcatDialog(dismiss: () -> Unit) { @Composable fun LogcatItem(item: LogcatItem, modifier: Modifier = Modifier) { - val interactionSource = remember { MutableInteractionSource() } - val color = when (item.level) { LogcatLevel.Fatal -> Color.Magenta LogcatLevel.Error -> Color.Red @@ -233,9 +229,9 @@ fun LogcatItem(item: LogcatItem, modifier: Modifier = Modifier) { modifier = Modifier .padding(2.dp) .rounded() - .background(colors.onBackground) + .background(MaterialTheme.colorScheme.onBackground) .padding(4.dp), - color = colors.surfaceVariant + color = MaterialTheme.colorScheme.surfaceVariant ) } Text( @@ -243,32 +239,29 @@ fun LogcatItem(item: LogcatItem, modifier: Modifier = Modifier) { modifier = Modifier .padding(2.dp) .rounded() - .background(colors.surfaceVariant) + .background(MaterialTheme.colorScheme.surfaceVariant) .padding(4.dp), - color = colors.onBackground + color = MaterialTheme.colorScheme.onBackground ) Text( item.tag, modifier = Modifier .padding(2.dp) .rounded() - .background(colors.surfaceVariant) + .background(MaterialTheme.colorScheme.surfaceVariant) .padding(4.dp), - color = colors.onBackground + color = MaterialTheme.colorScheme.onBackground ) } Row( modifier = modifier .height(IntrinsicSize.Min) .fillMaxWidth() + .rounded() .clickable( - interactionSource = interactionSource, - indication = null, onClick = { clipboardHelper(txt("Logcat"), item.toString()) }) - .rounded() - .ripple(interactionSource) .padding(5.dp) ) { Box( @@ -281,7 +274,7 @@ fun LogcatItem(item: LogcatItem, modifier: Modifier = Modifier) { Spacer(modifier = Modifier.width(5.dp)) Text( item.message, - color = colors.onBackground, + color = MaterialTheme.colorScheme.onBackground, fontSize = 14.sp, lineHeight = 15.sp, ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt index 3c3f55adb..91a75d0ab 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt @@ -22,6 +22,7 @@ abstract class BaseComposeFragment : Fragment(), Screen { ): View = createComposeView(inflater, container, savedInstanceState) override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) this.view?.let { view -> fixSystemBarsPadding( view, @@ -29,15 +30,14 @@ abstract class BaseComposeFragment : Fragment(), Screen { padBottom = isLandscape() ) } - super.onConfigurationChanged(newConfig) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) fixSystemBarsPadding( view, padLeft = isLayout(TV or EMULATOR), padBottom = isLandscape() ) - super.onViewCreated(view, savedInstanceState) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 340266f6c..eabac20d3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -33,6 +33,8 @@ import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia import com.lagradost.cloudstream3.utils.downloader.DownloadObjects import com.lagradost.cloudstream3.utils.serializers.WriteOnlySerializer +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KeepGeneratedSerializer import kotlinx.serialization.SerialName @@ -180,6 +182,10 @@ object DataStoreHelper { var selectedKeyIndex by PreferenceDelegate("$TAG/account_key_index", 0) val currentAccount: String get() = selectedKeyIndex.toString() + private val _selectedAccountNumberFlow = MutableStateFlow(0) + /** What account instance we are on, this number changes whenever anything about local accounts changes */ + val selectedAccountNumberFlow : StateFlow = _selectedAccountNumberFlow + /** * Get or set the current account homepage. * Setting this does not automatically reload the homepage. @@ -207,6 +213,7 @@ object DataStoreHelper { // This is not a new account, and the homepage has changed, reload it MainActivity.reloadHomeEvent(true) } + _selectedAccountNumberFlow.value += 1 } fun getDefaultAccount(context: Context): Account { diff --git a/app/src/main/res/drawable/audio_capture_24px.xml b/app/src/main/res/drawable/audio_capture_24px.xml new file mode 100644 index 000000000..a9ed40e02 --- /dev/null +++ b/app/src/main/res/drawable/audio_capture_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/build_24px.xml b/app/src/main/res/drawable/build_24px.xml new file mode 100644 index 000000000..0616691da --- /dev/null +++ b/app/src/main/res/drawable/build_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/close_24px.xml b/app/src/main/res/drawable/close_24px.xml new file mode 100644 index 000000000..77be1d01e --- /dev/null +++ b/app/src/main/res/drawable/close_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/description_24px.xml b/app/src/main/res/drawable/description_24px.xml new file mode 100644 index 000000000..f55b9ad85 --- /dev/null +++ b/app/src/main/res/drawable/description_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/encrypted_24px.xml b/app/src/main/res/drawable/encrypted_24px.xml new file mode 100644 index 000000000..66231650a --- /dev/null +++ b/app/src/main/res/drawable/encrypted_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/format_paint_24px.xml b/app/src/main/res/drawable/format_paint_24px.xml new file mode 100644 index 000000000..b27cf246e --- /dev/null +++ b/app/src/main/res/drawable/format_paint_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/keyboard_arrow_left_24px.xml b/app/src/main/res/drawable/keyboard_arrow_left_24px.xml new file mode 100644 index 000000000..b4b89a922 --- /dev/null +++ b/app/src/main/res/drawable/keyboard_arrow_left_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 970850882..6a1ea6912 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -356,7 +356,7 @@ Unit) { - // Safely update the current `onBack` lambda when a new one is provided - val currentOnBack by rememberUpdatedState(onBack) - // Remember in Composition a back callback that calls the `onBack` lambda - val backCallback = remember { - object : OnBackPressedCallback(enabled) { - override fun handleOnBackPressed() { - currentOnBack() - } - } - } - // On every successful composition, update the callback with the `enabled` value - SideEffect { - backCallback.isEnabled = enabled - } - val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { - "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" - }.onBackPressedDispatcher - val lifecycleOwner = LocalLifecycleOwner.current - DisposableEffect(lifecycleOwner, backDispatcher) { - // Add callback to the backDispatcher - backDispatcher.addCallback(lifecycleOwner, backCallback) - // When the effect leaves the Composition, remove the callback - onDispose { - backCallback.remove() - } - } -} \ No newline at end of file diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml index 3e6f4f768..fbd98f649 100644 --- a/shared/src/commonMain/composeResources/values/strings.xml +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -1,6 +1,7 @@ CloudStream None + No results found Ok Cancel Navigate up diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt index 2e009e4e7..6a363909f 100644 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt @@ -1,24 +1,24 @@ package com.lagradost.cloudstream4.compose import androidx.compose.material3.ButtonColors +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable -import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors object Colors { internal val blackButton @Composable @ReadOnlyComposable get() = ButtonColors( - containerColor = colors.surfaceVariant, - contentColor = colors.onBackground, - disabledContainerColor = colors.surface.copy(alpha = 0.9f), - disabledContentColor = colors.onBackground.copy(alpha = 0.9f) + containerColor = MaterialTheme.colorScheme.surfaceVariant, + contentColor = MaterialTheme.colorScheme.onBackground, + disabledContainerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f), + disabledContentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.9f) ) internal val whiteButton @Composable @ReadOnlyComposable get() = ButtonColors( - containerColor = colors.onBackground, - contentColor = colors.surfaceVariant, - disabledContainerColor = colors.onBackground.copy(alpha = 0.9f), - disabledContentColor = colors.surface.copy(alpha = 0.9f) + containerColor = MaterialTheme.colorScheme.onBackground, + contentColor = MaterialTheme.colorScheme.surfaceVariant, + disabledContainerColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.9f), + disabledContentColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f) ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt index 9a6ab32c8..05948e457 100644 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt @@ -2,15 +2,12 @@ package com.lagradost.cloudstream4.compose import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Image -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialogDefaults @@ -19,7 +16,6 @@ import androidx.compose.material3.ButtonColors import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -36,7 +32,6 @@ import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Dp @@ -44,7 +39,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import com.lagradost.cloudstream4.generated.resources.Res import com.lagradost.cloudstream4.generated.resources.check -import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors import com.mihon.material.padding import org.jetbrains.compose.resources.painterResource @@ -61,7 +55,7 @@ fun ActionDialog( confirm: () -> Unit ) { AlertDialog( - containerColor = colors.background, + containerColor = MaterialTheme.colorScheme.background, onDismissRequest = dismiss, title = { if (icon == null) { @@ -118,13 +112,14 @@ fun BaseButton( modifier: Modifier = Modifier, ) { var hasFocus by remember { mutableStateOf(false) } + val canHaveFocus = LocalFocusOutlineDefault.current Button( onClick = onClick, colors = buttonColors, modifier = modifier.onFocusChanged { newFocus -> - hasFocus = newFocus.hasFocus + hasFocus = canHaveFocus && newFocus.hasFocus }, - border = if (hasFocus) BorderStroke(1.dp, colors.onBackground) else null + border = if (hasFocus) BorderStroke(1.dp, MaterialTheme.colorScheme.onBackground) else null ) { AnimatedVisibility(hasFocus) { Icon( @@ -142,7 +137,7 @@ fun BaseButton( fun SingleSelectDialog( modifier: Modifier = Modifier, shape: Shape = AlertDialogDefaults.shape, - containerColor: Color = colors.background, + containerColor: Color = MaterialTheme.colorScheme.background, iconContentColor: Color = AlertDialogDefaults.iconContentColor, tonalElevation: Dp = AlertDialogDefaults.TonalElevation, icon: @Composable (() -> Unit)? = null, @@ -180,7 +175,7 @@ fun SingleSelectDialog( fun SingleSelectDialog( modifier: Modifier = Modifier, shape: Shape = AlertDialogDefaults.shape, - containerColor: Color = colors.background, + containerColor: Color = MaterialTheme.colorScheme.background, iconContentColor: Color = AlertDialogDefaults.iconContentColor, tonalElevation: Dp = AlertDialogDefaults.TonalElevation, icon: @Composable (() -> Unit)? = null, @@ -353,19 +348,13 @@ fun SingleSelectionItem( selectedPainter: Painter, onClick: () -> Unit, ) { - val interactionSource = remember { MutableInteractionSource() } Row( modifier = modifier .fillMaxWidth() - .combinedClickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick, - onLongClick = { - } - ) .rounded() - .ripple(interactionSource), + .clickable( + onClick = onClick, + ), verticalAlignment = Alignment.CenterVertically ) { if (isSelected) { @@ -373,13 +362,13 @@ fun SingleSelectionItem( painter = selectedPainter, contentDescription = null, modifier = Modifier.size(24.dp), - tint = colors.onBackground + tint = MaterialTheme.colorScheme.onBackground ) iconProvider?.invoke(key, text) Text( text = text, style = MaterialTheme.typography.bodyLarge, - color = colors.onBackground, + color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.padding(15.dp) ) } else { @@ -388,7 +377,7 @@ fun SingleSelectionItem( Text( text = text, style = MaterialTheme.typography.bodyLarge, - color = colors.onSurfaceVariant, + color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(15.dp) ) } @@ -400,7 +389,7 @@ fun SingleSelectionItem( fun MultiSelectDialog( modifier: Modifier = Modifier, shape: Shape = AlertDialogDefaults.shape, - containerColor: Color = colors.background, + containerColor: Color = MaterialTheme.colorScheme.background, iconContentColor: Color = AlertDialogDefaults.iconContentColor, tonalElevation: Dp = AlertDialogDefaults.TonalElevation, icon: @Composable (() -> Unit)? = null, diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt index d2bc48b46..ee910dd36 100644 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt @@ -1,32 +1,31 @@ package com.lagradost.cloudstream4.compose import androidx.compose.foundation.border -import androidx.compose.foundation.indication -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream4.theme.CloudStreamTheme.colors +/* @Composable fun Modifier.ripple( interactionSource: MutableInteractionSource, bounded: Boolean = true, ): Modifier = indication( interactionSource = interactionSource, - indication = ripple(bounded = bounded, color = colors.onBackground), -) + indication = ripple(bounded = bounded, color = MaterialTheme.colorScheme.onBackground), +)*/ // no dimens.xml allowed in compose :/ fun RoundedShape() = RoundedCornerShape(10.dp) @@ -40,8 +39,19 @@ fun Modifier.rounded(): Modifier = fun Modifier.circle(): Modifier = clip(CircleShape) +/** + * If we should have outlines on items when focused by default, this should be false for desktop + * as we do not use a dpad for that + * */ +val LocalFocusOutlineDefault: ProvidableCompositionLocal = staticCompositionLocalOf { true } + @Composable -fun Modifier.focusOutline(shape: Shape = RoundedShape()): Modifier { +fun Modifier.focusOutline( + enabled : Boolean = LocalFocusOutlineDefault.current, + shape: Shape = RoundedShape() +): Modifier { + if(!enabled) return this + var hasFocus by remember { mutableStateOf(false) } return this.onFocusChanged { newFocus -> diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt index ab1ea1e95..196d8d6a1 100644 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt +++ b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt @@ -99,15 +99,15 @@ private fun CloudStreamColorScheme.toMaterial3ColorScheme() = if (isLight) { ) } -val LocalSharedInfiniteTransition = staticCompositionLocalOf { throw NotImplementedError() } -val LocalCloudStreamColors = staticCompositionLocalOf { darkScheme() } +internal val LocalSharedInfiniteTransition = staticCompositionLocalOf { throw NotImplementedError() } -object CloudStreamTheme { - val colors: CloudStreamColorScheme @Composable @ReadOnlyComposable get() = LocalCloudStreamColors.current - /** Global synchronized animation, so many items in e.g. a LazyList can animate at the same time, - * even if they appeared at different times */ - val transition: InfiniteTransition @Composable @ReadOnlyComposable get() = LocalSharedInfiniteTransition.current -} +/** + * Global synchronized animation, so many items in e.g. a LazyList can animate at the same time, + * even if they appeared at different times + * */ +@Composable +@ReadOnlyComposable +fun MaterialTheme.infiniteSharedTransition() = LocalSharedInfiniteTransition.current @Composable fun CloudStreamPreviewTheme(content: @Composable () -> Unit) { @@ -122,7 +122,8 @@ fun CloudStreamTheme( ) { val csColors = modeToTheme(mode, primaryColor) val globalTransition = rememberInfiniteTransition(label = "GlobalSharedTransition") - CompositionLocalProvider(LocalCloudStreamColors provides csColors, LocalSharedInfiniteTransition provides globalTransition) { + // We do not provide csColors as a global, because people should use MaterialTheme directly instead + CompositionLocalProvider(LocalSharedInfiniteTransition provides globalTransition) { MaterialTheme( colorScheme = csColors.toMaterial3ColorScheme(), content = content, diff --git a/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt b/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt index 52fe5af75..e54441595 100644 --- a/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt +++ b/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt @@ -3,6 +3,7 @@ package com.mihon.material import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -99,6 +100,8 @@ fun AppBar( modifier = modifier, ) { TopAppBar( + // TODO @WindowInsets remove this when we have converted everything to compose + windowInsets = WindowInsets(0, 0, 0, 0), navigationIcon = { if (isActionMode) { IconButton(onClick = onCancelActionMode) { diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt index 4e79407c5..4f97d9f2b 100644 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt @@ -1,6 +1,7 @@ package com.mihon.presentation.settings import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -22,6 +23,8 @@ fun PreferenceScaffold( val (top, bottom) = remember { FocusRequester.createRefs() } Scaffold( + // TODO @WindowInsets remove this when we have converted everything to compose + contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = { AppBar( modifier = Modifier.focusRequester(top).focusProperties { diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt new file mode 100644 index 000000000..d221fc801 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt @@ -0,0 +1,158 @@ +package com.mihon.presentation.settings + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.retain.retain +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.lagradost.cloudstream4.generated.resources.Res +import com.lagradost.cloudstream4.generated.resources.no_results_found +import com.mihon.presentation.secondaryItemAlpha +import com.mihon.presentation.settings.widget.TextPreferenceWidget +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList +import org.jetbrains.compose.resources.stringResource + +private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean): String { + return if (node == null) { + path + } else { + if (isLtr) { + // This locale reads left to right. + "$path > $node" + } else { + // This locale reads right to left. + "$node < $path" + } + } +} + +data class SettingsData( + val title: String, + val contents: List, + val navigation: T, +) + +data class SearchResultItem( + val title: String, + val breadcrumbs: String, + val highlightKey: String, + val navigation: T, +) + +@Composable +fun SettingSearchResults( + searchKey: String, + onItemClick: (SearchResultItem) -> Unit, + items: ImmutableList>, + nestedScrollConnection: NestedScrollConnection, + empty: @Composable () -> Unit +) { + val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr + + // We want to retain the results as backpress should not re-calc it + var result by retain>?>> { + mutableStateOf(null) + } + + LaunchedEffect(searchKey) { + if (searchKey.isEmpty()) { + result = null + return@LaunchedEffect + } + result = items.asSequence() + .flatMap { settingsData -> + settingsData.contents.asSequence() + // Only search from enabled prefs and one with valid title + .filter { it.enabled && it.title.isNotBlank() } + // Flatten items contained inside *enabled* PreferenceGroup + .flatMap { p -> + when (p) { + is Preference.PreferenceGroup -> { + if (p.enabled) { + p.preferenceItems.asSequence() + .filter { it.enabled && it.title.isNotBlank() } + .map { p.title to it } + } else { + emptySequence() + } + } + + is Preference.PreferenceItem<*, *> -> sequenceOf(null to p) + } + } + // Don't show info preference + .filterNot { it.second is Preference.PreferenceItem.InfoPreference } + // Filter by search query + .filter { (_, p) -> + val inTitle = p.title.contains(searchKey, true) + val inSummary = p.subtitle?.contains(searchKey, true) ?: false + inTitle || inSummary // TODO filter out %s ? This is a bug-ish in mihon + } + // Map result data + .map { (categoryTitle, p) -> + SearchResultItem( + navigation = settingsData.navigation, + title = p.title, + breadcrumbs = getLocalizedBreadcrumb( + path = settingsData.title, + node = categoryTitle, + isLtr = isLtr, + ), + highlightKey = p.title, + ) + } + } + .take(10) // Just take top 10 result for quicker result + .toPersistentList() + } + + Crossfade( + targetState = result, + label = "results", + ) { + when { + it == null -> empty() + + it.isEmpty() -> { + Text( + text = stringResource(Res.string.no_results_found), + modifier = Modifier.secondaryItemAlpha().fillMaxWidth().padding(20.dp), + textAlign = TextAlign.Center + ) + } + + else -> { + Column( + modifier = Modifier + .nestedScroll(nestedScrollConnection) + ) { + it.forEach { item -> + TextPreferenceWidget( + title = item.title, + subtitle = item.breadcrumbs, + onPreferenceClick = { + onItemClick(item) + } + ) + } + } + } + } + } +} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt index 6537ea9a9..e103a1440 100644 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt +++ b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt @@ -99,7 +99,7 @@ internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier { } val highlight by animateColorAsState( targetValue = if (highlightFlag) { - MaterialTheme.colorScheme.surfaceTint.copy(alpha = .12f) + MaterialTheme.colorScheme.onBackground.copy(alpha = .12f) } else { Color.Transparent },