From a9771d8a03e3b522a17d9bb5238fc7b8f45366c4 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:37:49 -0700 Subject: [PATCH 001/333] Replace srcDirs with directories This is an error in AGP 9, but works in past AGP releases also. Just to prepare for AGP 9 we can just do this now. --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 49d4cf741..e246f1f0e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -243,7 +243,7 @@ dependencies { tasks.register("androidSourcesJar") { archiveClassifier.set("sources") - from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources + from(android.sourceSets.getByName("main").java.directories) // Full Sources } tasks.register("copyJar") { From e25847cb647951ab9e7682d7f717f4fb0bcde681 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:24:42 -0700 Subject: [PATCH 002/333] Add API for minimum media duration --- .../cloudstream3/ui/player/CS3IPlayer.kt | 24 ++++++++++++------- .../com/lagradost/cloudstream3/MainAPI.kt | 6 +++++ 2 files changed, 21 insertions(+), 9 deletions(-) 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 b7712cd79..41dc052a7 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 @@ -1572,16 +1572,22 @@ class CS3IPlayer : IPlayer { } Log.i(TAG, "Rendered first frame") hasUsedFirstRender = true - val invalid = exoPlayer?.duration?.let { duration -> - // Only errors short playback when not playing downloaded files - duration < 20_000L && currentDownloadedFile == null - // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period - // If you can get the total time that'd be better, but this is already niche. - && exoPlayer?.currentTimeline?.periodCount == 1 - && exoPlayer?.isCurrentMediaItemLive != true - } ?: false - if (invalid) { + // Only errors short playback when not playing downloaded files + val tooShort = if (currentDownloadedFile == null) { + val provider = getApiFromNameNull(currentLink?.source) + val minimumDurationMs = provider?.minimumDurationMs + exoPlayer?.duration?.let { duration -> + minimumDurationMs != null && + duration < minimumDurationMs && + // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period + // If you can get the total time that'd be better, but this is already niche. + exoPlayer?.currentTimeline?.periodCount == 1 && + exoPlayer?.isCurrentMediaItemLive != true + } ?: false + } else false + + if (tooShort) { releasePlayer(saveTime = false) event(ErrorEvent(InvalidFileException("Too short playback"))) return diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index cce42da19..84b010759 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -557,6 +557,12 @@ abstract class MainAPI { * */ open val loadTimeoutMs: Long? = null + /** + * The minimum media duration in milliseconds. If the duration is smaller + * than this value, it will result in to short playback errors. + */ + @Prerelease + open val minimumDurationMs: Long? = null /** * A set of which ids the provider can open with getLoadUrl() From a46b0ac6e678f5fc5b82d0b1081fa6d8610c3d32 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:35:11 +0100 Subject: [PATCH 003/333] Download selection fix + sub del fix + Del dialog fix (#2308) --- .../ui/download/DownloadChildFragment.kt | 107 +++++++----------- .../ui/download/DownloadFragment.kt | 99 ++++++---------- .../ui/download/DownloadViewModel.kt | 33 +++--- .../cloudstream3/utils/SubtitleUtils.kt | 25 ++-- .../utils/VideoDownloadManager.kt | 2 +- 5 files changed, 108 insertions(+), 158 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 08194fd31..d44ea0020 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -1,16 +1,16 @@ package com.lagradost.cloudstream3.ui.download import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.text.format.Formatter.formatShortFileSize import android.view.View +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.result.FOCUS_SELF @@ -55,22 +55,6 @@ class DownloadChildFragment : BaseFragment( } override fun onBindingCreated(binding: FragmentChildDownloadsBinding) { - /** - * We never want to retain multi-delete state - * when navigating to downloads. Setting this state - * immediately can sometimes result in the observer - * not being notified in time to update the UI. - * - * By posting to the main looper, we ensure that this - * operation is executed after the view has been fully created - * and all initializations are completed, allowing the - * observer to properly receive and handle the state change. - */ - Handler(Looper.getMainLooper()).post { - downloadViewModel.setIsMultiDeleteState(false) - } - - val folder = arguments?.getString("folder") val name = arguments?.getString("name") if (folder == null) { @@ -101,30 +85,56 @@ class DownloadChildFragment : BaseFragment( } (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(cards.value) } + else -> { (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(null) } } } - observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> - val adapter = binding.downloadChildList.adapter as? DownloadAdapter - adapter?.setIsMultiDeleteState(isMultiDeleteState) - binding.downloadDeleteAppbar.isVisible = isMultiDeleteState - if (!isMultiDeleteState) { - activity?.detachBackPressedCallback("Downloads") - downloadViewModel.clearSelectedItems() - binding.downloadChildToolbar.isVisible = true - } - } + observe(downloadViewModel.selectedBytes) { updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) } - observe(downloadViewModel.selectedItemIds) { - handleSelectedChange(it) - updateDeleteButton(it.count(), downloadViewModel.selectedBytes.value ?: 0L) - binding.btnDelete.isVisible = it.isNotEmpty() - binding.selectItemsText.isVisible = it.isEmpty() + + binding.apply { + btnDelete.setOnClickListener { view -> + downloadViewModel.handleMultiDelete(view.context ?: return@setOnClickListener) + } + + btnCancel.setOnClickListener { + downloadViewModel.cancelSelection() + } + + btnToggleAll.setOnClickListener { + val allSelected = downloadViewModel.isAllChildrenSelected() + if (allSelected) { + downloadViewModel.clearSelectedItems() + } else { + downloadViewModel.selectAllChildren() + } + } + } + + observeNullable(downloadViewModel.selectedItemIds) { selection -> + val isMultiDeleteState = selection != null + val adapter = binding.downloadChildList.adapter as? DownloadAdapter + adapter?.setIsMultiDeleteState(isMultiDeleteState) + binding.downloadDeleteAppbar.isVisible = isMultiDeleteState + binding.downloadChildToolbar.isGone = isMultiDeleteState + + if (selection == null) { + activity?.detachBackPressedCallback("Downloads") + return@observeNullable + } + activity?.attachBackPressedCallback("Downloads") { + downloadViewModel.cancelSelection() + } + + updateDeleteButton(selection.count(), downloadViewModel.selectedBytes.value ?: 0L) + + binding.btnDelete.isVisible = selection.isNotEmpty() + binding.selectItemsText.isVisible = selection.isEmpty() val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { @@ -160,37 +170,6 @@ class DownloadChildFragment : BaseFragment( } } - private fun handleSelectedChange(selected: Set) { - if (selected.isNotEmpty()) { - binding?.downloadDeleteAppbar?.isVisible = true - binding?.downloadChildToolbar?.isVisible = false - activity?.attachBackPressedCallback("Downloads") { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnDelete?.setOnClickListener { - context?.let { ctx -> - downloadViewModel.handleMultiDelete(ctx) - } - } - - binding?.btnCancel?.setOnClickListener { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllChildrenSelected() - if (allSelected) { - downloadViewModel.clearSelectedItems() - } else { - downloadViewModel.selectAllChildren() - } - } - - downloadViewModel.setIsMultiDeleteState(true) - } - } - private fun updateDeleteButton(count: Int, selectedBytes: Long) { val formattedSize = formatShortFileSize(context, selectedBytes) binding?.btnDelete?.text = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index e3d77abac..3bd424640 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -7,8 +7,6 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION import android.os.Build -import android.os.Handler -import android.os.Looper import android.text.format.Formatter.formatShortFileSize import android.view.View import android.widget.LinearLayout @@ -28,6 +26,7 @@ import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.player.BasicLink @@ -87,21 +86,6 @@ class DownloadFragment : BaseFragment( binding.downloadAppbar.setAppBarNoScrollFlagsOnTV() binding.downloadDeleteAppbar.setAppBarNoScrollFlagsOnTV() - /** - * We never want to retain multi-delete state - * when navigating to downloads. Setting this state - * immediately can sometimes result in the observer - * not being notified in time to update the UI. - * - * By posting to the main looper, we ensure that this - * operation is executed after the view has been fully created - * and all initializations are completed, allowing the - * observer to properly receive and handle the state change. - */ - Handler(Looper.getMainLooper()).post { - downloadViewModel.setIsMultiDeleteState(false) - } - observe(downloadViewModel.headerCards) { cards -> when (cards) { is Resource.Success -> { @@ -161,26 +145,44 @@ class DownloadFragment : BaseFragment( observe(downloadViewModel.selectedBytes) { updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) } - observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> - val adapter = binding.downloadList.adapter as? DownloadAdapter - adapter?.setIsMultiDeleteState(isMultiDeleteState) - binding.downloadDeleteAppbar.isVisible = isMultiDeleteState - if (!isMultiDeleteState) { - activity?.detachBackPressedCallback("Downloads") - downloadViewModel.clearSelectedItems() - // Prevent race condition and make sure - // we don't display it early - if (downloadViewModel.usedBytes.value?.let { it > 0 } == true) { - binding.downloadAppbar.isVisible = true + + binding.apply { + btnDelete.setOnClickListener { view -> + downloadViewModel.handleMultiDelete(view.context ?: return@setOnClickListener) + } + + btnCancel.setOnClickListener { + downloadViewModel.cancelSelection() + } + + btnToggleAll.setOnClickListener { + val allSelected = downloadViewModel.isAllHeadersSelected() + if (allSelected) { + downloadViewModel.clearSelectedItems() + } else { + downloadViewModel.selectAllHeaders() } } } - observe(downloadViewModel.selectedItemIds) { - handleSelectedChange(it) - updateDeleteButton(it.count(), downloadViewModel.selectedBytes.value ?: 0L) - binding.btnDelete.isVisible = it.isNotEmpty() - binding.selectItemsText.isVisible = it.isEmpty() + observeNullable(downloadViewModel.selectedItemIds) { selection -> + val isMultiDeleteState = selection != null + val adapter = binding.downloadList.adapter as? DownloadAdapter + adapter?.setIsMultiDeleteState(isMultiDeleteState) + binding.downloadDeleteAppbar.isVisible = isMultiDeleteState + binding.downloadAppbar.isGone = isMultiDeleteState + + if (selection == null) { + activity?.detachBackPressedCallback("Downloads") + return@observeNullable + } + activity?.attachBackPressedCallback("Downloads") { + downloadViewModel.cancelSelection() + } + updateDeleteButton(selection.count(), downloadViewModel.selectedBytes.value ?: 0L) + + binding.btnDelete.isVisible = selection.isNotEmpty() + binding.selectItemsText.isVisible = selection.isEmpty() val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { @@ -260,37 +262,6 @@ class DownloadFragment : BaseFragment( } } - private fun handleSelectedChange(selected: Set) { - if (selected.isNotEmpty()) { - binding?.downloadDeleteAppbar?.isVisible = true - binding?.downloadAppbar?.isVisible = false - activity?.attachBackPressedCallback("Downloads") { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnDelete?.setOnClickListener { - context?.let { ctx -> - downloadViewModel.handleMultiDelete(ctx) - } - } - - binding?.btnCancel?.setOnClickListener { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllHeadersSelected() - if (allSelected) { - downloadViewModel.clearSelectedItems() - } else { - downloadViewModel.selectAllHeaders() - } - } - - downloadViewModel.setIsMultiDeleteState(true) - } - } - private fun updateDeleteButton(count: Int, selectedBytes: Long) { val formattedSize = formatShortFileSize(context, selectedBytes) binding?.btnDelete?.text = 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 bf81e6069..ee69390ff 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 @@ -29,7 +29,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class DownloadViewModel : ViewModel() { - private val _headerCards = ResourceLiveData>(Resource.Loading()) + private val _headerCards = + ResourceLiveData>(Resource.Loading()) val headerCards: LiveData>> = _headerCards private val _childCards = ResourceLiveData>(Resource.Loading()) @@ -47,22 +48,20 @@ class DownloadViewModel : ViewModel() { private val _selectedBytes = ConsistentLiveData(0) val selectedBytes: LiveData = _selectedBytes - private val _isMultiDeleteState = ConsistentLiveData(false) - val isMultiDeleteState: LiveData = _isMultiDeleteState + private val _selectedItemIds = ConsistentLiveData?>(null) + val selectedItemIds: LiveData?> = _selectedItemIds - private val _selectedItemIds = ConsistentLiveData>(mutableSetOf()) - val selectedItemIds: LiveData> = _selectedItemIds - fun setIsMultiDeleteState(value: Boolean) { - _isMultiDeleteState.postValue(value) + fun cancelSelection() { + updateSelectedItems { null } } fun addSelected(itemId: Int) { - updateSelectedItems { it + itemId } + updateSelectedItems { it?.plus(itemId) ?: setOf(itemId) } } fun removeSelected(itemId: Int) { - updateSelectedItems { it - itemId } + updateSelectedItems { it?.minus(itemId) ?: emptySet() } } fun selectAllHeaders() { @@ -97,8 +96,8 @@ class DownloadViewModel : ViewModel() { return currentSelected.size == headers.size && headers.all { it.data.id in currentSelected } } - private fun updateSelectedItems(action: (Set) -> Set) { - val currentSelected = action(selectedItemIds.value ?: mutableSetOf()) + private fun updateSelectedItems(action: (Set?) -> Set?) { + val currentSelected = action(selectedItemIds.value) _selectedItemIds.postValue(currentSelected) postHeaders() postChildren() @@ -115,7 +114,6 @@ class DownloadViewModel : ViewModel() { fun updateHeaderList(context: Context) = viewModelScope.launchSafe { // Do not push loading as it interrupts the UI //_headerCards.postValue(Resource.Loading()) - clearSelectedItems() val visual = withContext(Dispatchers.IO) { val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) @@ -232,7 +230,6 @@ class DownloadViewModel : ViewModel() { fun updateChildList(context: Context, folder: String) = viewModelScope.launchSafe { _childCards.postValue(Resource.Loading()) // always push loading - clearSelectedItems() val visual = withContext(Dispatchers.IO) { context.getKeys(folder).mapNotNull { key -> @@ -260,6 +257,7 @@ class DownloadViewModel : ViewModel() { } private fun removeItems(idsToRemove: Set) = viewModelScope.launchSafe { + _selectedItemIds.postValue(null) postHeaders(_headerCards.success?.filter { it.data.id !in idsToRemove }) postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } @@ -368,16 +366,16 @@ class DownloadViewModel : ViewModel() { .joinToString(separator = "\n") { "• $it" } return when { + data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { + context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) + } + data.ids.count() == 1 -> { context.getString(R.string.delete_message).format( data.names.firstOrNull() ) } - data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { - context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) - } - data.parentName != null && data.names.isNotEmpty() -> { context.getString(R.string.delete_message_series_episodes) .format(data.parentName, formattedNames) @@ -406,7 +404,6 @@ class DownloadViewModel : ViewModel() { when (which) { DialogInterface.BUTTON_POSITIVE -> { viewModelScope.launchSafe { - setIsMultiDeleteState(false) deleteFilesAndUpdateSettings(context, ids, this) { successfulIds -> // We always remove parent because if we are deleting from here // and we have it as non-empty, it was triggered on diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt index 66a6e156c..97be98aea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt @@ -2,8 +2,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context import com.lagradost.api.Log -import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFolder -import com.lagradost.safefile.SafeFile +import com.lagradost.cloudstream3.utils.VideoDownloadManager.basePathToFile object SubtitleUtils { @@ -14,16 +13,20 @@ object SubtitleUtils { ) fun deleteMatchingSubtitles(context: Context, info: VideoDownloadManager.DownloadedFileInfo) { - val relative = info.relativePath - val display = info.displayName - val cleanDisplay = cleanDisplayName(display) + val cleanDisplay = cleanDisplayName(info.displayName) - getFolder(context, relative, info.basePath)?.forEach { (name, uri) -> - if (isMatchingSubtitle(name, display, cleanDisplay)) { - val subtitleFile = SafeFile.fromUri(context, uri) - if (subtitleFile == null || subtitleFile.delete() != true) { - Log.e("SubtitleDeletion", "Failed to delete subtitle file: ${subtitleFile?.name()}") - } + val base = basePathToFile(context, info.basePath) + val folder = + base?.gotoDirectory(info.relativePath, createMissingDirectories = false) ?: return + val folderFiles = folder.listFiles() ?: return + + for (file in folderFiles) { + val name = file.name() ?: continue + if (!isMatchingSubtitle(name, info.displayName, cleanDisplay)) { + continue + } + if (file.delete() != true) { + Log.e("SubtitleDeletion", "Failed to delete subtitle file: $name") } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 9748bd296..cdda11868 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -1628,7 +1628,7 @@ object VideoDownloadManager { * Turns a string to an UniFile. Used for stored string paths such as settings. * Should only be used to get a download path. * */ - private fun basePathToFile(context: Context, path: String?): SafeFile? { + fun basePathToFile(context: Context, path: String?): SafeFile? { return when { path.isNullOrBlank() -> getDefaultDir(context) path.startsWith("content://") -> SafeFile.fromUri(context, path.toUri()) From 8fabb5c572fd1542145ef7b7af507075d04c09c9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:17:25 -0700 Subject: [PATCH 004/333] Suppress an UnspecifiedRegisterReceiverFlag lint issue (#2316) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- .../cloudstream3/ui/player/AbstractPlayerFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 413bc5d89..929550dc2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -221,11 +221,16 @@ abstract class AbstractPlayerFragment( ) } } + val filter = IntentFilter() filter.addAction(ACTION_MEDIA_CONTROL) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { activity?.registerReceiver(pipReceiver, filter, Context.RECEIVER_EXPORTED) - } else activity?.registerReceiver(pipReceiver, filter) + } else { + @SuppressLint("UnspecifiedRegisterReceiverFlag") + activity?.registerReceiver(pipReceiver, filter) + } + val isPlaying = player.getIsPlaying() val isPlayingValue = if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused From d5eba57bc0a6e026db25de45ade3a29710a6fbf6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:20:22 -0700 Subject: [PATCH 005/333] Cleanup UnstableApi usage (#2314) * Remove `@UnstableApi` from GeneratorPlayer and use OptIn instead. * Remove `@OptIn` from WebviewFragment as it was unnecessary. * Move `@OptIn` in SaveCaptionStyle to the actual single line we need to OptIn. * Split `setCues` logic to a new method in ChromcastSubtitlesFragment and only add `@OptIn` to that method as it's only necessary there. * Add some missing `@OptIn` annotations to fix all remaining `UnsafeOptInUsageError` lint errors. --- .../com/lagradost/cloudstream3/ui/WebviewFragment.kt | 5 +---- .../cloudstream3/ui/player/AbstractPlayerFragment.kt | 3 +++ .../ui/player/CustomSubtitleDecoderFactory.kt | 8 ++++---- .../cloudstream3/ui/player/FullScreenPlayer.kt | 4 +--- .../cloudstream3/ui/player/GeneratorPlayer.kt | 7 +------ .../ui/subtitles/ChromecastSubtitlesFragment.kt | 10 +++++++++- .../cloudstream3/ui/subtitles/SubtitlesFragment.kt | 5 +++-- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt index efc9b51c9..0d951bf6a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt @@ -6,9 +6,7 @@ import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import androidx.annotation.OptIn import androidx.fragment.app.FragmentActivity -import androidx.media3.common.util.UnstableApi import androidx.navigation.fragment.findNavController import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.USER_AGENT @@ -28,7 +26,6 @@ class WebviewFragment : BaseFragment( } binding.webView.webViewClient = object : WebViewClient() { - @OptIn(UnstableApi::class) override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? @@ -43,6 +40,7 @@ class WebviewFragment : BaseFragment( return super.shouldOverrideUrlLoading(view, request) } } + binding.webView.apply { WebViewResolver.webViewUserAgent = settings.userAgentString @@ -53,7 +51,6 @@ class WebviewFragment : BaseFragment( loadUrl(url) } - } companion object { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 929550dc2..de04e386f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -20,11 +20,13 @@ import android.widget.ImageView import android.widget.ProgressBar import android.widget.Toast import androidx.annotation.LayoutRes +import androidx.annotation.OptIn import androidx.annotation.StringRes import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.media3.common.PlaybackException +import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaSession import androidx.media3.ui.AspectRatioFrameLayout @@ -74,6 +76,7 @@ const val NEXT_WATCH_EPISODE_PERCENTAGE = 90 // when the player should sync the progress of "watched", TODO MAKE SETTING const val UPDATE_SYNC_PROGRESS_PERCENTAGE = 80 +@OptIn(UnstableApi::class) abstract class AbstractPlayerFragment( var player: IPlayer = CS3IPlayer() ) : Fragment() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index 40aff83e1..ffcd83664 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -35,8 +35,8 @@ import java.nio.charset.Charset /** * @param fallbackFormat used to create a decoder based on mimetype if the subtitle string is not * enough to identify the subtitle format. - **/ -@UnstableApi + */ +@OptIn(UnstableApi::class) class CustomDecoder(private val fallbackFormat: Format?) : SubtitleParser { companion object { fun updateForcedEncoding(context: Context) { @@ -392,7 +392,7 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { /** * Decoders created here persists across reset() * Do not save state in the decoder which you want to reset (e.g subtitle offset) - **/ + */ override fun createDecoder(format: Format): SubtitleDecoder { val parser = CustomDecoder(format) // Allow garbage collection if player releases the decoder @@ -404,8 +404,8 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { } } -@OptIn(UnstableApi::class) /** We need to convert the newer SubtitleParser to an older SubtitleDecoder */ +@OptIn(UnstableApi::class) class DelegatingSubtitleDecoder(name: String, private val parser: SubtitleParser) : SimpleSubtitleDecoder(name) { 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 9cc829e95..3821d880a 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 @@ -103,6 +103,7 @@ const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay" // All the UI Logic for the player +@OptIn(UnstableApi::class) open class FullScreenPlayer : AbstractPlayerFragment() { private var isVerticalOrientation: Boolean = false protected open var lockRotation = true @@ -274,7 +275,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private fun animateLayoutChangesForSubtitles() = // Post here as bottomPlayerBar is gone the first frame => bottomPlayerBar.height = 0 playerBinding?.bottomPlayerBar?.post { - @OptIn(UnstableApi::class) val sView = subView ?: return@post val sStyle = CustomDecoder.style val binding = playerBinding ?: return@post @@ -378,7 +378,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } } - @OptIn(UnstableApi::class) override fun subtitlesChanged() { val tracks = player.getVideoTracks() val isBuiltinSubtitles = tracks.currentTextTracks.all { track -> @@ -1526,7 +1525,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private var loudnessEnhancer: LoudnessEnhancer? = null - @OptIn(UnstableApi::class) private fun handleVolumeAdjustment( delta: Float, fromButton: Boolean, 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 48353736b..6e362ed80 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 @@ -132,7 +132,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.Job import kotlinx.coroutines.launch -@UnstableApi +@OptIn(UnstableApi::class) class GeneratorPlayer : FullScreenPlayer() { companion object { const val NOTIFICATION_ID = 2326 @@ -266,12 +266,8 @@ class GeneratorPlayer : FullScreenPlayer() { return PendingIntent.getBroadcast(context, instanceId, intent, pendingFlags) } - @OptIn(UnstableApi::class) - @UnstableApi private var cachedPlayerNotificationManager: PlayerNotificationManager? = null - @OptIn(UnstableApi::class) - @UnstableApi private fun getMediaNotification(context: Context): PlayerNotificationManager { val cache = cachedPlayerNotificationManager if (cache != null) return cache @@ -876,7 +872,6 @@ class GeneratorPlayer : FullScreenPlayer() { //dialog.subtitles_search_year?.setText(currentTempMeta.year) } - @OptIn(UnstableApi::class) private fun openSubPicker() { try { subsPathPicker.launch( 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 4f41b436d..f9b1cb1fe 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 @@ -10,7 +10,9 @@ import android.util.TypedValue import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.annotation.OptIn import androidx.media3.common.text.Cue +import androidx.media3.common.util.UnstableApi import com.fasterxml.jackson.annotation.JsonProperty import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DEPRESSED import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DROP_SHADOW @@ -49,7 +51,7 @@ data class SaveChromeCaptionStyle( @JsonProperty("fontScale") var fontScale: Float = 1.05f, @JsonProperty("windowColor") var windowColor: Int = Color.TRANSPARENT, ) -@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + class ChromecastSubtitlesFragment : BaseFragment( BaseFragment.BindingCreator.Inflate(ChromecastSubtitleSettingsBinding::inflate) ) { @@ -330,6 +332,12 @@ class ChromecastSubtitlesFragment : BaseFragment( BaseFragment.BindingCreator.Inflate(SubtitleSettingsBinding::inflate) ) { From ae5e25726df720898811ae3047ba339bcd77c3b9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:31:36 -0700 Subject: [PATCH 006/333] Use String.toUri consistently (#2304) --- .../java/com/lagradost/cloudstream3/MainActivity.kt | 6 +++--- .../cloudstream3/actions/temp/MpvKtPackage.kt | 3 +-- .../lagradost/cloudstream3/actions/temp/MpvPackage.kt | 3 +-- .../cloudstream3/actions/temp/PlayInBrowserAction.kt | 4 ++-- .../cloudstream3/actions/temp/WebVideoCastPackage.kt | 3 +-- .../lagradost/cloudstream3/utils/AppContextUtils.kt | 10 +++++----- .../com/lagradost/cloudstream3/utils/CastHelper.kt | 4 ++-- .../lagradost/cloudstream3/utils/PowerManagerAPI.kt | 4 ++-- .../com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 10 +++++----- 9 files changed, 22 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 53ba51f52..c12b10f36 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -9,7 +9,6 @@ import android.content.SharedPreferences import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Rect -import android.net.Uri import android.os.Bundle import android.util.AttributeSet import android.util.Log @@ -33,6 +32,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.edit +import androidx.core.net.toUri import androidx.core.view.children import androidx.core.view.get import androidx.core.view.isGone @@ -344,7 +344,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa activity?.findViewById(R.id.nav_rail_view)?.selectedItemId = R.id.navigation_search } else if (safeURI(str)?.scheme == APP_STRING_PLAYER) { - val uri = Uri.parse(str) + val uri = str.toUri() val name = uri.getQueryParameter("name") val url = URLDecoder.decode(uri.authority, "UTF-8") @@ -1924,7 +1924,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa fun buildMediaQueueItem(video: String): MediaQueueItem { // val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_PHOTO) //movieMetadata.putString(MediaMetadata.KEY_TITLE, "CloudStream") - val mediaInfo = MediaInfo.Builder(Uri.parse(video).toString()) + val mediaInfo = MediaInfo.Builder(video.toUri().toString()) .setStreamType(MediaInfo.STREAM_TYPE_NONE) .setContentType(MimeTypes.IMAGE_JPEG) // .setMetadata(movieMetadata).build() diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt index 102f0ac8b..faae39212 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.updateDurationAndPosition @@ -45,7 +44,7 @@ open class MpvKtPackage( intent.apply { putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") + setDataAndType(link.url.toUri(), "video/*") // m3u8 plays, but changing sources feature is not available // makeTempM3U8Intent(activity, this, result) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 68e619c92..95d05aa3a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri import com.lagradost.api.Log import com.lagradost.cloudstream3.actions.OpenInAppAction @@ -44,7 +43,7 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv putExtra("title", video.name) if (index != null) { - setDataAndType(Uri.parse(result.links.getOrNull(index)?.url ?: return), "video/*") + setDataAndType((result.links.getOrNull(index)?.url ?: return).toUri(), "video/*") } else { makeTempM3U8Intent(context, this, result) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 7c1b68c05..bfd2926bf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.actions.temp import android.content.Context import android.content.Intent -import android.net.Uri +import androidx.core.net.toUri import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult @@ -33,7 +33,7 @@ class PlayInBrowserAction: VideoClickAction() { ) { val link = result.links.getOrNull(index ?: 0) ?: return val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(link.url) + i.data = link.url.toUri() launch(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt index 9f7eee7b8..963221bb3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import androidx.core.net.toUri import com.lagradost.cloudstream3.USER_AGENT @@ -38,7 +37,7 @@ class WebVideoCastPackage: OpenInAppAction( val link = result.links[index ?: 0] intent.apply { - setDataAndType(Uri.parse(link.url), "video/*") + setDataAndType(link.url.toUri(), "video/*") val title = video.name ?: video.headerName 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 8334833e4..0376b6835 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -18,7 +18,6 @@ import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities -import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper @@ -33,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.annotation.WorkerThread import androidx.appcompat.app.AlertDialog +import androidx.core.net.toUri import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned import androidx.core.widget.ContentLoadingProgressBar @@ -170,10 +170,10 @@ object AppContextUtils { ) .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) .setTitle(title) - .setPosterArtUri(Uri.parse(card.posterUrl)) - .setIntentUri(Uri.parse(card.id?.let { + .setPosterArtUri(card.posterUrl?.toUri()) + .setIntentUri((card.id?.let { "$APP_STRING_RESUME_WATCHING://$it" - } ?: card.url)) + } ?: card.url).toUri()) .setInternalProviderId(card.url) .setLastEngagementTimeUtcMillis( resumeWatching?.updateTime ?: System.currentTimeMillis() @@ -603,7 +603,7 @@ object AppContextUtils { ) = (this.getActivity() ?: activity)?.runOnUiThread { try { val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(url) + intent.data = url.toUri() intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) // activityResultRegistry is used to fall back to webview if a browser is missing diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt index d83731658..b48c8d40a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -1,6 +1,6 @@ package com.lagradost.cloudstream3.utils -import android.net.Uri +import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.google.android.gms.cast.* import com.google.android.gms.cast.framework.CastSession @@ -41,7 +41,7 @@ object CastHelper { val srcPoster = epData.poster ?: holder.poster if (srcPoster != null) { - movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) + movieMetadata.addImage(WebImage(srcPoster.toUri())) } var subIndex = 0 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 63f7e1559..e3c7d68df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt @@ -3,13 +3,13 @@ package com.lagradost.cloudstream3.utils import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.PowerManager import android.provider.Settings import android.util.Log import androidx.appcompat.app.AlertDialog import androidx.core.content.edit +import androidx.core.net.toUri import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast @@ -67,7 +67,7 @@ object BatteryOptimizationChecker { try { val intent = Intent().apply { action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$PACKAGE_NAME") + data = "package:$PACKAGE_NAME".toUri() } startActivity(intent) } catch (t: Throwable) { 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 17568c8d2..798cb9d07 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -4,8 +4,8 @@ import android.content.ComponentName import android.content.ContentUris import android.content.Context import android.content.Intent -import android.net.Uri import android.util.Log +import androidx.core.net.toUri import androidx.tvprovider.media.tv.Channel import androidx.tvprovider.media.tv.PreviewProgram import androidx.tvprovider.media.tv.TvContractCompat @@ -84,12 +84,12 @@ object TvChannelUtils { } .setContentId(item.url) .setType(TvContractCompat.PreviewPrograms.TYPE_MOVIE) - .setIntentUri(Uri.parse(csshareUri)) + .setIntentUri(csshareUri.toUri()) .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3) // Validate poster URL before setting if (!poster.isNullOrBlank() && poster.startsWith("http")) { - builder.setPosterArtUri(Uri.parse(poster)) + builder.setPosterArtUri(poster.toUri()) } val program = builder.build() @@ -135,14 +135,14 @@ object TvChannelUtils { fun createTvChannel(context: Context) { val componentName = ComponentName(context, MainActivity::class.java) - val iconUri = Uri.parse("android.resource://${context.packageName}/mipmap/ic_launcher") + val iconUri = "android.resource://${context.packageName}/mipmap/ic_launcher".toUri() val inputId = TvContractCompat.buildInputId(componentName) val channel = Channel.Builder() .setType(TvContractCompat.Channels.TYPE_PREVIEW) .setAppLinkIconUri(iconUri) .setDisplayName(context.getString(R.string.app_name)) .setAppLinkIntent(Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse("cloudstreamapp://open") + data = "cloudstreamapp://open".toUri() }) .setInputId(inputId) .build() From e0231520d587ec5ab8e7fb9de2928a997e3dcb39 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:07:19 +0530 Subject: [PATCH 007/333] added mpvex (#2309) --- .../com/lagradost/cloudstream3/actions/VideoClickAction.kt | 2 ++ .../com/lagradost/cloudstream3/actions/temp/MpvPackage.kt | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) 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 d4f35f081..4843b7617 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.actions.temp.BiglyBTPackage import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction import com.lagradost.cloudstream3.actions.temp.JustPlayerPackage import com.lagradost.cloudstream3.actions.temp.LibreTorrentPackage +import com.lagradost.cloudstream3.actions.temp.MpvExPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage import com.lagradost.cloudstream3.actions.temp.MpvPackage @@ -51,6 +52,7 @@ object VideoClickActionHolder { // main support external apps VlcPackage(), MpvPackage(), + MpvExPackage(), NextPlayerPackage(), JustPlayerPackage(), FcastAction(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 95d05aa3a..cd49eb994 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -17,6 +17,9 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType // https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 // https://mpv-android.github.io/mpv-android/intent.html +//https://github.com/marlboro-advance/mpvEx +class MpvExPackage: MpvPackage("mpvEx","app.marlboroadvance.mpvex","app.marlboroadvance.mpvex.ui.player.PlayerActivity") + class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { override val sourceTypes = setOf( ExtractorLinkType.VIDEO, @@ -25,10 +28,10 @@ class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { ) } -open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv"): OpenInAppAction( +open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv",intentClass:String = "is.xyz.mpv.MPVActivity"): OpenInAppAction( txt(appName), packageName, - "is.xyz.mpv.MPVActivity" + intentClass ) { override val oneSource = true // mpv has poor playlist support on TV override suspend fun putExtra( From 9a9e71354c0068d4d3be5a7d2fd359ffebdf6edb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:55:00 -0700 Subject: [PATCH 008/333] Remove check for SearchAutoComplete (#2313) --- .../java/com/lagradost/cloudstream3/CommonActivity.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index e4e7c69f4..58d65b516 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -51,7 +51,7 @@ import com.lagradost.cloudstream3.ui.settings.extensions.PluginAdapter import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Event -import com.lagradost.cloudstream3.utils.UIHelper +import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UiText import java.lang.ref.WeakReference @@ -648,6 +648,7 @@ object CommonActivity { else -> null } + // println("NEXT FOCUS : $nextView") if (nextView != null) { nextView.requestFocus() @@ -655,10 +656,8 @@ object CommonActivity { return true } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && - (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete) - ) { - UIHelper.showInputMethod(act.currentFocus?.findFocus()) + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && currentFocus is SearchView) { + showInputMethod(currentFocus.findFocus()) } //println("Keycode: $keyCode") @@ -667,7 +666,6 @@ object CommonActivity { // "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}", // Toast.LENGTH_LONG //) - } // if someone else want to override the focus then don't handle the event as it is already From 7ded6a4fa1c16a7234a96aff81996c9a3994b459 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:56:34 -0700 Subject: [PATCH 009/333] Add tools:targetApi to appease lint (#2315) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- .../main/res/layout/settings_title_top.xml | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/layout/settings_title_top.xml b/app/src/main/res/layout/settings_title_top.xml index 1e3671a6f..646371405 100644 --- a/app/src/main/res/layout/settings_title_top.xml +++ b/app/src/main/res/layout/settings_title_top.xml @@ -1,22 +1,24 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/settings_top_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryGrayBackground" + android:orientation="vertical"> + android:id="@android:id/list_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:targetApi="n" /> + \ No newline at end of file From 350d19bd6be3c0b1084ab6c28869ef2889ba4411 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:27:54 -0700 Subject: [PATCH 010/333] Some minor miscellaneous cleanup (#2306) * Some minor miscellaneous cleanup * Remove classes --- .../lagradost/cloudstream3/CommonActivity.kt | 6 +-- .../HeaderDecorationBindingAdapter.kt | 11 ----- .../cloudstream3/ui/HeaderViewDecoration.kt | 42 ------------------- 3 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 58d65b516..2a994b8b7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -24,6 +24,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat import androidx.core.view.children +import androidx.core.view.isNotEmpty import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession import com.google.android.material.chip.ChipGroup @@ -421,8 +422,7 @@ object CommonActivity { private fun View.hasContent(): Boolean { return isShown && when (this) { - //is RecyclerView -> this.childCount > 0 - is ViewGroup -> this.childCount > 0 + is ViewGroup -> this.isNotEmpty() else -> true } } @@ -452,7 +452,7 @@ object CommonActivity { // if cant focus but visible then break and let android decide // the exception if is the view is a parent and has children that wants focus val hasChildrenThatWantsFocus = (next as? ViewGroup)?.let { parent -> - parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.childCount > 0 + parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.isNotEmpty() } ?: false if (!next.isFocusable && shown && !hasChildrenThatWantsFocus) return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt deleted file mode 100644 index 045a7963a..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lagradost.cloudstream3 - -import android.view.LayoutInflater -import androidx.annotation.LayoutRes -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.ui.HeaderViewDecoration - -fun setHeaderDecoration(view: RecyclerView, @LayoutRes headerViewRes: Int) { - val headerView = LayoutInflater.from(view.context).inflate(headerViewRes, null) - view.addItemDecoration(HeaderViewDecoration(headerView)) -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt deleted file mode 100644 index 40c03012a..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.lagradost.cloudstream3.ui - -import android.graphics.Canvas -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class HeaderViewDecoration(private val customView: View) : RecyclerView.ItemDecoration() { - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - customView.layout(parent.left, 0, parent.right, customView.measuredHeight) - for (i in 0 until parent.childCount) { - val view = parent.getChildAt(i) - if (parent.getChildAdapterPosition(view) == 0) { - c.save() - val height = customView.measuredHeight - val top = view.top - height - c.translate(0f, top.toFloat()) - customView.draw(c) - c.restore() - break - } - } - } - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - if (parent.getChildAdapterPosition(view) == 0) { - customView.measure( - View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(parent.measuredHeight, View.MeasureSpec.AT_MOST) - ) - outRect.set(0, customView.measuredHeight, 0, 0) - } else { - outRect.setEmpty() - } - } -} \ No newline at end of file From 74ceaf9a3ffeee73a1831d983050da527749f2c6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:27:36 -0700 Subject: [PATCH 011/333] Add a lint suppression for RestrictedApi (#2312) --- .../java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 2 ++ 1 file changed, 2 insertions(+) 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 798cb9d07..feecbe312 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.content.ComponentName import android.content.ContentUris import android.content.Context @@ -66,6 +67,7 @@ object TvChannelUtils { } /** Insert programs into a channel */ + @SuppressLint("RestrictedApi") fun addPrograms(context: Context, channelId: Long, items: List) { for (item in items) { try { From a836b268495c1c988455eede70203594be09a70a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:32:32 -0700 Subject: [PATCH 012/333] Cleanup InAppUpdater (#2298) The only functional change here is that the commit in the updater dialog was normalized to what it is everywhere else, meaning it is 7 not 10 characters now. I also have another patch prepared to convert this entire class to an actual object rather than just a class with only a companion object but since that touches every single line due to indentation changes, I decided to split it in order to make it easier to review. --- .../cloudstream3/utils/InAppUpdater.kt | 237 ++++++++---------- 1 file changed, 104 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 12befafe0..3ad6eb0f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -4,32 +4,34 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri +import android.text.TextUtils import android.util.Log import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.services.PackageInstallerService +import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.io.IOException import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import okio.BufferedSink import okio.buffer import okio.sink -import java.io.File -import android.text.TextUtils -import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit -import com.lagradost.cloudstream3.services.PackageInstallerService -import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus -import java.io.BufferedReader -import java.io.IOException -import java.io.InputStreamReader - class InAppUpdater { companion object { @@ -38,53 +40,51 @@ class InAppUpdater { private const val LOG_TAG = "InAppUpdater" - // === IN APP UPDATER === - data class GithubAsset( + private data class GithubAsset( @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, // download link + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive ) - data class GithubRelease( + private data class GithubRelease( @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Desc + @JsonProperty("body") val body: String, // Description @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // branch + @JsonProperty("target_commitish") val targetCommitish: String, // Branch @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String //Node Id + @JsonProperty("node_id") val nodeId: String, ) - data class GithubObject( - @JsonProperty("sha") val sha: String, // sha 256 hash - @JsonProperty("type") val type: String, // object type + private data class GithubObject( + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, @JsonProperty("url") val url: String, ) - data class GithubTag( + private data class GithubTag( @JsonProperty("object") val githubObject: GithubObject, ) - data class Update( + private data class Update( @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, @JsonProperty("updateURL") val updateURL: String?, @JsonProperty("updateVersion") val updateVersion: String?, @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String? + @JsonProperty("updateNodeId") val updateNodeId: String?, ) private suspend fun Activity.getAppUpdate(): Update { return try { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (settingsManager.getBoolean( + if ( + settingsManager.getBoolean( getString(R.string.prerelease_update_key), resources.getBoolean(R.bool.is_prerelease) ) ) { getPreReleaseUpdate() - } else { - getReleaseUpdate() - } + } else getReleaseUpdate() } catch (e: Exception) { Log.e(LOG_TAG, Log.getStackTraceString(e)) Update(false, null, null, null, null) @@ -94,56 +94,44 @@ class InAppUpdater { private suspend fun Activity.getReleaseUpdate(): Update { val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = - parseJson>( - app.get( - url, - headers = headers - ).text - ) + val response = parseJson>( + app.get(url, headers = headers).text + ) val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") - /* - val releases = response.map { it.assets }.flatten() - .filter { it.content_type == "application/vnd.android.package-archive" } - val found = - releases.sortedWith(compareBy { - versionRegex.find(it.name)?.groupValues?.get(2) - }).toList().lastOrNull()*/ - val foundList = - response.filter { rel -> - !rel.prerelease - }.sortedWith(compareBy { release -> - release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - } - }).toList() + val foundList = response.filter { rel -> + !rel.prerelease + }.sortedWith(compareBy { release -> + release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> + versionRegex.find( + it1 + )?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + } + }).toList() + val found = foundList.lastOrNull() val foundAsset = found?.assets?.getOrNull(0) val currentVersion = packageName?.let { - packageManager.getPackageInfo( - it, - 0 - ) + packageManager.getPackageInfo(it, 0) } foundAsset?.name?.let { assetName -> val foundVersion = versionRegex.find(assetName) val shouldUpdate = - if (foundAsset.browserDownloadUrl != "" && foundVersion != null) currentVersion?.versionName?.let { versionName -> - versionRegexLocal.find(versionName)?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - }?.compareTo( - foundVersion.groupValues.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - )!! < 0 else false + if (foundAsset.browserDownloadUrl != "" && foundVersion != null) { + currentVersion?.versionName?.let { versionName -> + versionRegexLocal.find(versionName)?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + }?.compareTo( + foundVersion.groupValues.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + )!! < 0 + } else false return if (foundVersion != null) { Update( shouldUpdate, @@ -152,57 +140,43 @@ class InAppUpdater { found.body, found.nodeId ) - } else { - Update(false, null, null, null, null) - } + } else Update(false, null, null, null, null) } + return Update(false, null, null, null, null) } private suspend fun Activity.getPreReleaseUpdate(): Update { - val tagUrl = - "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" + val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = - parseJson>(app.get(releaseUrl, headers = headers).text) + val response = parseJson>( + app.get(releaseUrl, headers = headers).text + ) + + val found = response.lastOrNull { rel -> + rel.prerelease || rel.tagName == "pre-release" + } - val found = - response.lastOrNull { rel -> - rel.prerelease || rel.tagName == "pre-release" - } val foundAsset = found?.assets?.filter { it -> it.contentType == "application/vnd.android.package-archive" }?.getOrNull(0) - val tagResponse = - parseJson(app.get(tagUrl, headers = headers).text) - - Log.d(LOG_TAG, "Fetched GitHub tag: ${tagResponse.githubObject.sha.take(7)}") - - val shouldUpdate = - (getString(R.string.commit_hash) - .trim { c -> c.isWhitespace() } - .take(7) - != - tagResponse.githubObject.sha - .trim { c -> c.isWhitespace() } - .take(7)) - return if (foundAsset != null) { + val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) + val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) + Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") + Update( - shouldUpdate, + getString(R.string.commit_hash) != updateCommitHash, foundAsset.browserDownloadUrl, - tagResponse.githubObject.sha.take(10), + updateCommitHash, found.body, found.nodeId ) - } else { - Update(false, null, null, null, null) - } + } else Update(false, null, null, null, null) } - private val updateLock = Mutex() private suspend fun Activity.downloadUpdate(url: String): Boolean { @@ -214,9 +188,7 @@ class InAppUpdater { // Delete all old updates this.cacheDir.listFiles()?.filter { it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { - deleteFileOnExit(it) - } + }?.forEach { deleteFileOnExit(it) } val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") val sink: BufferedSink = downloadedFile.sink().buffer() @@ -226,8 +198,10 @@ class InAppUpdater { sink.close() openApk(this, Uri.fromFile(downloadedFile)) } + return true } catch (e: Exception) { + logError(e) return false } } @@ -255,23 +229,20 @@ class InAppUpdater { /** * @param checkAutoUpdate if the update check was launched automatically - **/ + */ suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (!checkAutoUpdate || settingsManager.getBoolean( getString(R.string.auto_update_key), true ) ) { val update = getAppUpdate() - if ( - update.shouldUpdate && - update.updateURL != null) { - + if (update.shouldUpdate && update.updateURL != null) { // Check if update should be skipped - val updateNodeId = - settingsManager.getString(getString(R.string.skip_update_key), "") + val updateNodeId = settingsManager.getString( + getString(R.string.skip_update_key), "" + ) // Skips the update if its an automatic update and the update is skipped // This allows updating manually @@ -282,10 +253,7 @@ class InAppUpdater { runOnUiThread { try { val currentVersion = packageName?.let { - packageManager.getPackageInfo( - it, - 0 - ) + packageManager.getPackageInfo(it, 0) } val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) @@ -302,8 +270,6 @@ class InAppUpdater { } // Sanitized because it looks cluttered builder.setMessage(sanitizedChangelog) - - val context = this builder.apply { setPositiveButton(R.string.update) { _, _ -> // Forcefully start any delayed installations @@ -312,42 +278,45 @@ class InAppUpdater { showToast(R.string.download_started, Toast.LENGTH_LONG) // Check if the setting hasn't been changed - if (settingsManager.getInt( + if ( + settingsManager.getInt( getString(R.string.apk_installer_key), -1 ) == -1 ) { - if (isMiUi()) // Set to legacy if using miui - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), 1) - .apply() + // Set to legacy installer if using MIUI + if (isMiUi()) { + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), 1) + } + } } - val currentInstaller = - settingsManager.getInt( - getString(R.string.apk_installer_key), - 0 - ) + val currentInstaller = settingsManager.getInt( + getString(R.string.apk_installer_key), + 0 + ) when (currentInstaller) { // New method 0 -> { val intent = PackageInstallerService.Companion.getIntent( - context, + this@runAutoUpdate, update.updateURL ) - ContextCompat.startForegroundService(context, intent) + ContextCompat.startForegroundService(this@runAutoUpdate, intent) } // Legacy 1 -> { ioSafe { - if (!downloadUpdate(update.updateURL)) + if (!downloadUpdate(update.updateURL)) { runOnUiThread { showToast( R.string.download_failed, Toast.LENGTH_LONG ) } + } } } } @@ -357,10 +326,12 @@ class InAppUpdater { if (checkAutoUpdate) { setNeutralButton(R.string.skip_update) { _, _ -> - settingsManager.edit().putString( - getString(R.string.skip_update_key), - update.updateNodeId ?: "" - ).apply() + settingsManager.edit { + putString( + getString(R.string.skip_update_key), + update.updateNodeId ?: "" + ) + } } } } @@ -386,7 +357,7 @@ class InAppUpdater { BufferedReader(InputStreamReader(p.inputStream), 1024).use { it.readLine() } - } catch (ex: IOException) { + } catch (_: IOException) { null } } From 70121f45482fbd6cd6551930681a32a7c4151296 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:41:51 -0700 Subject: [PATCH 013/333] Fix crash on Android 5 (#2320) I just realized I hadn't done a PR to fix this issue yet but this issue is why I've been working on fixing all error level lint issues so that we can enable `failOnError` which would have prevented this. --- .../lagradost/cloudstream3/ui/player/FullScreenPlayer.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 3821d880a..4be6c250c 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 @@ -1875,7 +1875,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } playerBinding?.apply { - if (isLayout(TV or EMULATOR)) { mapOf( playerGoBack to playerGoBackText, @@ -1990,8 +1989,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() { return@setOnTouchListener handleMotionEvent(callView, event) } - playerControlsScroll.setOnScrollChangeListener { _, _, _, _, _ -> - autoHide() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + playerControlsScroll.setOnScrollChangeListener { _, _, _, _, _ -> + autoHide() + } } exoProgress.setOnTouchListener { _, event -> From 5d2e432614a4841702ce66390e4a1f02b86b5796 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:03:37 -0700 Subject: [PATCH 014/333] Make InAppUpdater an object (#2321) Better than a class with only a companion object I think. --- .../lagradost/cloudstream3/MainActivity.kt | 2 +- .../ui/settings/SettingsUpdates.kt | 2 +- .../cloudstream3/utils/InAppUpdater.kt | 532 +++++++++--------- 3 files changed, 267 insertions(+), 269 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c12b10f36..f6b722fea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -159,7 +159,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.ImageLoader.loadImage -import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SnackbarHelper.showSnackbar import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 6ff072038..e2805c402 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -33,7 +33,7 @@ import com.lagradost.cloudstream3.ui.settings.utils.getChooseFolderLauncher 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.Companion.runAutoUpdate +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 3ad6eb0f7..45891a5d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -33,333 +33,331 @@ import okio.BufferedSink import okio.buffer import okio.sink -class InAppUpdater { - companion object { - private const val GITHUB_USER_NAME = "recloudstream" - private const val GITHUB_REPO = "cloudstream" +object InAppUpdater { + private const val GITHUB_USER_NAME = "recloudstream" + private const val GITHUB_REPO = "cloudstream" - private const val LOG_TAG = "InAppUpdater" + private const val LOG_TAG = "InAppUpdater" - private data class GithubAsset( - @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size in bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, - @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive - ) + private data class GithubAsset( + @JsonProperty("name") val name: String, + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, + @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive + ) - private data class GithubRelease( - @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Description - @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // Branch - @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String, - ) + private data class GithubRelease( + @JsonProperty("tag_name") val tagName: String, // Version code + @JsonProperty("body") val body: String, // Description + @JsonProperty("assets") val assets: List, + @JsonProperty("target_commitish") val targetCommitish: String, // Branch + @JsonProperty("prerelease") val prerelease: Boolean, + @JsonProperty("node_id") val nodeId: String, + ) - private data class GithubObject( - @JsonProperty("sha") val sha: String, // SHA-256 hash - @JsonProperty("type") val type: String, - @JsonProperty("url") val url: String, - ) + private data class GithubObject( + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, + @JsonProperty("url") val url: String, + ) - private data class GithubTag( - @JsonProperty("object") val githubObject: GithubObject, - ) + private data class GithubTag( + @JsonProperty("object") val githubObject: GithubObject, + ) - private data class Update( - @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, - @JsonProperty("updateURL") val updateURL: String?, - @JsonProperty("updateVersion") val updateVersion: String?, - @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String?, - ) + private data class Update( + @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, + @JsonProperty("updateURL") val updateURL: String?, + @JsonProperty("updateVersion") val updateVersion: String?, + @JsonProperty("changelog") val changelog: String?, + @JsonProperty("updateNodeId") val updateNodeId: String?, + ) - private suspend fun Activity.getAppUpdate(): Update { - return try { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if ( - settingsManager.getBoolean( - getString(R.string.prerelease_update_key), - resources.getBoolean(R.bool.is_prerelease) - ) - ) { - getPreReleaseUpdate() - } else getReleaseUpdate() - } catch (e: Exception) { - Log.e(LOG_TAG, Log.getStackTraceString(e)) - Update(false, null, null, null, null) - } + private suspend fun Activity.getAppUpdate(): Update { + return try { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + if ( + settingsManager.getBoolean( + getString(R.string.prerelease_update_key), + resources.getBoolean(R.bool.is_prerelease) + ) + ) { + getPreReleaseUpdate() + } else getReleaseUpdate() + } catch (e: Exception) { + Log.e(LOG_TAG, Log.getStackTraceString(e)) + Update(false, null, null, null, null) } + } - private suspend fun Activity.getReleaseUpdate(): Update { - val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" - val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( - app.get(url, headers = headers).text - ) + private suspend fun Activity.getReleaseUpdate(): Update { + val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" + val headers = mapOf("Accept" to "application/vnd.github.v3+json") + val response = parseJson>( + app.get(url, headers = headers).text + ) - val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") - val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") - val foundList = response.filter { rel -> - !rel.prerelease - }.sortedWith(compareBy { release -> - release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } + val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") + val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") + val foundList = response.filter { rel -> + !rel.prerelease + }.sortedWith(compareBy { release -> + release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> + versionRegex.find( + it1 + )?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() } - }).toList() - - val found = foundList.lastOrNull() - val foundAsset = found?.assets?.getOrNull(0) - val currentVersion = packageName?.let { - packageManager.getPackageInfo(it, 0) } + }).toList() - foundAsset?.name?.let { assetName -> - val foundVersion = versionRegex.find(assetName) - val shouldUpdate = - if (foundAsset.browserDownloadUrl != "" && foundVersion != null) { - currentVersion?.versionName?.let { versionName -> - versionRegexLocal.find(versionName)?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - }?.compareTo( - foundVersion.groupValues.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - )!! < 0 - } else false - return if (foundVersion != null) { - Update( - shouldUpdate, - foundAsset.browserDownloadUrl, - foundVersion.groupValues[2], - found.body, - found.nodeId - ) - } else Update(false, null, null, null, null) - } - - return Update(false, null, null, null, null) + val found = foundList.lastOrNull() + val foundAsset = found?.assets?.getOrNull(0) + val currentVersion = packageName?.let { + packageManager.getPackageInfo(it, 0) } - private suspend fun Activity.getPreReleaseUpdate(): Update { - val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" - val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" - val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( - app.get(releaseUrl, headers = headers).text - ) - - val found = response.lastOrNull { rel -> - rel.prerelease || rel.tagName == "pre-release" - } - - val foundAsset = found?.assets?.filter { it -> - it.contentType == "application/vnd.android.package-archive" - }?.getOrNull(0) - - return if (foundAsset != null) { - val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) - val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) - Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") - + foundAsset?.name?.let { assetName -> + val foundVersion = versionRegex.find(assetName) + val shouldUpdate = + if (foundAsset.browserDownloadUrl != "" && foundVersion != null) { + currentVersion?.versionName?.let { versionName -> + versionRegexLocal.find(versionName)?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + }?.compareTo( + foundVersion.groupValues.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + )!! < 0 + } else false + return if (foundVersion != null) { Update( - getString(R.string.commit_hash) != updateCommitHash, + shouldUpdate, foundAsset.browserDownloadUrl, - updateCommitHash, + foundVersion.groupValues[2], found.body, found.nodeId ) } else Update(false, null, null, null, null) } - private val updateLock = Mutex() + return Update(false, null, null, null, null) + } - private suspend fun Activity.downloadUpdate(url: String): Boolean { - try { - Log.d(LOG_TAG, "Downloading update: $url") - val appUpdateName = "CloudStream" - val appUpdateSuffix = "apk" + private suspend fun Activity.getPreReleaseUpdate(): Update { + val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" + val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" + val headers = mapOf("Accept" to "application/vnd.github.v3+json") + val response = parseJson>( + app.get(releaseUrl, headers = headers).text + ) - // Delete all old updates - this.cacheDir.listFiles()?.filter { - it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { deleteFileOnExit(it) } - - val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") - val sink: BufferedSink = downloadedFile.sink().buffer() - - updateLock.withLock { - sink.writeAll(app.get(url).body.source()) - sink.close() - openApk(this, Uri.fromFile(downloadedFile)) - } - - return true - } catch (e: Exception) { - logError(e) - return false - } + val found = response.lastOrNull { rel -> + rel.prerelease || rel.tagName == "pre-release" } - private fun openApk(context: Context, uri: Uri) { - try { - uri.path?.let { - val contentUri = FileProvider.getUriForFile( - context, - BuildConfig.APPLICATION_ID + ".provider", - File(it) - ) - val installIntent = Intent(Intent.ACTION_VIEW).apply { - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) - data = contentUri - } - context.startActivity(installIntent) - } - } catch (e: Exception) { - logError(e) - } - } + val foundAsset = found?.assets?.filter { it -> + it.contentType == "application/vnd.android.package-archive" + }?.getOrNull(0) - /** - * @param checkAutoUpdate if the update check was launched automatically - */ - suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (!checkAutoUpdate || settingsManager.getBoolean( - getString(R.string.auto_update_key), - true + return if (foundAsset != null) { + val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) + val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) + Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") + + Update( + getString(R.string.commit_hash) != updateCommitHash, + foundAsset.browserDownloadUrl, + updateCommitHash, + found.body, + found.nodeId + ) + } else Update(false, null, null, null, null) + } + + private val updateLock = Mutex() + + private suspend fun Activity.downloadUpdate(url: String): Boolean { + try { + Log.d(LOG_TAG, "Downloading update: $url") + val appUpdateName = "CloudStream" + val appUpdateSuffix = "apk" + + // Delete all old updates + this.cacheDir.listFiles()?.filter { + it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix + }?.forEach { deleteFileOnExit(it) } + + val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") + val sink: BufferedSink = downloadedFile.sink().buffer() + + updateLock.withLock { + sink.writeAll(app.get(url).body.source()) + sink.close() + openApk(this, Uri.fromFile(downloadedFile)) + } + + return true + } catch (e: Exception) { + logError(e) + return false + } + } + + private fun openApk(context: Context, uri: Uri) { + try { + uri.path?.let { + val contentUri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + ".provider", + File(it) ) - ) { - val update = getAppUpdate() - if (update.shouldUpdate && update.updateURL != null) { - // Check if update should be skipped - val updateNodeId = settingsManager.getString( - getString(R.string.skip_update_key), "" - ) + val installIntent = Intent(Intent.ACTION_VIEW).apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + data = contentUri + } + context.startActivity(installIntent) + } + } catch (e: Exception) { + logError(e) + } + } - // Skips the update if its an automatic update and the update is skipped - // This allows updating manually - if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { - return false - } + /** + * @param checkAutoUpdate if the update check was launched automatically + */ + suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + if (!checkAutoUpdate || settingsManager.getBoolean( + getString(R.string.auto_update_key), + true + ) + ) { + val update = getAppUpdate() + if (update.shouldUpdate && update.updateURL != null) { + // Check if update should be skipped + val updateNodeId = settingsManager.getString( + getString(R.string.skip_update_key), "" + ) - runOnUiThread { - try { - val currentVersion = packageName?.let { - packageManager.getPackageInfo(it, 0) - } + // Skips the update if its an automatic update and the update is skipped + // This allows updating manually + if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { + return false + } - val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) - builder.setTitle( - getString(R.string.new_update_format).format( - currentVersion?.versionName, - update.updateVersion - ) + runOnUiThread { + try { + val currentVersion = packageName?.let { + packageManager.getPackageInfo(it, 0) + } + + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) + builder.setTitle( + getString(R.string.new_update_format).format( + currentVersion?.versionName, + update.updateVersion ) + ) - val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)") - val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult -> - matchResult.groupValues[1] - } // Sanitized because it looks cluttered + val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)") + val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult -> + matchResult.groupValues[1] + } // Sanitized because it looks cluttered - builder.setMessage(sanitizedChangelog) - builder.apply { - setPositiveButton(R.string.update) { _, _ -> - // Forcefully start any delayed installations - if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton + builder.setMessage(sanitizedChangelog) + builder.apply { + setPositiveButton(R.string.update) { _, _ -> + // Forcefully start any delayed installations + if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton - showToast(R.string.download_started, Toast.LENGTH_LONG) + showToast(R.string.download_started, Toast.LENGTH_LONG) - // Check if the setting hasn't been changed - if ( - settingsManager.getInt( - getString(R.string.apk_installer_key), - -1 - ) == -1 - ) { - // Set to legacy installer if using MIUI - if (isMiUi()) { - settingsManager.edit { - putInt(getString(R.string.apk_installer_key), 1) - } + // Check if the setting hasn't been changed + if ( + settingsManager.getInt( + getString(R.string.apk_installer_key), + -1 + ) == -1 + ) { + // Set to legacy installer if using MIUI + if (isMiUi()) { + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), 1) } } + } - val currentInstaller = settingsManager.getInt( - getString(R.string.apk_installer_key), - 0 - ) + val currentInstaller = settingsManager.getInt( + getString(R.string.apk_installer_key), + 0 + ) - when (currentInstaller) { - // New method - 0 -> { - val intent = PackageInstallerService.Companion.getIntent( - this@runAutoUpdate, - update.updateURL - ) - ContextCompat.startForegroundService(this@runAutoUpdate, intent) - } - // Legacy - 1 -> { - ioSafe { - if (!downloadUpdate(update.updateURL)) { - runOnUiThread { - showToast( - R.string.download_failed, - Toast.LENGTH_LONG - ) - } + when (currentInstaller) { + // New method + 0 -> { + val intent = PackageInstallerService.Companion.getIntent( + this@runAutoUpdate, + update.updateURL + ) + ContextCompat.startForegroundService(this@runAutoUpdate, intent) + } + // Legacy + 1 -> { + ioSafe { + if (!downloadUpdate(update.updateURL)) { + runOnUiThread { + showToast( + R.string.download_failed, + Toast.LENGTH_LONG + ) } } } } } + } - setNegativeButton(R.string.cancel) { _, _ -> } + setNegativeButton(R.string.cancel) { _, _ -> } - if (checkAutoUpdate) { - setNeutralButton(R.string.skip_update) { _, _ -> - settingsManager.edit { - putString( - getString(R.string.skip_update_key), - update.updateNodeId ?: "" - ) - } + if (checkAutoUpdate) { + setNeutralButton(R.string.skip_update) { _, _ -> + settingsManager.edit { + putString( + getString(R.string.skip_update_key), + update.updateNodeId ?: "" + ) } } } - builder.show().setDefaultFocus() - } catch (e: Exception) { - logError(e) } + builder.show().setDefaultFocus() + } catch (e: Exception) { + logError(e) } - return true } - return false + return true } return false } + return false + } - private fun isMiUi(): Boolean { - return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) - } + private fun isMiUi(): Boolean { + return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) + } - private fun getSystemProperty(propName: String): String? { - return try { - val p = Runtime.getRuntime().exec("getprop $propName") - BufferedReader(InputStreamReader(p.inputStream), 1024).use { - it.readLine() - } - } catch (_: IOException) { - null + private fun getSystemProperty(propName: String): String? { + return try { + val p = Runtime.getRuntime().exec("getprop $propName") + BufferedReader(InputStreamReader(p.inputStream), 1024).use { + it.readLine() } + } catch (_: IOException) { + null } } } From eaf2ac07921af26d69285f9127ea6f13fd6d3b54 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:04:11 -0700 Subject: [PATCH 015/333] Add some tools:targetApi to styles to appease lint (#2319) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- app/src/main/res/values/styles.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 486872657..f0d937ea5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + + + + + + + From 0d77f7b91ae1bb0f878d425934a63f5481e64af7 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 01:27:09 +0000 Subject: [PATCH 047/333] chore(locales): fix locale issues --- app/src/main/res/values-b+af/strings.xml | 2 +- app/src/main/res/values-b+am/strings.xml | 2 +- app/src/main/res/values-b+apc/strings.xml | 2 +- app/src/main/res/values-b+ar/strings.xml | 2 +- app/src/main/res/values-b+ars/strings.xml | 2 +- app/src/main/res/values-b+as/strings.xml | 2 +- app/src/main/res/values-b+az/strings.xml | 2 +- app/src/main/res/values-b+bg/strings.xml | 2 +- app/src/main/res/values-b+bn/strings.xml | 2 +- app/src/main/res/values-b+ckb/strings.xml | 2 +- app/src/main/res/values-b+cs/strings.xml | 2 +- app/src/main/res/values-b+de/strings.xml | 2 +- app/src/main/res/values-b+el/strings.xml | 2 +- app/src/main/res/values-b+eo/strings.xml | 2 +- app/src/main/res/values-b+es/strings.xml | 2 +- app/src/main/res/values-b+fa/strings.xml | 2 +- app/src/main/res/values-b+fil/strings.xml | 2 +- app/src/main/res/values-b+fr/strings.xml | 2 +- app/src/main/res/values-b+gl/strings.xml | 2 +- app/src/main/res/values-b+hi/strings.xml | 2 +- app/src/main/res/values-b+hr/strings.xml | 2 +- app/src/main/res/values-b+hu/strings.xml | 2 +- app/src/main/res/values-b+in/strings.xml | 2 +- app/src/main/res/values-b+it/strings.xml | 2 +- app/src/main/res/values-b+iw/strings.xml | 2 +- app/src/main/res/values-b+ja/strings.xml | 2 +- app/src/main/res/values-b+kn/strings.xml | 2 +- app/src/main/res/values-b+ko/strings.xml | 2 +- app/src/main/res/values-b+lt/strings.xml | 2 +- app/src/main/res/values-b+lv/strings.xml | 2 +- app/src/main/res/values-b+mk/strings.xml | 2 +- app/src/main/res/values-b+ml/strings.xml | 2 +- app/src/main/res/values-b+ms/strings.xml | 2 +- app/src/main/res/values-b+mt/strings.xml | 2 +- app/src/main/res/values-b+my/strings.xml | 2 +- app/src/main/res/values-b+ne/strings.xml | 2 +- app/src/main/res/values-b+nl/strings.xml | 2 +- app/src/main/res/values-b+nn/strings.xml | 2 +- app/src/main/res/values-b+no/strings.xml | 2 +- app/src/main/res/values-b+or/strings.xml | 2 +- app/src/main/res/values-b+pl/strings.xml | 2 +- app/src/main/res/values-b+pt+BR/strings.xml | 2 +- app/src/main/res/values-b+pt/strings.xml | 2 +- app/src/main/res/values-b+qt/strings.xml | 2 +- app/src/main/res/values-b+ro/strings.xml | 2 +- app/src/main/res/values-b+ru/strings.xml | 2 +- app/src/main/res/values-b+sk/strings.xml | 2 +- app/src/main/res/values-b+so/strings.xml | 2 +- app/src/main/res/values-b+sv/strings.xml | 2 +- app/src/main/res/values-b+ta/strings.xml | 2 +- app/src/main/res/values-b+ti/strings.xml | 2 +- app/src/main/res/values-b+tl/strings.xml | 2 +- app/src/main/res/values-b+tr/strings.xml | 2 +- app/src/main/res/values-b+uk/strings.xml | 2 +- app/src/main/res/values-b+ur/strings.xml | 2 +- app/src/main/res/values-b+vi/strings.xml | 2 +- app/src/main/res/values-b+zh+TW/strings.xml | 2 +- app/src/main/res/values-b+zh/strings.xml | 2 +- 58 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app/src/main/res/values-b+af/strings.xml b/app/src/main/res/values-b+af/strings.xml index 9bcfb3f63..71a18f7a5 100644 --- a/app/src/main/res/values-b+af/strings.xml +++ b/app/src/main/res/values-b+af/strings.xml @@ -121,4 +121,4 @@ PIN moet 4 karakters bevat Verkeerde PIN. Probeer weer. Verskaffers - \ No newline at end of file + diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index 34b5c3433..26fb84dd3 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -109,4 +109,4 @@ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index b911207ad..7d3263652 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -718,4 +718,4 @@ عدّل صورة الملف ادخل لينك ( عنوان ال URL ) تبع صورة الملف تم تعديل الصورة بنجاح - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index c86e91132..ff697d99f 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -765,4 +765,4 @@ اعلى وسط اعلى يمين شاهد المسلسل كاملاً - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index dabedeed1..b8240dc2e 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -346,4 +346,4 @@ عنوان مشغل الفيديو بحد أقصى لعدد الأحرف hide_player_control_names_key DNS عبر HTTPS - \ No newline at end of file + diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index fd6c35842..68fc2e163 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -665,4 +665,4 @@ সংহতিসমূহ/প্ৰদানকাৰী/পছন্দৰ মাধ্যমত টৰেণ্ট সামৰ্থবান কৰক চফ্টৱেৰ ডিকোডিং চফ্টৱেৰ ডিকোডিঙে প্লেয়াৰক আপোনাৰ ফোন দ্বাৰা সমৰ্থিত নোহোৱা ভিডিঅ\' ফাইলসমূহ চলাবলৈ সামৰ্থবান কৰে, কিন্তু উচ্চ ৰিজ\'লিউচনত লেগি বা অস্থিৰ প্লেবেকৰ সৃষ্টি কৰিব পাৰে - \ No newline at end of file + diff --git a/app/src/main/res/values-b+az/strings.xml b/app/src/main/res/values-b+az/strings.xml index 430cd4593..ffbd9d37d 100644 --- a/app/src/main/res/values-b+az/strings.xml +++ b/app/src/main/res/values-b+az/strings.xml @@ -144,4 +144,4 @@ Dəstəklənməyən xəta Gözlənilməyən oynadıcı xətası Ekran yansıtma - \ No newline at end of file + diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index 4116724d1..9eb439c88 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -703,4 +703,4 @@ Винаги изпращай запитване Задръжте, за да удвоите скоростта Дълго задържане за смяна на скоростта - \ No newline at end of file + diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index 4208f70f0..f65d673ae 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -352,4 +352,4 @@ প্রস্থান %1$d%2$s hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ckb/strings.xml b/app/src/main/res/values-b+ckb/strings.xml index c47af36a3..b38a7956c 100644 --- a/app/src/main/res/values-b+ckb/strings.xml +++ b/app/src/main/res/values-b+ckb/strings.xml @@ -83,4 +83,4 @@ کۆپی داخستن سەیڤ - \ No newline at end of file + diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index 9da634e73..07a37beb8 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -757,4 +757,4 @@ Uprostřed nahoře Vpravo nahoře Přehrát celý seriál - \ No newline at end of file + diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index a6cb66b52..08b76d927 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -729,4 +729,4 @@ Oben links Oben mitte Oben rechts - \ No newline at end of file + diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index c5d3e5444..96da7f206 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -684,4 +684,4 @@ Κάντε όλους τους υπότιτλους πλάγιους Ακτίνα φόντου Σύρετε ξανά προς τα πάνω για να υπερβείτε το 100% - \ No newline at end of file + diff --git a/app/src/main/res/values-b+eo/strings.xml b/app/src/main/res/values-b+eo/strings.xml index 4a23a93da..e3e428075 100644 --- a/app/src/main/res/values-b+eo/strings.xml +++ b/app/src/main/res/values-b+eo/strings.xml @@ -128,4 +128,4 @@ Elŝutante Elŝuto Malsukcesite hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 35343b9ad..c4fcc75dc 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -722,4 +722,4 @@ Inferior izquierda Inferior central Inferior derecho - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index ae6ffae39..146236651 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -352,4 +352,4 @@ ­همه افزونه ها را تست کنید خودکار رنگ اصلی - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index 7ea370852..f8ba8fa47 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -53,4 +53,4 @@ Itago ang napiling quality ng video sa mga resulta ng paghahanap Pumili ng mode upang i-filter ang pag-download ng mga plugin Awtomatikong i-install ang lahat ng hindi pa naka-install na plugin mula sa mga idinagdag na repository. - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index 3edc7fa88..d8ecc4dae 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -723,4 +723,4 @@ En haut au centre En haut à droite Jouer la série complète - \ No newline at end of file + diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index a5ad765d0..aeb76080e 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -293,4 +293,4 @@ Redimensionar Omitir introducción Non volver a amosar - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index c0ee55e70..e4eec65f3 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -340,4 +340,4 @@ टीवी में देखे Play mirror" कितना hd है वो वाला लेबल - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index e5ae7752d..902a75500 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -743,4 +743,4 @@ Ukloni gledano do ove epizode Ponovo učitano Usluga ponovnog učitavanja - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 13210d6dd..1e97719c0 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -592,4 +592,4 @@ Elérhető offline megtekintésre Mindet Kiválaszt Mindent Kijelölés Eltávolítása - \ No newline at end of file + diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 6f57647b8..ca3a39906 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -753,4 +753,4 @@ Atas tengah Atas kanan Putar Seri Penuh - \ No newline at end of file + diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 331cdf061..56defcbcd 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -753,4 +753,4 @@ In alto a destra In centro a sinistra Riproduci serie completa - \ No newline at end of file + diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 910b1ee0d..558d98a4b 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -560,4 +560,4 @@ הראה המלצות מוסיף אפשרות מהירות בנגן תדירות גיבוי - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index dddecc389..614fe14b9 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -705,4 +705,4 @@ 上中央 右上 全シリーズを再生 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index fcfaa9045..3a77aeef0 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -130,4 +130,4 @@ ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 77e6ec748..7ab550913 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -659,4 +659,4 @@ %1$d시간 %2$d분 %3$d초 %1$d분 %2$d초 %1$d초 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index 6493e33e2..357192018 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -255,4 +255,4 @@ Pašalinti iš žiūrimų Garso takelis hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 1aae0639c..287d79048 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -590,4 +590,4 @@ Nepareizs URL vai nederīgs attēls Profila attēls veiksmīgi nomainīts Rādīt ieteikumus - \ No newline at end of file + diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index c70f62fc5..bccc2a00d 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -709,4 +709,4 @@ Горе во центар Горе на десно Пушти ја целата серија - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index fb956d011..dcb9e5270 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -273,4 +273,4 @@ ഔട്ട്ലൈൻ നിറം പശ്ചാത്തല നിറം hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index 6905abed4..8bbb2a7e0 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -538,4 +538,4 @@ Siaran Langsung Audio Podcast - \ No newline at end of file + diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index bd605bb37..ca62a043b 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -123,4 +123,4 @@ Neħħi Falla t-tniżżil hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index b982e2717..1d35cbaa0 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -538,4 +538,4 @@ လိုက်ဘရီရွေးချယ်ရန် ဖြင့်ဖွင့်မည် hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index 4eea78b9c..9345cab2b 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -128,4 +128,4 @@ रिपोजिटरी को नाम र यूआरएल कपी गरियो! hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index e1078212c..6e0982c79 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -634,4 +634,4 @@ Fout: wordt niet ondersteund Beveiliging Accounts - \ No newline at end of file + diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 6989a85da..2cf83c183 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -192,4 +192,4 @@ Fortsett å sjå Prøv tilkopling på nytt… hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index eea8a95fa..a981609cf 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -526,4 +526,4 @@ Hjelp Profilbakgrunn hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+or/strings.xml b/app/src/main/res/values-b+or/strings.xml index 8d0f604fb..807a3bcc6 100644 --- a/app/src/main/res/values-b+or/strings.xml +++ b/app/src/main/res/values-b+or/strings.xml @@ -159,4 +159,4 @@ ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 0a234db96..5d67069be 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -734,4 +734,4 @@ Górne środkowe Górne prawe Odtwórz całą serię - \ No newline at end of file + 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 cb2e4b730..53df149ba 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -745,4 +745,4 @@ Superior direito Assistir à série completa Resolução e nome - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index 31e01661d..a789e9739 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -731,4 +731,4 @@ Centro em cima Direita em cima Reproduzir Série Inteira - \ No newline at end of file + diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 258a30df7..3de0f32df 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -654,4 +654,4 @@ Boo Oo-ahh oo-chit ar-ar Boooooo - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index b97397919..642eea0c3 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -656,4 +656,4 @@ Încărcați prima disponibilă Șterge pluginul Închide - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index 7589647f6..a37b0f373 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -719,4 +719,4 @@ Вверху центр Вверху правый Смотреть полностью - \ No newline at end of file + diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index 1b1cba51b..fb65841f2 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -458,4 +458,4 @@ Podcast Všetko Chyba kódovania - \ No newline at end of file + diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index 8366f1eea..fc42c63f7 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -473,4 +473,4 @@ Bilow isku qasan Qoraalka dhamaadka hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 6b62e66a0..dfbfce4b5 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -680,4 +680,4 @@ Betyg%s Uppdatera Plugins Gå till Hämtade filer - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 368717dfb..94b6f717a 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -677,4 +677,4 @@ %1$d மணி %2$d நிமிடம் %3$d விநாடி %1$d நிமிடம் %2$d விநாடி %1$d விநாடி - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ti/strings.xml b/app/src/main/res/values-b+ti/strings.xml index 99e083351..6c154c8d8 100644 --- a/app/src/main/res/values-b+ti/strings.xml +++ b/app/src/main/res/values-b+ti/strings.xml @@ -4,4 +4,4 @@ ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index cf63eceb6..94bb8ea1d 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -258,4 +258,4 @@ Mga setting ng mga subtitle ng Chromecast Maglaro ng Trailer hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index 1e49ba60a..f6a125b91 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -777,4 +777,4 @@ Sağ üst Altyazı Hizalama Tüm Seriyi Oynat - \ No newline at end of file + diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 2cb4e5da5..519e311c3 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -705,4 +705,4 @@ Верхній центр Угорі праворуч Відтворити повну серію - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index 563bf085d..d2c3d9f1c 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -621,4 +621,4 @@ آواز تاریخ %s اپنے اسمارٹ فون یا کمپیوٹر پر یہ %s وزٹ کریں اور مندرجہ بالا کوڈ ڈالیں - \ No newline at end of file + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 8804e0876..aa9caffd1 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -746,4 +746,4 @@ Giữa trái Giữa phải Phát trọn bộ loạt phim - \ No newline at end of file + diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index 7979c9d95..251df543c 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -703,4 +703,4 @@ 軟體解碼 不接受的種子 載入第一個可用的 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 2b149356e..0b893327f 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -777,4 +777,4 @@ 顶部居中 右上 播放全剧 - \ No newline at end of file + From 0593cfbc01c9e3ff7b5bc7f4a6d41adb6aa9367f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:11:54 -0700 Subject: [PATCH 048/333] Add linting to library (#2301) --- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + library/build.gradle.kts | 1 + library/lint.xml | 6 +++ .../com/lagradost/cloudstream3/MainAPI.kt | 2 +- .../extractors/ContentXExtractor.kt | 6 +-- .../cloudstream3/extractors/DoodExtractor.kt | 29 +++++++-------- .../cloudstream3/extractors/Filegram.kt | 37 +++++++++---------- .../extractors/RapidVidExtractor.kt | 6 +-- .../cloudstream3/extractors/TRsTXExtractor.kt | 9 ++--- .../extractors/VidMoxyExtractor.kt | 6 +-- .../lagradost/cloudstream3/extractors/Vtbe.kt | 28 +++++++------- .../extractors/helper/CryptoJSHelper.kt | 8 ++-- .../cloudstream3/utils/HlsPlaylistParser.kt | 4 +- 14 files changed, 73 insertions(+), 71 deletions(-) create mode 100644 library/lint.xml diff --git a/build.gradle.kts b/build.gradle.kts index defd28515..cca263dd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.lint) apply false alias(libs.plugins.android.multiplatform.library) apply false alias(libs.plugins.buildkonfig) apply false // Universal build config alias(libs.plugins.dokka) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d29aab04..24f69824c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -112,6 +112,7 @@ work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "w [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" } android-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "androidGradlePlugin" } buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaGradlePlugin" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 5e2b59098..392552739 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("maven-publish") // Gradle core plugin + alias(libs.plugins.android.lint) alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.buildkonfig) diff --git a/library/lint.xml b/library/lint.xml new file mode 100644 index 000000000..6f4e20222 --- /dev/null +++ b/library/lint.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 43f7f4070..ada8d5cd8 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -2502,7 +2502,7 @@ constructor( fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") { try { - this.date = SimpleDateFormat(format).parse(date ?: return)?.time + this.date = SimpleDateFormat(format, Locale.getDefault()).parse(date ?: return)?.time } catch (e: Exception) { logError(e) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt index 06c5ec321..dba2e9267 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt @@ -17,12 +17,12 @@ open class ContentX : ExtractorApi() { val iSource = app.get(url, referer=extRef).text val iExtract = Regex("""window\.openPlayer\('([^']+)'""").find(iSource)!!.groups[1]?.value ?: throw ErrorLoadingException("iExtract is null") - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(iSource).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( 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 58b6396a8..e1fd47fff 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -77,7 +77,7 @@ open class DoodLaExtractor : ExtractorApi() { override var name = "DoodStream" override var mainUrl = "https://dood.la" override val requiresReferer = false - + private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" override suspend fun getUrl( @@ -87,18 +87,17 @@ open class DoodLaExtractor : ExtractorApi() { callback: (ExtractorLink) -> Unit ) { val embedUrl = url.replace("/d/", "/e/") - val req = app.get(embedUrl) + val req = app.get(embedUrl) val host = getBaseUrl(req.url) val response0 = req.text - val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) + 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}p") .find(response0.substringAfter("").substringBefore("")) ?.groupValues ?.getOrNull(0) - - callback.invoke( + + callback.invoke( newExtractorLink( this.name, this.name, @@ -108,19 +107,17 @@ open class DoodLaExtractor : ExtractorApi() { this.quality = getQualityFromName(quality) } ) - } - -private fun createHashTable(): String { - return buildString { - repeat(10) { - append(alphabet.random()) + + private fun createHashTable(): String { + return buildString { + repeat(10) { + append(alphabet.random()) + } } } -} - -private fun getBaseUrl(url: String): String { + private fun getBaseUrl(url: String): String { return URI(url).let { "${it.scheme}://${it.host}" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt index df7e03373..7baa62710 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt @@ -32,32 +32,29 @@ open class Filegram : ExtractorApi() { "Sec-Fetch-Site" to "same-site", "user-agent" to USER_AGENT, ) - + val doc = app.get(getEmbedUrl(url), referer = referer).document val unpackedJs = unpackJs(doc).toString() - val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) + val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) if (videoUrl != null) { M3u8Helper.generateM3u8( - this.name, - fixUrl(videoUrl), - "$mainUrl/", - headers = header - ).forEach(callback) + this.name, + fixUrl(videoUrl), + "$mainUrl/", + headers = header + ).forEach(callback) } } - private fun unpackJs(script: Element): String? { - return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } - ?.data()?.let { getAndUnpack(it) } - } - - private fun getEmbedUrl(url: String): String { - return if (!url.contains("/embed-")) { - val videoId = url.substringAfter("$mainUrl/") - "$mainUrl/embed-$videoId" - } else { - url - } - } + private fun unpackJs(script: Element): String? { + return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } + ?.data()?.let { getAndUnpack(it) } + } + private fun getEmbedUrl(url: String): String { + return if (!url.contains("/embed-")) { + val videoId = url.substringAfter("$mainUrl/") + "$mainUrl/embed-$videoId" + } else url + } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt index bacd658bb..9654e5f38 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt @@ -15,12 +15,12 @@ open class RapidVid : ExtractorApi() { val extRef = referer ?: "" val videoReq = app.get(url, referer=extRef).text - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(videoReq).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( 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 c8796896c..1348f74d5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt @@ -29,7 +29,7 @@ open class TRsTX : ExtractorApi() { ) } - val vidLinks = mutableSetOf() + val vidLinks = mutableSetOf() val vidMap = mutableListOf>() for (item in postJson) { if (item.file == null || item.title == null) continue @@ -37,8 +37,8 @@ open class TRsTX : ExtractorApi() { 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) + if (videoData in vidLinks) { continue } + vidLinks.add(videoData) vidMap.add(mapOf( "title" to item.title, @@ -46,12 +46,11 @@ open class TRsTX : ExtractorApi() { )) } - for (mapEntry in vidMap) { val title = mapEntry["title"] ?: continue val m3uLink = mapEntry["videoData"] ?: continue - callback.invoke( + callback.invoke( newExtractorLink( source = this.name, name = "${this.name} - ${title}", diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt index e4cb69603..36acf7f7a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt @@ -15,12 +15,12 @@ open class VidMoxy : ExtractorApi() { val extRef = referer ?: "" val videoReq = app.get(url, referer=extRef).text - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(videoReq).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt index 0f078b3bb..37b8ecb23 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt @@ -20,20 +20,20 @@ open class Vtbe : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { val response = app.get(url,referer=mainUrl).document val extractedpack =response.selectFirst("script:containsData(function(p,a,c,k,e,d))")?.data().toString() - JsUnpacker(extractedpack).unpack()?.let { unPacked -> - Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> - return listOf( - newExtractorLink( - this.name, - this.name, - link, - ) { - this.referer = referer ?: "" - this.quality = Qualities.Unknown.value - } - ) - } + JsUnpacker(extractedpack).unpack()?.let { unPacked -> + Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> + return listOf( + newExtractorLink( + this.name, + this.name, + link, + ) { + this.referer = referer ?: "" + this.quality = Qualities.Unknown.value + } + ) } - return null + } + return null } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt index a13db65cc..af59b6f7d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt @@ -1,7 +1,8 @@ package com.lagradost.cloudstream3.extractors.helper +import com.lagradost.cloudstream3.base64DecodeArray +import com.lagradost.cloudstream3.base64Encode import java.util.Arrays -import java.util.Base64 import java.security.MessageDigest import java.security.SecureRandom import javax.crypto.Cipher @@ -51,8 +52,7 @@ object CryptoJS { System.arraycopy(saltBytes, 0, b, sBytes.size, saltBytes.size) System.arraycopy(cipherText, 0, b, sBytes.size + saltBytes.size, cipherText.size) - val bEncode = Base64.getEncoder().encode(b) - return String(bEncode) + return base64Encode(b) } /** @@ -62,7 +62,7 @@ object CryptoJS { * @param cipherText encrypted string */ fun decrypt(password: String, cipherText: String): String { - val ctBytes = Base64.getDecoder().decode(cipherText.toByteArray()) + val ctBytes = base64DecodeArray(cipherText) val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16) val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) 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 dbc3c92f6..01e5bb862 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -222,7 +222,7 @@ object HlsPlaylistParser { if (codecs.isNullOrEmpty()) { return arrayOf() } - return split(codecs.trim { it <= ' ' }, "(\\s*,\\s*)") + return split(codecs.trim(), "(\\s*,\\s*)") } fun getCodecsOfType( @@ -928,7 +928,7 @@ object HlsPlaylistParser { fun getMediaMimeType(codecOrNull: String?): String? { var codec = codecOrNull ?: return null - codec = codec.trim { it <= ' ' }.lowercase() + codec = codec.trim().lowercase() if (codec.startsWith("avc1") || codec.startsWith("avc3")) { return MimeTypes.VIDEO_H264 } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { From bc68b3d7c69bdf8e203a6303e1734398b2d90524 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 02:19:29 +0000 Subject: [PATCH 049/333] chore(locales): fix locale issues --- app/src/main/res/values-b+hi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 7fdb37c28..8b7c79650 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -314,7 +314,7 @@ खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं प्रकरण - + %1$d-%2$d @string/home_play From be78306c553dabb83ed19c8d02d668aba2b1d03d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:22:07 -0700 Subject: [PATCH 050/333] Minor order fix for lint plugin (#2355) --- library/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 392552739..e73ed970d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -7,8 +7,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("maven-publish") // Gradle core plugin - alias(libs.plugins.android.lint) alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.lint) alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.buildkonfig) alias(libs.plugins.dokka) From 6c2228b964a097e0cf558fea638b1a260a3309a7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:48:37 -0700 Subject: [PATCH 051/333] Improve caching system for actions (#2249) --- .github/workflows/build_to_archive.yml | 6 +++++- .github/workflows/generate_dokka.yml | 6 +++++- .github/workflows/prerelease.yml | 6 +++++- .github/workflows/pull_request.yml | 7 ++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_to_archive.yml b/.github/workflows/build_to_archive.yml index ce920002e..07096014a 100644 --- a/.github/workflows/build_to_archive.yml +++ b/.github/workflows/build_to_archive.yml @@ -40,7 +40,6 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -56,6 +55,11 @@ jobs: echo "::add-mask::${KEY_PWD}" echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Run Gradle run: ./gradlew assemblePrerelease env: diff --git a/.github/workflows/generate_dokka.yml b/.github/workflows/generate_dokka.yml index e082b79f4..e3dac3857 100644 --- a/.github/workflows/generate_dokka.yml +++ b/.github/workflows/generate_dokka.yml @@ -45,7 +45,11 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Android SDK uses: android-actions/setup-android@v3 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index cee9538bd..c7dee13eb 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -31,7 +31,6 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -47,6 +46,11 @@ jobs: echo "::add-mask::${KEY_PWD}" echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Run Gradle run: ./gradlew assemblePrerelease build androidSourcesJar makeJar env: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 381331f0b..090e7a2ec 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -13,11 +13,16 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + cache-read-only: false + - name: Run Gradle run: ./gradlew assemblePrereleaseDebug From 7fd490218039a64251d221fe5ce455de87afc755 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 23 Dec 2025 20:00:28 +0100 Subject: [PATCH 052/333] Translated using Weblate (Belarusian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 33.4% (277 of 828 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 27.5% (228 of 828 strings) Translated using Weblate (Latvian) Currently translated at 84.9% (703 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 25.4% (211 of 828 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 23.0% (191 of 828 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Italian) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (828 of 828 strings) Co-authored-by: Hosted Weblate Co-authored-by: Juan Rubin Co-authored-by: Massimo Pissarello Co-authored-by: Pizza Party Co-authored-by: Sasha Glazko Co-authored-by: soldado-do-wolfenstein Co-authored-by: Максим Горпиніч Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) Co-authored-by: 大王叫我来巡山 Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/be/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/it/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/lv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translation: Cloudstream/App --- app/src/main/res/values-b+apc/strings.xml | 28 +++++- app/src/main/res/values-b+it/strings.xml | 1 + app/src/main/res/values-b+lv/strings.xml | 2 +- app/src/main/res/values-b+pt+BR/strings.xml | 4 + app/src/main/res/values-b+pt/strings.xml | 4 + app/src/main/res/values-b+uk/strings.xml | 1 + app/src/main/res/values-b+zh/strings.xml | 1 + app/src/main/res/values-be/strings.xml | 104 ++++++++++++++++++++ 8 files changed, 142 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 7d3263652..56e1a7e34 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -75,7 +75,7 @@ تلقائيًا نَزِل كل الإضافات من الريپويات يللي نزادِت. محي بلش - فيه تِلِفونات م فيها تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إزا م عم تنزل التجديدات. + فيه أجهزة م فيها تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إزا م عم تنزل التجديدات. بعد ما تسكر \"كلود ستريم\"، بكفي الڤيديو بشِباك زغير فوق غير آپ هيدا المصدر م بيدعم \"كروم كاست\" تنبيش منظّم @@ -670,7 +670,7 @@ أفّي اللودينگ تلقائيًا سماح ب إستعمال الـTorrent بال سَتِنگز/المصادر/المحتوى المفضل سكر الآپ و رجاع فتحه، و قبال دعم التورنت ت تكفي. - السوفتوار ديكودينگ بخلي مشغل الڤيديو يمشّي أنواع فيلات ڤيديو م بيدعمها جهازك، بس هال شي معقول يخلي الڤيديو يعلق، خاصتًا إزا كانت الجودة عالية + السوفتوار ديكودينگ بخلي مشغل الڤيديو يمشّي أنواع فيلات ڤيديو م بيدعمها جهازك، بس هال شي معقول يخلي الڤيديو يعلق، خاصتًا إزا كانت الجودة عالية. سوفتوار ديكودينگ رايتينگ (أوطا) نهار اللي نزل (أجدد) @@ -718,4 +718,28 @@ عدّل صورة الملف ادخل لينك ( عنوان ال URL ) تبع صورة الملف تم تعديل الصورة بنجاح + بلّش كل المسلسل + نزل النسخة التجريبية من الآپ + أصلًا عندك النسخة التجريبية. + فشل تنزيل النسخة التجريبية. + ستعمل مصدر بديل" + كتيبة الحلقة + م لقينا الرابط + الرابط أو الصورة مش صالحة + عتبر الحلقات محدورة لحد هون + وقف إعتبار الحلقات محدورة لحد هون + عملنا ري-لوود + عمول ري-لوود للمصدر + اسم + الجودة وال اسم + ميلة الترجمة + تحت، عال شمال + تحت، بال نُص + تحت، عال يمين + نُص، شمال + نُص النُص + نُص، عال يمين + فوق، عال شمال + فوق، بال نُص + فوق، عال يمين diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 4ea21be51..fee5b56f3 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -756,4 +756,5 @@ Installa la versione pre-release La versione pre-release è già installata. Impossibile installare la versione pre-release. + Testo dell\'episodio diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index e8fb46baa..444a59a5f 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -85,7 +85,7 @@ Settingi Žanrs Dalities - Atvērt internetā + Atvērt pārlūkā Ieladēts Lādējas Aizvērt 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 6a6ba2111..3fbc4fb28 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -745,4 +745,8 @@ Superior direito Assistir à série completa Resolução e nome + Falha ao instalar a versão antecipada. + Versão antecipada instalada. + Instalar versão antecipada + Episódio Text diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index a789e9739..23b49195f 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -731,4 +731,8 @@ Centro em cima Direita em cima Reproduzir Série Inteira + Instalar versão de pré-lançamento + Versão de pré-lançamento já instalada. + Falha ao instalar pré-lançamento. + Texto do Episódio diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index f5c9c2c18..54562f263 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -708,4 +708,5 @@ Встановити передрелізну версію Попередня версія вже встановлена. Не вдалося встановити попередню версію. + Текст епізоду diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 243b56a47..224041e49 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -780,4 +780,5 @@ 安装预发行版 已安装预发行版。 安装预发行版失败。 + 剧集文本 diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index a81f30ef7..2111713d6 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -52,4 +52,108 @@ Прайграць фільм Прайграць трэйлер Прайграць трансляцыю + Трансліраваць Torrent + Прайграць серыял поўнасцю + Гэта відэа — Torrent, гэта значыць, што ваша актыўнасць можа быць адсочана.\nУпэўніцеся, што вы ведаеце, як працуюць Torrent-файлы, перад працягам. + Крыніцы + Субцітры + Паспрабаваць перападлучыцца… + Назад + Прайграць серыю + Спампаваць + Спампавана + Ідзе спампоўванне + Спампоўванне прыпынена + Спампоўванне пачалося + Не ўдалося спампаваць + Спампоўванне скасавана + Спампоўванне завершана + Выберыце элементы для выдалення + Спамповак пакуль што няма. + Даступна для прагляду па-за сеткай + Выбраць ўсё + Зняць выбар + Пачалося абнаўленне + Сеткавая трансляцыя + Адкрыць лакальнае відэа + Памылка пры загрузцы спасылак + Спасылкі перазагружаны + Унутранае сховішча + Дуб + Суб + Выдаліць файл + Прайграць файл + Узнавіць спампоўванне + Прыпыніць спампоўванне + Дадатковыя звесткі + Схаваць + Прайграць + Звесткі + Фільтр закладак + Закладкі + Выдаліць + Задаць статус прагляду + Прымяніць + Скапіраваць + Закрыць + Ачысціць + Захаваць + Назва рэпазіторыя і спасылка + скапіравана! + Паведамленне аб новай серыі + Пошук у іншых пашырэннях + Паказваць рэкамендацыі + Хуткасць прайгравальніка + Налады субцітраў + Колер тэксту + Колер контуру + Колер фону + Колер акна + Тып контуру + Уздым субцітраў + Шрыфт + Памер шрыфту + Пошук праз пастаўшчыкоў + Пошук праз тыпы + %d бенена(ў) дадзена распрацоўшчыкам + Бененаў не дадзена + Выбраць мову аўтаматычна + Спампаваць мовы + Мова субцітраў + Утрымайце, каб скінуць + Усталёўвайце шрыфты, перацягваючы іх да %s + Працягнуць прагляд + Выдаліць + Больш інфармацыі + \@string/home_play + Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN + Гэты пастаўшчык — Torrent, рэкамендуецца VPN + Сайт не пастаўляе метаданых, загрузіць відэа не ўдасца, калі на сайце яго няма. + Апісанне + Сюжэту не знойдзена + Апісання не знойдзена + Паказаць Logcat 🐈 + Журнал + Відарыс у відарысе + Працягвае прайграванне ў мініяцюры зверху іншых праграм + Кнопка змены памеру прайгравальніка + Прыбраць чорныя межы + Субцітры + Налады субцітраў прайгравальніка + Субцітры Chromecast + Налады субцітраў Chromecast + Хуткасць прайгравання + Дадаць параметр хуткасці да прайгравальніка + Чырканне для перамоткі + Правядзіце пальцам з боку ў бок, каб кіраваць пазіцыяй у відэа + Чырканне для змены налад + Правядзіце пальцам уверх або ўніз злева ці справа, каб змяніць яркасць або гучнасць + Аўтаматычнае прайграванне наступнай серыі + Прайграць наступную серыю пасля сканчэння бягучай + Падвойнае націсканне для перамоткі + Падвойнае націсканне для прыпынення + Крок перамоткі (у секундах) + Двойчы націсніце справа ці злева, каб перайсці наперад ці назад + Двойчы націсніце пасярэдзіне, каб прыпыніць прайграванне + Выкарыстоўваць сістэмную яркасць From 063d960c3a0d3c19eef2c38b8b86cf6371c81e68 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:57:28 -0700 Subject: [PATCH 053/333] Pin rhino version (#2369) --- 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 24f69824c..692fd3b4c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ paletteKtx = "1.0.0" preferenceKtx = "1.2.1" previewseekbarMedia3 = "1.1.1.0" qrcodeKotlin = "4.5.0" -rhino = "1.8.1" +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" From 3fe6a7853ae08e8df4ccf319e19d87b2d6ba8b01 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:07:08 -0700 Subject: [PATCH 054/333] Replace QuickJS with Zipline (#2256) QuickJS was renamed to Zipline all the way back in 2021. Unlike old QuickJS, newer Zipline versions are 16kb aligned. Current Zipline is also compatible back to minSdk 21. --- app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a7536da0d..e8a07b571 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -204,12 +204,12 @@ dependencies { // Extensions & Other Libs implementation(libs.jsoup) // HTML Parser implementation(libs.rhino) // Run JavaScript - implementation(libs.quickjs) implementation(libs.fuzzywuzzy) // Library/Ext Searching with Levenshtein Distance implementation(libs.safefile) // To Prevent the URI File Fu*kery coreLibraryDesugaring(libs.desugar.jdk.libs.nio) // NIO Flavor Needed for NewPipeExtractor implementation(libs.conscrypt.android) // To Fix SSL Fu*kery on Android 9 implementation(libs.jackson.module.kotlin) // JSON Parser + implementation(libs.zipline) // Torrent Support implementation(libs.torrentserver) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 692fd3b4c..f03f2d868 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" video = "1.0.0" workRuntimeKtx = "2.10.5" +zipline = "1.24.0" jvmTarget = "1.8" jdkToolchain = "17" @@ -100,7 +101,6 @@ palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "paletteK preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } previewseekbar-media3 = { module = "com.github.rubensousa:previewseekbar-media3", version.ref = "previewseekbarMedia3" } qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrcodeKotlin" } -quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } 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" } @@ -109,6 +109,7 @@ torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } 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" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From 81d9ecde674e23ad2d53b335230369aedaea4a51 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:21:59 -0700 Subject: [PATCH 055/333] Move untranslatable strings to seperate file (#2273) This could cause crashes or poisoned data on some languages as some untranslatable strings were being translated, including keys and format strings that shouldn't be translatable. Also when translating the episodes key on weblate it caused a conflict between the plural version (which weblate does support) and the actual episodes key, meaning the episodes key was translating as the singular version of the plural episodes version in some cases. Moving to a separate resource file should hopefully prevent these issues. --- app/src/main/res/values-b+af/strings.xml | 1 - app/src/main/res/values-b+am/strings.xml | 1 - app/src/main/res/values-b+apc/strings.xml | 3 - app/src/main/res/values-b+ar/strings.xml | 22 --- app/src/main/res/values-b+ars/strings.xml | 1 - app/src/main/res/values-b+as/strings.xml | 1 - app/src/main/res/values-b+bg/strings.xml | 3 - app/src/main/res/values-b+bn/strings.xml | 2 - app/src/main/res/values-b+cs/strings.xml | 12 -- app/src/main/res/values-b+de/strings.xml | 1 - app/src/main/res/values-b+el/strings.xml | 1 - app/src/main/res/values-b+eo/strings.xml | 1 - app/src/main/res/values-b+es/array.xml | 44 ----- app/src/main/res/values-b+es/strings.xml | 3 - app/src/main/res/values-b+fa/strings.xml | 1 - app/src/main/res/values-b+fil/strings.xml | 1 - app/src/main/res/values-b+fr/strings.xml | 1 - app/src/main/res/values-b+gl/strings.xml | 5 - app/src/main/res/values-b+hi/strings.xml | 5 - app/src/main/res/values-b+hr/strings.xml | 13 -- app/src/main/res/values-b+hu/strings.xml | 1 - app/src/main/res/values-b+in/strings.xml | 10 -- app/src/main/res/values-b+it/strings.xml | 1 - app/src/main/res/values-b+iw/strings.xml | 1 - app/src/main/res/values-b+ja/strings.xml | 1 - app/src/main/res/values-b+kn/strings.xml | 1 - app/src/main/res/values-b+ko/strings.xml | 1 - app/src/main/res/values-b+lt/strings.xml | 1 - app/src/main/res/values-b+lv/strings.xml | 1 - app/src/main/res/values-b+mk/strings.xml | 1 - app/src/main/res/values-b+ml/strings.xml | 1 - app/src/main/res/values-b+ms/strings.xml | 4 - app/src/main/res/values-b+mt/strings.xml | 1 - app/src/main/res/values-b+my/strings.xml | 1 - app/src/main/res/values-b+ne/strings.xml | 1 - app/src/main/res/values-b+nl/strings.xml | 9 -- app/src/main/res/values-b+nn/strings.xml | 1 - app/src/main/res/values-b+no/strings.xml | 3 - app/src/main/res/values-b+or/strings.xml | 1 - app/src/main/res/values-b+pl/array.xml | 44 ----- app/src/main/res/values-b+pl/strings.xml | 1 - app/src/main/res/values-b+pt+BR/strings.xml | 10 -- app/src/main/res/values-b+pt/strings.xml | 1 - app/src/main/res/values-b+qt/strings.xml | 1 - app/src/main/res/values-b+ro/strings.xml | 2 - app/src/main/res/values-b+ru/strings.xml | 3 - app/src/main/res/values-b+sk/strings.xml | 1 - app/src/main/res/values-b+so/strings.xml | 1 - app/src/main/res/values-b+sv/strings.xml | 3 - app/src/main/res/values-b+ta/strings.xml | 1 - app/src/main/res/values-b+ti/strings.xml | 1 - app/src/main/res/values-b+tl/strings.xml | 1 - app/src/main/res/values-b+tr/array.xml | 44 ----- app/src/main/res/values-b+tr/strings.xml | 35 ---- app/src/main/res/values-b+uk/strings.xml | 3 - app/src/main/res/values-b+ur/strings.xml | 1 - app/src/main/res/values-b+vi/array.xml | 44 ----- app/src/main/res/values-b+vi/strings.xml | 1 - app/src/main/res/values-b+zh+TW/strings.xml | 35 ---- app/src/main/res/values-b+zh/strings.xml | 33 ---- .../res/values/donottranslate-strings.xml | 152 ++++++++++++++++++ app/src/main/res/values/strings.xml | 152 +----------------- 62 files changed, 153 insertions(+), 580 deletions(-) create mode 100644 app/src/main/res/values/donottranslate-strings.xml diff --git a/app/src/main/res/values-b+af/strings.xml b/app/src/main/res/values-b+af/strings.xml index 71a18f7a5..81d7a96ae 100644 --- a/app/src/main/res/values-b+af/strings.xml +++ b/app/src/main/res/values-b+af/strings.xml @@ -105,7 +105,6 @@ Voer lettertipes in deur dit in %s te plaas Rolverdeling: %s Nuwe episode notifikasie - hide_player_control_names_key Gratis Gebruik Wis Uit diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index 26fb84dd3..7fd3274b9 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -108,5 +108,4 @@ ተጨማሪ መረጃ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ - hide_player_control_names_key diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 56e1a7e34..9bc697acf 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -573,8 +573,6 @@ حطو الأرقام السرية الحالية صوت حط كبسة لبرم إتجاه الشاشة - rotate_video_key - auto_rotate_video_key برم الشاشة أوتوماتيكيًا برومو غير إتجاه الشاشة أوتوماتيكيًا حسب شكل الڤيديو @@ -625,7 +623,6 @@ تجاهل فتاح الريپو فتاح %s ع تلفونك أو كمپيوترك، وحط الكود اللي فوق - hide_player_control_names_key بلشه من الأول تحذير فتاح الڤيديو اللي ع جهازك diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index ff697d99f..487b29d84 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -235,10 +235,6 @@ ملصق مدبلج ملصق مترجم العنوان - show_hd_key - show_dub_key - show_sub_key - show_title_key التحكم في عناصر الواجهة على الملصق لم يتم العثور على تحديثات تحقق من التحديثات @@ -270,8 +266,6 @@ امتداد تكبير إخلاء مسؤولية - legal_notice_key - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. عام زر العشوائي إظهار زر عشوائي على الصفحة الرئيسية والمكتبة @@ -291,10 +285,6 @@ مكان عنوان الملصق وضع العنوان تحت الملصق - anilist_key - mal_key - opensubtitles_key - nginx_key كلمة المرور إسم المستخدم البريد الإلكتروني @@ -302,14 +292,6 @@ إسم الموقع الجديد رابط الموقع مثلا : https://example.com اللغة (الإنجليزية) - %1$s %2$s حساب تسجيل الخروج @@ -332,7 +314,6 @@ الكل الحد الاقصي الحد الأدنى - @string/none الخطوط المحيطة النمط المنخفض ظل @@ -602,8 +583,6 @@ تعديل الحساب تم إعادة تحميل الروابط عرض زر تبديل لاتجاه الشاشة - تدوير الفيديو - مفتاح تدوير الفيديو التلقائي الدوران التلقائي تدوير تمكين التبديل التلقائي لاتجاه الشاشة بناءً على اتجاه الفيديو @@ -652,7 +631,6 @@ قم بزيارة %s على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية تنتهي صلاحية الرمز خلال %1$dm %2$ds - hide_player_control_names_key تشغيل من البداية فتح فيديو محلي تحذير diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index b8240dc2e..3104e6a9a 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -344,6 +344,5 @@ وثائقي موقع عنوان مشغل الفيديو بحد أقصى لعدد الأحرف - hide_player_control_names_key DNS عبر HTTPS diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index 68fc2e163..eb6ad4aa4 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -607,7 +607,6 @@ চাবটাইটলসমূহ এপিচ\'ড প্লে কৰক প্ৰয়োগ কৰক - hide_player_control_names_key ফাইলসমূহ ডিলিট কৰক ডিলিট (%1$d | %2$s) আপুনি স্থায়ীভাৱে তলত দিয়া আইটেমসমূহ ডিলিট কৰিবলৈ নিশ্চিত নেকি? diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index 9eb439c88..2c238b968 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -296,8 +296,6 @@ НовоИмеНаСайт example.com Езиков код (en) - %1$s %2$s Акаунт Излизане @@ -588,7 +586,6 @@ Покажи предложения Добавя опция за промяна на скоростта в плеъра Този тест е направен за програмисти и не проверява работата на никакви добавки. - hide_player_control_names_key Предстоящо в %s Име на хранилището и URL адрес копирани! diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index f65d673ae..2e37f43f3 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -238,7 +238,6 @@ অ্যান্ড্রয়েড টিভির মতো, কম মেমরির ডিভাইসে খুব বেশি সেট করা হলে সমস্যা করবে। ক্লোন সাইট প্লেয়ারের ফিচার - MAL AniList TMDB IMDB Kitsu Trakt %1$s%2$s অ্যাপ থিম রিকমেন্ডেশনগুলো দেখাও প্লেয়ারে গতির বিকল্প যোগ কর @@ -351,5 +350,4 @@ অ্যাকাউন্ট প্রস্থান %1$d%2$s - hide_player_control_names_key diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index c42cb4c18..e553ccd73 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -253,7 +253,6 @@ Roztáhnout Přiblížit Odmítnutí odpovědnosti - Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí. Obecné Náhodné tlačítko Zobrazit na domovské stránce a v knihovně náhodné tlačítko @@ -275,14 +274,6 @@ Uživatelské jméno ahoj@svete.cz 127.0.0.1 - %1$s %2$s účet Odhlásit se @@ -594,8 +585,6 @@ Upravit účet Odkazy znovu načteny Zobrazit tlačítko pro přepnutí otočení obrazovky - rotate_video_key - auto_rotate_video_key Automatické otáčení Otočení Zapnout automatické otáčení obrazovky v závislosti na orientaci videa @@ -644,7 +633,6 @@ Účty Lokální ověření PIN kód vypršel! - hide_player_control_names_key Přehrát od začátku Aktuálně neprobíhají žádná stahování. Otevřít místní video diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index eb5734ca1..67cf55fd7 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -602,7 +602,6 @@ Zurücksetzen Akkuverbrauch der App ist bereits auf unbeschränkt eingestellt CloudStreams App-Info kann nicht geöffnet werden. - hide_player_control_names_key Staffel %1$d Episode %2$d wird veröffentlicht in Wird veröffentlicht in %s Sicherheit diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index 96da7f206..4b671644b 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -611,7 +611,6 @@ Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία. Λογαριασμοί Ασφάλεια - hide_player_control_names_key Απόρριψη Ενσωματωμένο Συνδεμένοι diff --git a/app/src/main/res/values-b+eo/strings.xml b/app/src/main/res/values-b+eo/strings.xml index e3e428075..f957da076 100644 --- a/app/src/main/res/values-b+eo/strings.xml +++ b/app/src/main/res/values-b+eo/strings.xml @@ -127,5 +127,4 @@ Elŝutite Elŝutante Elŝuto Malsukcesite - hide_player_control_names_key diff --git a/app/src/main/res/values-b+es/array.xml b/app/src/main/res/values-b+es/array.xml index 376519bf3..1a7ca4608 100644 --- a/app/src/main/res/values-b+es/array.xml +++ b/app/src/main/res/values-b+es/array.xml @@ -290,48 +290,4 @@ Vietnamita (VISCII) Vietnamita (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 8cd37933b..390c2df58 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -567,8 +567,6 @@ Editar la cuenta Enlaces recargados Mostrar un botón para cambiar la orientación de la pantalla - rotate_video_key - auto_rotate_video_key Giro automático Girar Activar el cambio automático de la orientación de la pantalla en función de la orientación del vídeo @@ -617,7 +615,6 @@ ¡El código PIN ya ha caducado! El código caduca en %1$d mín y %2$d s No puedo obtener el código PIN del dispositivo; intente con la autenticación local - hide_player_control_names_key Reproducir desde el principio Abrir vídeo de forma local Advertencia diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index 146236651..da6f04d8e 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -190,7 +190,6 @@ پیش‌فرض کارتون تورنت - hide_player_control_names_key این ارائه‌دهنده تورنتی است، استفاده از VPN توصیه می‌شود بارگزاری پرونده پشتیبانی‌ ارتفاع زیرنویس diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index f8ba8fa47..d4844d1d7 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -1,6 +1,5 @@ - hide_player_control_names_key Maling PIN. Pakisubukang muli. Alisin ang napanood hanggang sa episode na ito Walang koneksyon sa internet.\n\nKumonekta sa internet at subukang muli, o panoorin ang iyong mga na-download habang ikaw ay offline. diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index d8ecc4dae..a4e669ccf 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -606,7 +606,6 @@ Verrouillage biométrique Sélectionnez un appareil de diffusion Saison %1$d Episode %2$d sera publié dans - hide_player_control_names_key Regarder depuis le début Ouvrir une vidéo locale Attention diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index aeb76080e..1b8f068e3 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -161,7 +161,6 @@ Selecciona o modo para filtrar a descarga dos complementos Instala automáticamente todos os complementos aínda non instalados dos repositorios engadidos. Mostrar actualizacións da aplicación - hide_player_control_names_key Reiniciar aos valores predefinidos -30 Audiolibro @@ -274,10 +273,6 @@ Ligazón copiada ó portapapeis Reproducir capítulo Temporada - - Capítulos - - %1$d-%2$d %1$d %2$s T diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 8b7c79650..2c5247238 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -202,7 +202,6 @@ रूपरेखा रंग उपशीर्षक ऊंचाई मुद्रलिपि - hide_player_control_names_key वर्तमान में कोई डाउनलोड नहीं है। आरम्भ से शुरू करें मिटाने के लिए वस्तु चुनें @@ -312,10 +311,6 @@ लाइब्रेरी मीडिया खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं - - प्रकरण - - %1$d-%2$d @string/home_play प्लेयर में आगे पीछे जाने का समय (सेकंड्स) diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index 902a75500..1e2c344ab 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s epizoda %2$d Glumačka postava: %s Epizoda %d će izaći za @@ -285,7 +276,6 @@ Rastegni Zoom Pravna obavijest - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. Općenito Gumb za slučajni odabir Prikaži gumb za slučajni odabir na početnoj stranici i biblioteci @@ -597,8 +587,6 @@ Prikaži gumb za prebacivanje orijentacije zaslona Omogućuje automatsko mijenjanje orijentacije zaslona na temelju orijentacije videa Automatsko rotiranje - rotiraj_video_tipka - automatski_rotiraj_video_tipka Obavijest za novu epizodu Traži u drugim proširenjima Dodaje opciju za brzinu u playeru @@ -636,7 +624,6 @@ CloudStream Wiki Računi Sigurnost - hide_player_control_names_key Lokalna autentifikacija Otvori lokalni video Posjeti %s na svom mobitelu ili računalu i unesi gore navedeni kod diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 1e97719c0..ae018207b 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -579,7 +579,6 @@ A PIN 4 karakter hosszú kell legyen Auto elforgatás Az automatikus videó orientáció alapján való képernyő elforgatás bekapcsolása - hide_player_control_names_key Helyi videó megnyitása Tárhely név és URL Ez a videó egy Torrent, ami azt jelenti, hogy a videótevékenységed nyomon követhető.\nGyőződj meg róla, hogy megérted a torrentezés működését, mielőtt folytatnád. diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index ca3a39906..17b4f075d 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -250,7 +250,6 @@ Regang Zoom Disclaimer - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. Umum Tombol Acak Tampilkan tombol acak di Beranda dan Pustaka @@ -267,14 +266,6 @@ Lokasi judul poster Meletakkan judul di bawah poster - %1$s %2$s akun Keluar @@ -632,7 +623,6 @@ CloudStream Wiki Keamanan Akun - hide_player_control_names_key Hati hati Kode PIN kini telah kedaluwarsa! Gambar Kode QR diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index fee5b56f3..3962f5d23 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -640,7 +640,6 @@ Impossibile ottenere il codice PIN del dispositivo, prova l\'autenticazione locale Il codice PIN è scaduto! Il codice scadrà tra %1$dm %2$ds - hide_player_control_names_key Apri il video locale Al momento non ci sono download. Riproduci dall\'inizio diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 558d98a4b..ef4cb9202 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -537,7 +537,6 @@ \nיגרמו לעדיפות הסרטון להיות 10. \n \nשימו לב: אם הסכום הוא 10 או יותר, הנגן ידלג על טעינת הסרטון כאשר הלינק נטען! - hide_player_control_names_key עונה %1$d פרק %2$d תשודר ב: %1$d שעות %2$d דקות %3$d שניות %1$d דקות %2$d שניות diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index df0559ef7..e246c6f27 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -234,7 +234,6 @@ 現在のエピソードが終了したら次のエピソードを開始する 長押しするとデフォルトにリセットされます ダウンロードを再開 - hide_player_control_names_key ブックマークのフィルタ プロットが見つかりません ダブルタップで一時停止 diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index 3a77aeef0..22a45b906 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -129,5 +129,4 @@ Brightness ಅಥವಾ volume ಬದಲಾಯಿಸಲು ಎಡ ಅಥವಾ ಬಲಭಾಗದಲ್ಲಿ ಮೇಲಕ್ಕೆ ಅಥವಾ ಕೆಳಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 7ab550913..af84eb3a9 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -620,7 +620,6 @@ %s의 PIN 입력 즐겨찾기에서 제거 캐스트미러 - hide_player_control_names_key 플러그인 삭제 경고 탐색바 미리보기 diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index 357192018..cb2d816f3 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -254,5 +254,4 @@ Ar tikrai norite išeiti? Pašalinti iš žiūrimų Garso takelis - hide_player_control_names_key diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 444a59a5f..b87e9e4fe 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -509,7 +509,6 @@ Abonēto šovu atjaunināšana Abonēts Abonēts %s - hide_player_control_names_key %1$d. sezona un %2$d. sērija tiks izlaista pēc %1$dh %2$dm %3$ds %1$dm %2$ds diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index bccc2a00d..6998c49db 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -593,7 +593,6 @@ Грешка при пристапот до таблата со исечоци, обиди се повторно. Грешка при копирање, молам копирај го логот и контактирај ја поддршката на апликацијата. Аудио книга - hide_player_control_names_key Безбедност Отфрли Отвори извор diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index dcb9e5270..d1c9409a3 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -272,5 +272,4 @@ എഡ്ജ് തരം ഔട്ട്ലൈൻ നിറം പശ്ചാത്തല നിറം - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index 8bbb2a7e0..83492a5ff 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -54,7 +54,6 @@ Kongsi Tetapan Tutup - hide_player_control_names_key Pratonton Resensi:%.1f Kemas kini baru dijumpai! @@ -516,9 +515,6 @@ Sandarkan data Gagal pulihkan data dari fail %s Ralat sandaran %s - - Episode - Akan datang pada %s Ini akan memadamkan secara kekal %s\nAdakah anda pasti? Adakah anda pasti mahu memadamkan item berikut secara kekal?\n\n%s diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index ca62a043b..ea859ee29 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -122,5 +122,4 @@ Bookmarks Neħħi Falla t-tniżżil - hide_player_control_names_key diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index 1d35cbaa0..4a7a50aa7 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -537,5 +537,4 @@ သင်နဂိုတည်းကသတ်မှတ်ပြီး လိုက်ဘရီရွေးချယ်ရန် ဖြင့်ဖွင့်မည် - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index 9345cab2b..8a432a505 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -127,5 +127,4 @@ प्लेयरको उपशीर्षकको सेटिङ रिपोजिटरी को नाम र यूआरएल कपी गरियो! - hide_player_control_names_key diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index 6e0982c79..54508e652 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -292,14 +292,6 @@ MyCoolSite voorbeeld.com Taalcode (nl) - %1$s %2$s account Log uit @@ -594,7 +586,6 @@ Link opnieuw geladen Autoroteer Roteer - hide_player_control_names_key Er zijn momenteel geen downloads beschikbaar. Gekopieerd! Verbergen diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 2cf83c183..245bf6618 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -191,5 +191,4 @@ Bilde i bilde Fortsett å sjå Prøv tilkopling på nytt… - hide_player_control_names_key diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index a981609cf..374b033c6 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -192,8 +192,6 @@ Primær Farge App Tema Foretrukket Videoinnhold - Disclaimer - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. Besetning: %s %dm Tøm @@ -525,5 +523,4 @@ Bruk Hjelp Profilbakgrunn - hide_player_control_names_key diff --git a/app/src/main/res/values-b+or/strings.xml b/app/src/main/res/values-b+or/strings.xml index 807a3bcc6..8c9379f5b 100644 --- a/app/src/main/res/values-b+or/strings.xml +++ b/app/src/main/res/values-b+or/strings.xml @@ -155,7 +155,6 @@ କୌଣସି ତଥ୍ୟ ନାହିଁ %1$s ଅ %2$d ଆଦ୍ୟ ବାଦ୍ ଦିଅ - hide_player_control_names_key ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ diff --git a/app/src/main/res/values-b+pl/array.xml b/app/src/main/res/values-b+pl/array.xml index 7b1683b41..466066852 100644 --- a/app/src/main/res/values-b+pl/array.xml +++ b/app/src/main/res/values-b+pl/array.xml @@ -299,48 +299,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index e067b391c..59ac0db4b 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -621,7 +621,6 @@ Odrzuć Otwórz repozytorium Odwiedź %s na swoim smartfonie lub komputerze i wprowadź powyższy kod - hide_player_control_names_key Odtwarzaj od początku Usuń wtyczkę Uwaga 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 3fbc4fb28..e74a7db74 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -266,7 +266,6 @@ Esticar Zoom Aviso Legal - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. Geral Botão Aleatório Mostrar botão aleatório na página inicial e na biblioteca @@ -291,14 +290,6 @@ NovoNomedoSite https://example.com Codigo da Língua (bp) - %1$s %2$s Conta Sair @@ -640,7 +631,6 @@ Não é possível obter o código PIN do dispositivo, tente a autenticação local O código PIN expirou! O código expira em %1$dm %2$ds - hide_player_control_names_key Reproduzir do começo Reprovou alguns testes Excluir plugin diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index 23b49195f..e7b3623e6 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -606,7 +606,6 @@ Temporada %1$d Episódio %2$d será lançado em Escolha o dispositivo Transmitir - hide_player_control_names_key Abrir vídeo local Não é possível receber o PIN do dispositivo, tentando autenticação local Pré-visualização na barra de progresso diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 3de0f32df..d60a4e32c 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -239,7 +239,6 @@ oooooh uuaagh @string/home_play oouuhhh ahhooo-ahah - hide_player_control_names_key uuuugg aaaahh oogg aagg uuuuggg og %1$dm %2$ds aaaahhh ag diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index 642eea0c3..e2dbd0e32 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -261,7 +261,6 @@ Întindere Mărire Aviz juridic (declinarea responsabilității și drepturi de autor) - Orice probleme legale privind conținutul acestei aplicații ar trebui să fie rezolvate cu furnizorii și gazdele actuale de fișiere, întrucât noi nu suntem afiliați cu aceștia. În caz de încălcare a drepturilor de autor, vă rugăm să contactați direct părțile responsabile sau site-urile de streaming. Aplicația este destinată exclusiv utilizării educaționale și personale. CloudStream 3 nu găzduiește niciun fel de conținut în aplicație și nu are niciun control asupra conținutului media care este pus sau retras. CloudStream 3 funcționează ca orice alt motor de căutare, cum ar fi Google. CloudStream 3 nu găzduiește, nu încarcă și nu gestionează niciun videoclip, film sau conținut. Pur și simplu navighează, adună și afișează linkuri într-o interfață convenabilă și ușor de utilizat. Pur și simplu, acesta extrage paginile web ale unor terțe părți care sunt accesibile publicului prin intermediul oricărui browser web obișnuit. Este responsabilitatea utilizatorului de a evita orice acțiune care ar putea încălca legile care guvernează locația sa. Utilizați CloudStream 3 pe propria răspundere. General Aleatoriu Afișează butonul pentru aleatoriu pe Pagina Principală și în Bibliotecă @@ -627,7 +626,6 @@ Sezonul %1$d Episod %2$d va fi lansat în Selectați divece-ul pe care doriți să faceți cast Cast mirror - hide_player_control_names_key Redă de la început Nu există descărcări. Selectionati elementele de sters diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index d25f7e274..d8b369a3d 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -555,12 +555,10 @@ Добавить в любимое Включить автоматическую смену ориентации экрана на основе ориентации видео Автоповорот - rotate_video_key Использовать учётную запись по умолчанию Отписаться Заменить Введите текущий ПИН-код - auto_rotate_video_key Любимые %s добавлено в любимые Введите ПИН-код от %s @@ -600,7 +598,6 @@ Сезон %1$d серия %2$d выйдет Выйдет %s Выберите устройство для трансляции - hide_player_control_names_key В данный момент скачиваний нет. Играть с самого начала Открыть локальное видео diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index fb65841f2..93505971c 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -369,7 +369,6 @@ Pridať repozitár Názov repozitára Zobraziť komunitné repozitáre - hide_player_control_names_key HD Prehrávač Rozlíšenie a titul diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index fc42c63f7..09499af00 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -472,5 +472,4 @@ Bilowga Bilow isku qasan Qoraalka dhamaadka - hide_player_control_names_key diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index dfbfce4b5..75c7efda4 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -465,7 +465,6 @@ Redigera konto Loggat in som %s Hoppa över val av konto vid start - auto_rotera_video_nyckel Gå förbi blockering av rå GitHub-URL:er med jsDelivr. Kan göra att uppdateringar försenas med några dagar. Funktion Önskad media @@ -557,7 +556,6 @@ %s togs bort från favoriter %s har lagts till i favoriter Använd standard konto - rotera_video_nyckel PIN-kod Sök mängden som används när spelaren är dold Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek: @@ -612,7 +610,6 @@ CloudStream Wiki Konton Säkerhet - hide_player_control_names_key Avfärda Öppna databasen Koden löper ut om %1$dm %2$ds diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 94b6f717a..e223f6c60 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -576,7 +576,6 @@ ஆதாரங்கள் எவ்வாறு உத்தரவிடப்படுகின்றன என்பதை இங்கே மாற்றலாம். ஒரு வீடியோவுக்கு அதிக முன்னுரிமை இருந்தால், அது மூல தேர்வில் அதிகமாகத் தோன்றும். மூல முன்னுரிமையின் தொகை மற்றும் தரமான முன்னுரிமை ஆகியவை வீடியோ முன்னுரிமை. \n\n சான்று A: 3 \n தகுதி பி: 7 \n 10 இன் ஒருங்கிணைந்த வீடியோ முன்னுரிமை இருக்கும். \n\n குறிப்பு: தொகை 10 அல்லது அதற்கு மேற்பட்டதாக இருந்தால், அந்த இணைப்பு ஏற்றப்படும்போது பிளேயர் தானாகவே ஏற்றுவதைத் தவிர்க்கும்! உங்கள் கிளவுட்ச்ட்ரீம் தரவு இப்போது காப்புப் பிரதி எடுக்கப்பட்டுள்ளது. இதன் சாத்தியம் மிகக் குறைவு என்றாலும், எல்லா சாதனங்களும் வித்தியாசமாக நடந்து கொள்ளலாம். அரிய விசயத்தில், பயன்பாட்டை அணுகுவதிலிருந்து நீங்கள் பூட்டப்படுகிறீர்கள், பயன்பாட்டு தரவை முழுவதுமாக அழித்து, காப்புப்பிரதியிலிருந்து மீட்டெடுக்கவும். இதிலிருந்து எழும் ஏதேனும் சிரமத்திற்கு நாங்கள் மிகவும் வருந்துகிறோம். ஊடகம் - hide_player_control_names_key கணக்குகள் எச்சரிக்கை தற்போது பதிவிறக்கங்கள் எதுவும் இல்லை. diff --git a/app/src/main/res/values-b+ti/strings.xml b/app/src/main/res/values-b+ti/strings.xml index 6c154c8d8..46235bbd7 100644 --- a/app/src/main/res/values-b+ti/strings.xml +++ b/app/src/main/res/values-b+ti/strings.xml @@ -3,5 +3,4 @@ %1$s ክፋል %2$d ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s - hide_player_control_names_key diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index 94bb8ea1d..4050ddbd7 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -257,5 +257,4 @@ Mga Subtitle ng Chromecast Mga setting ng mga subtitle ng Chromecast Maglaro ng Trailer - hide_player_control_names_key diff --git a/app/src/main/res/values-b+tr/array.xml b/app/src/main/res/values-b+tr/array.xml index 56cea4c9c..dbc2d3e66 100644 --- a/app/src/main/res/values-b+tr/array.xml +++ b/app/src/main/res/values-b+tr/array.xml @@ -319,48 +319,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index f6a125b91..10137e7b5 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s B. %2$d Cast: %s Bölüm %d şu tarihte yayınlanacak @@ -22,9 +13,7 @@ Bölüm Afişi Ana Afiş Sonraki Rastgele - @string/play_episode Geri git - @string/home_change_provider_img_des Sağlayıcıyı Değiştir Arkaplanı Önizle @@ -45,7 +34,6 @@ Veri Yok Daha Fazla Seçenek Sonraki bölüm - @string/synopsis Türler Paylaş Tarayıcıda aç @@ -74,7 +62,6 @@ İndirme Başarısız İndirme İptal Edildi İndirme Tamamlandı - %s - %s Ağ akışı Bağlantılar yüklenirken hata oluştu Dahili Depolama @@ -258,10 +245,6 @@ Dublaj etiketi Altyazı etiketi Başlık - show_hd_key - show_dub_key - show_sub_key - show_title_key Afiş üzerindeki öğeleri değiştir Güncelleme bulunamadı Güncellemeleri denetle @@ -293,8 +276,6 @@ Uzat Yakınlaştır Yasal uyarı - legal_notice_key - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. Genel Rastgele düğmesi Ana sayfa ve kütüphane üstünde rastgele düğmesini göster @@ -314,10 +295,6 @@ Afiş başlık konumu Başlığı afişin altına yerleştir - anilist_key - mal_key - opensubtitles_key - nginx_key şifre123 Kullanıcı Adı hello@world.com @@ -325,14 +302,6 @@ YeniSiteAdı https://ornek.com Dil kodu (tr) - %1$s %2$s hesap Çıkış yap @@ -355,7 +324,6 @@ Tümü Azami Asgari - @string/none Dış hat Çökmüş Gölge @@ -614,8 +582,6 @@ PIN Geçerli PIN\'i Giriniz Ekran yönü için bir geçiş düğmesi göster - rotate_video_key - auto_rotate_video_key Otomatik döndür Döndür Video yönüne göre ekran yönünün otomatik olarak değişmesini sağla @@ -664,7 +630,6 @@ Cihaz PIN kodu alınamıyor, yerel kimlik doğrulamayı deneyin PIN kodunun süresi doldu! Kodun süresi %1$dm %2$ds içinde doluyor - hide_player_control_names_key Şu an hiç bir indirme bulunmamaktadır. Eklentiyi sil Yerel videoyu aç diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 54562f263..20bd9e6e2 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -550,10 +550,8 @@ Керувати обліковими записами Редагувати обліковий запис Показувати кнопку перемикання орієнтації екрана - rotate_video_key Обернути Покликання перезавантажено - auto_rotate_video_key Автообертання Увімкнути автоматичну зміну орієнтації екрана відповідно до відео Додати налаштування швидкості до програвача @@ -600,7 +598,6 @@ Термін дії коду закінчується через %1$dхв %2$dс Локальна автентифікація Відхилити - hide_player_control_names_key Відтворити з початку Попередження Видалити розширення diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index d2c3d9f1c..5f6d8aa14 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -604,7 +604,6 @@ دیگر ایکسٹینشنز میں تلاش کریں سفارشات دکھائیں آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔ - hide_player_control_names_key سیزن %1$d کی قسط %2$d جاری ہوگی شروع سےپلے کریں کلام شناسی دستیاب نہیں diff --git a/app/src/main/res/values-b+vi/array.xml b/app/src/main/res/values-b+vi/array.xml index d1887505e..16d45e516 100644 --- a/app/src/main/res/values-b+vi/array.xml +++ b/app/src/main/res/values-b+vi/array.xml @@ -291,48 +291,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index aa9caffd1..b26c715f3 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -630,7 +630,6 @@ Truy cập %s trên điện thoại hoặc máy tính và nhập mã bên trên Mã PIN đã hết hạn! Mã sẽ hết hạn trong %1$dm %2$ds - hide_player_control_names_key Không lấy được mã PIN, hãy thử xác thực cục bộ Hiện không có bản tải xuống nào. Xác thực cục bộ diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index 251df543c..7dc4b48f2 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s 共 %2$d 集 演員:%s 第 %d 集即將發佈於 @@ -22,9 +13,7 @@ 劇集封面 主封面 隨機下一個 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改片源 預覽背景 @@ -45,7 +34,6 @@ 無資料 更多選項 下一集 - @string/synopsis 類型 分享 在瀏覽器中打開 @@ -74,7 +62,6 @@ 下載失敗 下載取消 下載完畢 - %s - %s 網路串流 載入連結錯誤 內部儲存空間 @@ -259,10 +246,6 @@ 配音標籤 字幕標籤 標題 - show_hd_key - show_dub_key - show_sub_key - show_title_key 封面內容 未找到更新 檢查更新 @@ -294,8 +277,6 @@ 拉伸 縮放 免責聲明 - legal_notice_key - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. 通用 隨機按鈕 在主畫面與媒體庫中顯示隨機按鈕 @@ -315,10 +296,6 @@ 封面標題位置 將標題移到封面下方 - anilist_key - mal_key - opensubtitles_key - nginx_key 密碼 使用者名稱 電子郵件 @@ -326,14 +303,6 @@ 新網站名稱 https://example.com 語言代號 (zh_TW) - %1$s %2$s 帳號 登出 @@ -356,7 +325,6 @@ 全部 最大 最小 - @string/none 輪廓 凹陷 陰影 @@ -614,9 +582,7 @@ \n注意:如果加總達到 10 或更高,則載入該連結時播放器將自動跳過載入! 輸入目前的 PIN 碼 顯示切換畫面方向的按鈕 - rotate_video_key 選擇篩選外掛程式下載的模式 - auto_rotate_video_key 自動旋轉 旋轉 根據影片方向自動切換畫面方向 @@ -656,7 +622,6 @@ 為了確保下載與通知已訂閱的電視節目的不間斷,CloudStream 需要取得在背景執行的權限。若點選「確定」,將移至「應用程式資訊」,請找到「應用程式電池使用」並將電池用量設置為「無限制」。請注意,取得此權限並不表示 CS3 會明顯增加電池用量,而是只在必要時在背景執行,例如取得通知或使用官方擴充功能下載影片時。若選擇「取消」,您可以稍後在「一般設定」中調整此設定。 CloudStream Wiki 此裝置不支援生物特徵認證 - hide_player_control_names_key 無法取得裝置 PIN 碼,請嘗試本機驗證 刪除外掛程式 開啟資源庫 diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 224041e49..0301a3a2d 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s 共 %2$d 集 演员:%s 第 %d 集将发布于 @@ -22,9 +13,7 @@ 剧集封面 主封面 随机下一个 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改片源 预览背景 @@ -45,7 +34,6 @@ 无数据 更多选项 下一集 - @string/synopsis 类型 分享 在浏览器中打开 @@ -74,7 +62,6 @@ 下载失败 下载取消 下载完毕 - %s - %s 播放 加载链接错误 内部存储 @@ -260,10 +247,6 @@ 配音标签 字幕标签 标题 - show_hd_key - show_dub_key - show_sub_key - show_title_key 封面内容 未找到更新 检查更新 @@ -295,8 +278,6 @@ 拉伸 缩放 免责声明 - legal_notice_key - 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. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. 通用 随机按钮 在主页和库中显示随机按钮 @@ -316,10 +297,6 @@ 封面标题位置 将标题移至封面下方 - anilist_key - mal_key - opensubtitles_key - nginx_key 密码 用户名 邮箱 @@ -327,14 +304,6 @@ 网站名称 网站链接 语言代码 (zh) - %1$s %2$s 账户 注销 @@ -357,7 +326,6 @@ 全部 最大 最小 - @string/none 轮廓 凹陷 阴影 @@ -654,7 +622,6 @@ 选择投射设备 %1$d季%2$d集将在 投射镜像 - hide_player_control_names_key 目前尚无下载。 打开本地视频 安全 diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml new file mode 100644 index 000000000..5f2186fae --- /dev/null +++ b/app/src/main/res/values/donottranslate-strings.xml @@ -0,0 +1,152 @@ + + + + search_providers_list + app_locale + search_type_list + auto_update + auto_update_plugins + auto_download_plugins_key2 + skip_update_key + install_prerelease_key + manual_check_update + fast_forward_button_time + benene_count + subtitle_settings_key + test_providers_key + subtitle_settings_chromecast_key + quality_pref_key + quality_pref_mobile_data_key + player_default_key + prefer_limit_title_key + prefer_limit_title_rez_key + apk_installer_key + video_buffer_size_key + video_buffer_length_key + video_buffer_clear_key + video_buffer_disk_key + use_system_brightness_key + swipe_enabled_key + playback_speed_enabled_key + player_resize_enabled_key + pip_enabled_key + double_tap_enabled_key + double_tap_pause_enabled_key + double_tap_seek_time_key2 + android_tv_interface_off_seek_key + android_tv_interface_on_seek_key + swipe_vertical_enabled_key + autoplay_next_key + display_sub_key + show_fillers_key + show_trailers_key + show_kitsu_posters_key + random_button_key + provider_lang_key + dns_key + jsdelivr_proxy_key + download_path_key + download_parallel_key + download_concurrent_key + download_path_key_visual + Cloudstream + app_layout_key + primary_color_key + restore_key + backup_key + automatic_backup_key + prefer_media_type_key_2 + app_theme_key + episode_sync_enabled_key + log_enabled_key + show_logcat_key + bottom_title_key + poster_ui_key + overscan_key + poster_size_key + subtitles_encoding_key + override_site_key + redo_setup_key + filter_sub_lang_key + pref_filter_search_quality_key + enable_nsfw_on_providers_key + skip_startup_account_select_key + enable_skip_op_from_database + rotate_video_key + auto_rotate_video_key + biometric_key + battery_optimisation + show_hd_key + show_dub_key + show_sub_key + show_rating_key + show_title_key + show_episode_text_key + hide_player_control_names_key + preview_seekbar_key + backup_path_key + backup_dir_key + confirm_exit_key + software_decoding_key2 + manual_update_plugins + legal_notice_key + speedup_key + anilist_key + simkl_key + mal_key + opensubtitles_key + subdl_key + + pref_category_security_key + pref_category_gestures_key + pref_category_android_tv_key + + tv_no_focus_tag + + + %1$d %2$s | %3$s + %1$s • %2$s + %1$s - %2$s + %1$s / %2$s + %1$s %2$s + S1E1 + +%d + -%d + %d + %d + %s/10.0 + %d + + @string/play_episode + @string/home_change_provider_img_des + @string/synopsis + + @string/none + @string/none + @string/cancel + @string/cancel + @string/action_default + @string/action_default + + 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. + + The 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. + 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, + user-friendly interface. + + It 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. + + + + @string/episode + @string/episodes + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa586e8a..8ad0ec423 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,91 +1,6 @@ - - search_providers_list - app_locale - search_type_list - auto_update - auto_update_plugins - auto_download_plugins_key2 - skip_update_key - install_prerelease_key - manual_check_update - fast_forward_button_time - benene_count - subtitle_settings_key - test_providers_key - subtitle_settings_chromecast_key - quality_pref_key - quality_pref_mobile_data_key - player_default_key - prefer_limit_title_key - prefer_limit_title_rez_key - apk_installer_key - video_buffer_size_key - video_buffer_length_key - video_buffer_clear_key - video_buffer_disk_key - use_system_brightness_key - swipe_enabled_key - playback_speed_enabled_key - player_resize_enabled_key - pip_enabled_key - double_tap_enabled_key - double_tap_pause_enabled_key - double_tap_seek_time_key2 - android_tv_interface_off_seek_key - android_tv_interface_on_seek_key - swipe_vertical_enabled_key - autoplay_next_key - display_sub_key - show_fillers_key - show_trailers_key - show_kitsu_posters_key - random_button_key - provider_lang_key - dns_key - jsdelivr_proxy_key - download_path_key - download_parallel_key - download_concurrent_key - download_path_key_visual - Cloudstream - app_layout_key - primary_color_key - restore_key - backup_key - automatic_backup_key - prefer_media_type_key_2 - app_theme_key - episode_sync_enabled_key - log_enabled_key - show_logcat_key - bottom_title_key - poster_ui_key - overscan_key - poster_size_key - subtitles_encoding_key - override_site_key - redo_setup_key - filter_sub_lang_key - pref_filter_search_quality_key - enable_nsfw_on_providers_key - skip_startup_account_select_key - enable_skip_op_from_database - rotate_video_key - auto_rotate_video_key - biometric_key - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %s/10.0 - %d %1$s Ep %2$d Cast: %s Episode %d will be released in @@ -102,10 +17,8 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back Play from the Beginning - @string/home_change_provider_img_des Change Provider Preview Background @@ -127,7 +40,6 @@ No Data More Options Next episode - @string/synopsis Genres Share Open In Browser @@ -139,7 +51,6 @@ Completed Dropped Plan to Watch - @string/none Rewatching Play Movie Play Trailer @@ -161,7 +72,6 @@ Download Failed Download Canceled Download Done - %s - %s Select Items to Delete There are currently no downloads. Available for watching offline @@ -188,7 +98,6 @@ Remove Set watch status Apply - @string/cancel Copy Close Clear @@ -342,8 +251,6 @@ queued No Subtitles Default - @string/action_default - @string/action_default Free Used App @@ -359,10 +266,6 @@ Livestreams NSFW Others - - @string/episode - @string/episodes - Movie Series @@ -403,12 +306,6 @@ Rating Label Title Episode Text - show_hd_key - show_dub_key - show_sub_key - show_rating_key - show_title_key - show_episode_text_key Toggle UI elements on poster No Update Found Check for Update @@ -448,23 +345,6 @@ Stretch Zoom Disclaimer - legal_notice_key - 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. - - The app is purely for educational and personal use. - - CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. - CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or - manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, - user-friendly interface. - - It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. - ISP Bypasses Links App updates @@ -473,11 +353,8 @@ Actions Cache Android TV - pref_category_android_tv_key Gestures - pref_category_gestures_key Security - pref_category_security_key Accounts Player features Subtitles @@ -507,12 +384,6 @@ Poster title location Put the title under the poster - anilist_key - simkl_key - mal_key - opensubtitles_key - subdl_key - nginx_key password123 Username hello@world.com @@ -520,14 +391,6 @@ NewSiteName https://example.com Language code (en) - %1$s %2$s account Log out @@ -552,7 +415,6 @@ All Max Min - @string/none Outline Depressed Shadow @@ -653,7 +515,7 @@ View community repositories Public list Uppercase all subtitles - Warning: CloudStream 3 does not take any responsibility for using third-party extensions and does not provide any support for them! + Warning: CloudStream does not take any responsibility for using third-party extensions and does not provide any support for them! %s (Disabled) Tracks Audio tracks @@ -707,7 +569,6 @@ TV shows, CloudStream needs permission to run in background. By pressing "OK", you\'ll be shown a request dialog. Please press \'Allow\'.\n\nPlease note, this permission does not mean CS3 will drain your battery. It will only operate in the background when necessary, such as when receiving notifications or downloading videos from official extensions. - battery_optimisation App battery usage is already set to unrestricted Unable to open CloudStream\'s App info. Downloading app update… @@ -777,7 +638,6 @@ Add Replace Replace All - @string/cancel It appears that a potentially duplicate item already exists in your library: \'%s.\' @@ -790,7 +650,6 @@ \n\nWould you like to add this item anyway, replace the existing ones, or cancel the action? - tv_no_focus_tag Enter PIN Enter PIN for %s Enter Current PIN @@ -827,27 +686,21 @@ Code expires in %1$dm %2$ds Release Date (New to Old) Release Date (Old to New) - hide_player_control_names_key Hide names of the player\'s controls - preview_seekbar_key Seekbar preview Enable preview thumbnail on seekbar No subtitles loaded yet - backup_path_key Backup folder location - backup_dir_key Custom Confirm before exiting Show dialog before exiting the app - confirm_exit_key Show Don\'t Show Edge Size Enable torrent in Settings/Providers/Preferred media Restart app and accept Stream Torrent pop-up to proceed. - software_decoding_key2 Software decoding Software decoding enables the player to play video files not supported by your device, but may cause laggy or unstable playback on high resolution. Volume has exceeded 100% @@ -855,7 +708,6 @@ Update Plugins Update plugins manually - manual_update_plugins Starting plugin update process! Successfully updated %d plugin(s)! No plugins were updated. @@ -879,7 +731,6 @@ Overscan Changes size of posters Poster size - speedup_key LongPress Speed Toggle Hold to get 2x speed Edit Profile Image @@ -903,5 +754,4 @@ Top left Top center Top right - S1E1 From 2795e9e0e2856dfd4d43177eb0be4981da1270b3 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jan 2026 10:08:15 +0100 Subject: [PATCH 056/333] feat(extractors): add vidnest extractor (#2390) --- .../lagradost/cloudstream3/extractors/AsianLoad.kt | 12 +++++++----- .../com/lagradost/cloudstream3/utils/ExtractorApi.kt | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt index 256681679..70e869f55 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -4,9 +4,12 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink -import java.net.URI + +class Vidnest : AsianLoad() { + override var name = "Vidnest" + override var mainUrl = "https://vidnest.io" +} open class AsianLoad : ExtractorApi() { override var name = "AsianLoad" @@ -20,7 +23,7 @@ open class AsianLoad : ExtractorApi() { sourceRegex.findAll(this.text).forEach { sourceMatch -> val extractedUrl = sourceMatch.groupValues[1] // Trusting this isn't mp4, may fuck up stuff - if (URI(extractedUrl).path.endsWith(".m3u8")) { + if (extractedUrl.contains(".m3u8")) { M3u8Helper.generateM3u8( name, extractedUrl, @@ -29,7 +32,7 @@ open class AsianLoad : ExtractorApi() { ).forEach { link -> extractedLinksList.add(link) } - } else if (extractedUrl.endsWith(".mp4")) { + } else if (extractedUrl.contains(".mp4")) { extractedLinksList.add( newExtractorLink( source = name, @@ -37,7 +40,6 @@ open class AsianLoad : ExtractorApi() { url = extractedUrl, ) { this.referer = url.replace(" ", "%20") - this.quality = getQualityFromName(sourceMatch.groupValues[2]) } ) } 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 641c91319..b9a147fc7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -263,6 +263,7 @@ import com.lagradost.cloudstream3.extractors.Vidguardto3 import com.lagradost.cloudstream3.extractors.VidhideExtractor import com.lagradost.cloudstream3.extractors.Vidmoly import com.lagradost.cloudstream3.extractors.Vidmolyme +import com.lagradost.cloudstream3.extractors.Vidnest import com.lagradost.cloudstream3.extractors.Vido import com.lagradost.cloudstream3.extractors.Vidstreamz import com.lagradost.cloudstream3.extractors.VinovoSi @@ -1169,6 +1170,7 @@ val extractorApis: MutableList = arrayListOf( FlaswishCom(), SfastwishCom(), Playerwish(), + Vidnest(), EmturbovidExtractor(), Vtbe(), EPlayExtractor(), From dc6b9f435d988cd09c7d101eb1c043ccdb4a4e3c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jan 2026 10:09:50 +0100 Subject: [PATCH 057/333] feat(extractors): add up4stream extractor (#2389) --- .../cloudstream3/extractors/Up4Stream.kt | 60 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 4 ++ 2 files changed, 64 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt new file mode 100644 index 000000000..91150992b --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt @@ -0,0 +1,60 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.api.Log +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.JsUnpacker +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.fixUrl +import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.coroutines.delay + +class Up4FunTop : Up4Stream() { + override var mainUrl: String = "https://up4fun.top" +} + +open class Up4Stream : ExtractorApi() { + override var name = "Up4Stream" + override var mainUrl = "https://up4stream.com" + override val requiresReferer = true + + override suspend fun getUrl(url: String, referer: String?): List? { + val movieId = url.substringAfterLast("/").substringBefore(".html") + + // redirect from "wait 5 seconds" page to actual movie page + val redirectResponse = app.get(url, cookies = mapOf("id" to movieId)) + val redirectForm = redirectResponse.document.selectFirst("form[method=POST]") ?: return null + val redirectUrl = fixUrl(redirectForm.attr("action")) + val redirectParams = redirectForm.select("input[type=hidden]").associate { input -> + input.attr("name") to input.attr("value") + } + + // wait for 5 seconds, otherwise the below md5 hash is invalid + delay(5000) + val response = app.post(redirectUrl, data = redirectParams).document + + // starting here, this works similar to many other extractors like StreamWish + val extractedpack = + response.selectFirst("script:containsData(function(p,a,c,k,e,d))")?.data() + if (extractedpack == null) { + Log.e("up4stream", "file not ready: delay too short") + } + + JsUnpacker(extractedpack).unpack()?.let { unPacked -> + Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> + return listOf( + newExtractorLink( + this.name, + this.name, + link, + ) { + this.referer = referer.orEmpty() + this.quality = Qualities.Unknown.value + } + ) + } + } + return null + } +} \ 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 b9a147fc7..c0d5c5da7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -230,6 +230,8 @@ import com.lagradost.cloudstream3.extractors.Techinmind import com.lagradost.cloudstream3.extractors.Tomatomatela import com.lagradost.cloudstream3.extractors.TomatomatelalClub import com.lagradost.cloudstream3.extractors.Tubeless +import com.lagradost.cloudstream3.extractors.Up4FunTop +import com.lagradost.cloudstream3.extractors.Up4Stream import com.lagradost.cloudstream3.extractors.Upstream import com.lagradost.cloudstream3.extractors.UpstreamExtractor import com.lagradost.cloudstream3.extractors.Uqload @@ -1223,6 +1225,8 @@ val extractorApis: MutableList = arrayListOf( VkExtractor(), Bysezejataos(), ByseSX(), + Up4Stream(), + Up4FunTop() ) From 5e54552338eca91ec7b792e54dd0e5da28d17bf5 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:59:04 +0530 Subject: [PATCH 058/333] remove check icon in tvtype chips (#2363) --- app/src/main/res/values/styles.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index eb5a57c03..8cf61eaea 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -102,6 +102,7 @@ @font/google_sans @string/tv_no_focus_tag 0dp + false + + +