From 33aecfbba520be7bdf3adcb8e3a6731113a75bcf Mon Sep 17 00:00:00 2001 From: LikDev-256 <81100289+LikDev-256@users.noreply.github.com> Date: Sat, 18 Feb 2023 17:45:50 +0530 Subject: [PATCH 1/5] Fix Streamsb (#380) --- .../java/com/lagradost/cloudstream3/extractors/StreamSB.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt index 958d63fb..b77617c2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt @@ -130,7 +130,7 @@ open class StreamSB : ExtractorApi() { it.value.replace(Regex("(embed-|/e/)"), "") }.first() // val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" - val master = "$mainUrl/sources50/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" + val master = "$mainUrl/sources51/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" val headers = mapOf( "watchsb" to "sbstream", ) @@ -156,4 +156,4 @@ open class StreamSB : ExtractorApi() { ) } } -} \ No newline at end of file +} From 00a91ca5fb487d3f206d436cdd7b39bdf2b23396 Mon Sep 17 00:00:00 2001 From: no-commit <> Date: Sun, 19 Feb 2023 19:27:40 +0100 Subject: [PATCH 2/5] Added Subscriptions (pinged every ~6 hours) --- app/build.gradle.kts | 4 +- .../com/lagradost/cloudstream3/MainAPI.kt | 28 ++- .../cloudstream3/plugins/PluginManager.kt | 31 +-- .../services/SubscriptionWorkManager.kt | 218 ++++++++++++++++++ .../syncproviders/providers/LocalList.kt | 8 +- .../cloudstream3/ui/result/ResultFragment.kt | 35 ++- .../ui/result/ResultViewModel2.kt | 54 ++++- .../lagradost/cloudstream3/utils/AppUtils.kt | 18 ++ .../cloudstream3/utils/DataStoreHelper.kt | 78 ++++++- .../utils/PackageInstallerService.kt | 23 +- .../utils/VideoDownloadManager.kt | 13 +- .../baseline_notifications_none_24.xml | 5 + .../ic_cloudstream_monochrome_big.xml | 27 +++ .../main/res/layout/fragment_result_swipe.xml | 23 +- app/src/main/res/values/strings.xml | 5 + 15 files changed, 515 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt create mode 100644 app/src/main/res/drawable/baseline_notifications_none_24.xml create mode 100644 app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3c855d28..9cbccbe5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -184,8 +184,8 @@ dependencies { //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0") // Downloading - implementation("androidx.work:work-runtime:2.7.1") - implementation("androidx.work:work-runtime-ktx:2.7.1") + implementation("androidx.work:work-runtime:2.8.0") + implementation("androidx.work:work-runtime-ktx:2.8.0") // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 3958984e..c20786c3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -1327,7 +1327,7 @@ fun LoadResponse?.isAnimeBased(): Boolean { fun TvType?.isEpisodeBased(): Boolean { if (this == null) return false - return (this == TvType.TvSeries || this == TvType.Anime) + return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama) } @@ -1351,6 +1351,7 @@ interface EpisodeResponse { var showStatus: ShowStatus? var nextAiring: NextAiring? var seasonNames: List? + fun getLatestEpisodes(): Map } @JvmName("addSeasonNamesString") @@ -1419,7 +1420,18 @@ data class AnimeLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse, EpisodeResponse +) : LoadResponse, EpisodeResponse { + override fun getLatestEpisodes(): Map { + return episodes.map { (status, episodes) -> + val maxSeason = episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE } + .takeUnless { it == Int.MIN_VALUE } + status to episodes + .filter { it.season == maxSeason } + .maxOfOrNull { it.episode ?: Int.MIN_VALUE } + .takeUnless { it == Int.MIN_VALUE } + }.toMap() + } +} /** * If episodes already exist appends the list. @@ -1617,7 +1629,17 @@ data class TvSeriesLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse, EpisodeResponse +) : LoadResponse, EpisodeResponse { + override fun getLatestEpisodes(): Map { + val maxSeason = + episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }.takeUnless { it == Int.MIN_VALUE } + val max = episodes + .filter { it.season == maxSeason } + .maxOfOrNull { it.episode ?: Int.MIN_VALUE } + .takeUnless { it == Int.MIN_VALUE } + return mapOf(DubStatus.None to max) + } +} suspend fun MainAPI.newTvSeriesLoadResponse( name: String, diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 3533d6a8..0dee57eb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -16,6 +16,7 @@ import com.google.gson.Gson import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.removePluginMapping +import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey @@ -165,11 +166,11 @@ object PluginManager { private var loadedLocalPlugins = false private val gson = Gson() - private suspend fun maybeLoadPlugin(activity: Activity, file: File) { + private suspend fun maybeLoadPlugin(context: Context, file: File) { val name = file.name if (file.extension == "zip" || file.extension == "cs3") { loadPlugin( - activity, + context, file, PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET) ) @@ -199,7 +200,7 @@ object PluginManager { // var allCurrentOutDatedPlugins: Set = emptySet() - suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { + suspend fun loadSinglePlugin(context: Context, apiName: String): Boolean { return (getPluginsOnline().firstOrNull { // Most of the time the provider ends with Provider which isn't part of the api name it.internalName.replace("provider", "", ignoreCase = true) == apiName @@ -209,7 +210,7 @@ object PluginManager { })?.let { savedData -> // OnlinePluginData(savedData, onlineData) loadPlugin( - activity, + context, File(savedData.filePath), savedData ) @@ -371,11 +372,11 @@ object PluginManager { /** * Use updateAllOnlinePluginsAndLoadThem * */ - fun loadAllOnlinePlugins(activity: Activity) { + fun loadAllOnlinePlugins(context: Context) { // Load all plugins as fast as possible! (getPluginsOnline()).toList().apmap { pluginData -> loadPlugin( - activity, + context, File(pluginData.filePath), pluginData ) @@ -398,7 +399,7 @@ object PluginManager { * @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins * and reload all pages even if they are previously valid **/ - fun loadAllLocalPlugins(activity: Activity, forceReload: Boolean) { + fun loadAllLocalPlugins(context: Context, forceReload: Boolean) { val dir = File(LOCAL_PLUGINS_PATH) removeKey(PLUGINS_KEY_LOCAL) @@ -416,7 +417,7 @@ object PluginManager { Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins") sortedPlugins?.sortedBy { it.name }?.apmap { file -> - maybeLoadPlugin(activity, file) + maybeLoadPlugin(context, file) } loadedLocalPlugins = true @@ -441,14 +442,14 @@ object PluginManager { /** * @return True if successful, false if not * */ - private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean { + private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean { val fileName = file.nameWithoutExtension val filePath = file.absolutePath currentlyLoading = fileName Log.i(TAG, "Loading plugin: $data") return try { - val loader = PathClassLoader(filePath, activity.classLoader) + val loader = PathClassLoader(filePath, context.classLoader) var manifest: Plugin.Manifest loader.getResourceAsStream("manifest.json").use { stream -> if (stream == null) { @@ -492,22 +493,22 @@ object PluginManager { addAssetPath.invoke(assets, file.absolutePath) pluginInstance.resources = Resources( assets, - activity.resources.displayMetrics, - activity.resources.configuration + context.resources.displayMetrics, + context.resources.configuration ) } plugins[filePath] = pluginInstance classLoaders[loader] = pluginInstance urlPlugins[data.url ?: filePath] = pluginInstance - pluginInstance.load(activity) + pluginInstance.load(context) Log.i(TAG, "Loaded plugin ${data.internalName} successfully") currentlyLoading = null true } catch (e: Throwable) { Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") showToast( - activity, - activity.getString(R.string.plugin_load_fail).format(fileName), + context.getActivity(), + context.getString(R.string.plugin_load_fail).format(fileName), Toast.LENGTH_LONG ) currentlyLoading = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt new file mode 100644 index 00000000..d1b1b660 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt @@ -0,0 +1,218 @@ +package com.lagradost.cloudstream3.services + +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.net.toUri +import androidx.work.* +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel +import com.lagradost.cloudstream3.utils.Coroutines.ioWork +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions +import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute +import com.lagradost.cloudstream3.utils.VideoDownloadManager.getImageBitmapFromUrl +import kotlinx.coroutines.withTimeoutOrNull +import java.util.concurrent.TimeUnit + +const val SUBSCRIPTION_CHANNEL_ID = "cloudstream3.subscriptions" +const val SUBSCRIPTION_WORK_NAME = "work_subscription" +const val SUBSCRIPTION_CHANNEL_NAME = "Subscriptions" +const val SUBSCRIPTION_CHANNEL_DESCRIPTION = "Notifications for new episodes on subscribed shows" +const val SUBSCRIPTION_NOTIFICATION_ID = 938712897 // Random unique + +class SubscriptionWorkManager(val context: Context, workerParams: WorkerParameters) : + CoroutineWorker(context, workerParams) { + companion object { + fun enqueuePeriodicWork(context: Context?) { + if (context == null) return + + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val periodicSyncDataWork = + PeriodicWorkRequest.Builder(SubscriptionWorkManager::class.java, 6, TimeUnit.HOURS) + .addTag(SUBSCRIPTION_WORK_NAME) + .setConstraints(constraints) + .build() + + WorkManager.getInstance(context).enqueueUniquePeriodicWork( + SUBSCRIPTION_WORK_NAME, + ExistingPeriodicWorkPolicy.KEEP, + periodicSyncDataWork + ) + + // Uncomment below for testing + +// val oneTimeSyncDataWork = +// OneTimeWorkRequest.Builder(SubscriptionWorkManager::class.java) +// .addTag(SUBSCRIPTION_WORK_NAME) +// .setConstraints(constraints) +// .build() +// +// WorkManager.getInstance(context).enqueue(oneTimeSyncDataWork) + } + } + + private val progressNotificationBuilder = + NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID) + .setAutoCancel(false) + .setColorized(true) + .setOnlyAlertOnce(true) + .setSilent(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setColor(context.colorFromAttribute(R.attr.colorPrimary)) + .setContentTitle(context.getString(R.string.subscription_in_progress_notification)) + .setSmallIcon(R.drawable.quantum_ic_refresh_white_24) + .setProgress(0, 0, true) + + private val updateNotificationBuilder = + NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID) + .setColorized(true) + .setOnlyAlertOnce(true) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setColor(context.colorFromAttribute(R.attr.colorPrimary)) + .setSmallIcon(R.drawable.ic_cloudstream_monochrome_big) + + private val notificationManager: NotificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + private fun updateProgress(max: Int, progress: Int, indeterminate: Boolean) { + notificationManager.notify( + SUBSCRIPTION_NOTIFICATION_ID, progressNotificationBuilder + .setProgress(max, progress, indeterminate) + .build() + ) + } + + override suspend fun doWork(): Result { +// println("Update subscriptions!") + context.createNotificationChannel( + SUBSCRIPTION_CHANNEL_ID, + SUBSCRIPTION_CHANNEL_NAME, + SUBSCRIPTION_CHANNEL_DESCRIPTION + ) + + safeApiCall { + setForeground( + ForegroundInfo( + SUBSCRIPTION_NOTIFICATION_ID, + progressNotificationBuilder.build() + ) + ) + } + + val subscriptions = getAllSubscriptions() + + if (subscriptions.isEmpty()) { + WorkManager.getInstance(context).cancelWorkById(this.id) + return Result.success() + } + + val max = subscriptions.size + var progress = 0 + + updateProgress(max, progress, true) + + // We need all plugins loaded. + PluginManager.loadAllOnlinePlugins(context) + PluginManager.loadAllLocalPlugins(context, false) + + subscriptions.apmap { savedData -> + try { + val id = savedData.id ?: return@apmap null + val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null + + // Reasonable timeout to prevent having this worker run forever. + val response = withTimeoutOrNull(60_000) { + api.load(savedData.url) as? EpisodeResponse + } ?: return@apmap null + + val dubPreference = + getDub(id) ?: if ( + context.getApiDubstatusSettings().contains(DubStatus.Dubbed) + ) { + DubStatus.Dubbed + } else { + DubStatus.Subbed + } + + val latestEpisodes = response.getLatestEpisodes() + val latestPreferredEpisode = latestEpisodes[dubPreference] + + val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) { + val latestSeenEpisode = + savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE + val shouldUpdate = latestPreferredEpisode > latestSeenEpisode + shouldUpdate to latestPreferredEpisode + } else { + val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE + val latestSeenEpisode = + savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE + val shouldUpdate = latestEpisode > latestSeenEpisode + shouldUpdate to latestEpisode + } + + DataStoreHelper.updateSubscribedData( + id, + savedData, + response + ) + + if (shouldUpdate) { + val updateHeader = savedData.name + val updateDescription = txt( + R.string.subscription_episode_released, + latestEpisode, + savedData.name + ).asString(context) + + val intent = Intent(context, MainActivity::class.java).apply { + data = savedData.url.toUri() + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + + val pendingIntent = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE + ) + } else { + PendingIntent.getActivity(context, 0, intent, 0) + } + + val poster = ioWork { savedData.posterUrl?.let { url -> context.getImageBitmapFromUrl(url, savedData.posterHeaders) } } + val updateNotification = + updateNotificationBuilder.setContentTitle(updateHeader) + .setContentText(updateDescription) + .setContentIntent(pendingIntent) + .setLargeIcon(poster) + .build() + + notificationManager.notify(id, updateNotification) + } + + // You can probably get some issues here since this is async but it does not matter much. + updateProgress(max, ++progress, false) + } catch (_: Throwable) { + } + } + + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 0b081220..7dd43fe7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -9,6 +9,7 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.Coroutines.ioWork +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState @@ -74,13 +75,16 @@ class LocalList : SyncAPI { group.value.mapNotNull { getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) } - } + } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { + it.toLibraryItem() + }) } val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { // None is not something to display it.stringRes to emptyList() - } + } + mapOf(R.string.subscription_list_name to emptyList()) + return SyncAPI.LibraryMetadata( (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 68dd1c0e..bdef14b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -15,6 +15,7 @@ import android.view.ViewGroup import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.ImageView +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible @@ -27,12 +28,14 @@ import com.google.android.material.chip.ChipDrawable import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.updateHasTrailers +import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.* +import com.lagradost.cloudstream3.services.SubscriptionWorkManager import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD @@ -850,7 +853,7 @@ open class ResultFragment : ResultTrailerPlayer() { } observe(viewModel.page) { data -> - if(data == null) return@observe + if (data == null) return@observe when (data) { is Resource.Success -> { val d = data.value @@ -904,6 +907,36 @@ open class ResultFragment : ResultTrailerPlayer() { updateList(d.actors ?: emptyList()) } + observeNullable(viewModel.subscribeStatus) { isSubscribed -> + result_subscribe?.isVisible = isSubscribed != null + if (isSubscribed == null) return@observeNullable + + val drawable = if (isSubscribed) { + R.drawable.ic_baseline_notifications_active_24 + } else { + R.drawable.baseline_notifications_none_24 + } + + result_subscribe?.setImageResource(drawable) + } + + result_subscribe?.setOnClickListener { + val isSubscribed = + viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener + + val message = if (isSubscribed) { + // Kinda icky to have this here, but it works. + SubscriptionWorkManager.enqueuePeriodicWork(context) + R.string.subscription_new + } else { + R.string.subscription_deleted + } + + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + showToast(activity, txt(message, name), Toast.LENGTH_SHORT) + } + result_open_in_browser?.isVisible = d.url.startsWith("http") result_open_in_browser?.setOnClickListener { val i = Intent(ACTION_VIEW) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index afaaeef9..2983b41d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.unixTime +import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast @@ -414,6 +415,9 @@ class ResultViewModel2 : ViewModel() { private val _episodeSynopsis: MutableLiveData = MutableLiveData(null) val episodeSynopsis: LiveData = _episodeSynopsis + private val _subscribeStatus: MutableLiveData = MutableLiveData(null) + val subscribeStatus: LiveData = _subscribeStatus + companion object { const val TAG = "RVM2" private const val EPISODE_RANGE_SIZE = 20 @@ -815,6 +819,42 @@ class ResultViewModel2 : ViewModel() { } } + /** + * @return true if the new status is Subscribed, false if not. Null if not possible to subscribe. + **/ + fun toggleSubscriptionStatus(): Boolean? { + val isSubscribed = _subscribeStatus.value ?: return null + val response = currentResponse ?: return null + if (response !is EpisodeResponse) return null + + val currentId = response.getId() + + if (isSubscribed) { + DataStoreHelper.removeSubscribedData(currentId) + } else { + val current = DataStoreHelper.getSubscribedData(currentId) + + DataStoreHelper.setSubscribedData( + currentId, + DataStoreHelper.SubscribedData( + currentId, + current?.bookmarkedTime ?: unixTimeMS, + unixTimeMS, + response.getLatestEpisodes(), + response.name, + response.url, + response.apiName, + response.type, + response.posterUrl, + response.year + ) + ) + } + + _subscribeStatus.postValue(!isSubscribed) + return !isSubscribed + } + private fun startChromecast( activity: Activity?, result: ResultEpisode, @@ -1473,7 +1513,8 @@ class ResultViewModel2 : ViewModel() { this.engName, this.name, this.japName - ).filter { it.length > 2 }.distinct(), // the reason why we filter is due to not wanting smth like " " or "?" + ).filter { it.length > 2 } + .distinct(), // the reason why we filter is due to not wanting smth like " " or "?" TrackerType.getTypes(this.type), this.year ) @@ -1670,6 +1711,16 @@ class ResultViewModel2 : ViewModel() { postResume() } + private fun postSubscription(loadResponse: LoadResponse) { + if (loadResponse.isEpisodeBased()) { + val id = loadResponse.getId() + val data = DataStoreHelper.getSubscribedData(id) + DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse) + val isSubscribed = data != null + _subscribeStatus.postValue(isSubscribed) + } + } + private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { if (range == null || indexer == null) { return @@ -1806,6 +1857,7 @@ class ResultViewModel2 : ViewModel() { ) { currentResponse = loadResponse postPage(loadResponse, apiRepository) + postSubscription(loadResponse) if (updateEpisodes) postEpisodes(loadResponse, updateFillers) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index 4b1053b1..860144ee 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -4,6 +4,8 @@ import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.app.Activity import android.app.Activity.RESULT_CANCELED +import android.app.NotificationChannel +import android.app.NotificationManager import android.content.* import android.content.pm.PackageManager import android.database.Cursor @@ -196,6 +198,22 @@ object AppUtils { animation.start() } + fun Context.createNotificationChannel(channelId: String, channelName: String, description: String) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = + NotificationChannel(channelId, channelName, importance).apply { + this.description = description + } + + // Register the channel with the system. + val notificationManager: NotificationManager = + this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + notificationManager.createNotificationChannel(channel) + } + } + @SuppressLint("RestrictedApi") fun getAllWatchNextPrograms(context: Context): Set { val COLUMN_WATCH_NEXT_ID_INDEX = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 281c9c44..516cd990 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,16 +1,13 @@ package com.lagradost.cloudstream3.utils import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.APIHolder.capitalize +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.DubStatus -import com.lagradost.cloudstream3.SearchQuality -import com.lagradost.cloudstream3.SearchResponse -import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.WatchType @@ -20,6 +17,7 @@ const val VIDEO_POS_DUR = "video_pos_dur" const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" +const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" @@ -42,6 +40,37 @@ object DataStoreHelper { return this } + /** + * Used to display notifications on new episodes and posters in library. + **/ + data class SubscribedData( + @JsonProperty("id") override var id: Int?, + @JsonProperty("subscribedTime") val bookmarkedTime: Long, + @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, + @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, + @JsonProperty("name") override val name: String, + @JsonProperty("url") override val url: String, + @JsonProperty("apiName") override val apiName: String, + @JsonProperty("type") override var type: TvType? = null, + @JsonProperty("posterUrl") override var posterUrl: String?, + @JsonProperty("year") val year: Int?, + @JsonProperty("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, + ) : SearchResponse { + fun toLibraryItem(): SyncAPI.LibraryItem? { + return SyncAPI.LibraryItem( + name, + url, + id?.toString() ?: return null, + null, + null, + null, + latestUpdatedTime, + apiName, type, posterUrl, posterHeaders, quality, this.id + ) + } + } + data class BookmarkedData( @JsonProperty("id") override var id: Int?, @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, @@ -63,7 +92,7 @@ object DataStoreHelper { null, null, null, - null, + latestUpdatedTime, apiName, type, posterUrl, posterHeaders, quality, this.id ) } @@ -75,9 +104,7 @@ object DataStoreHelper { @JsonProperty("apiName") override val apiName: String, @JsonProperty("type") override var type: TvType? = null, @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("watchPos") val watchPos: PosDur?, - @JsonProperty("id") override var id: Int?, @JsonProperty("parentId") val parentId: Int?, @JsonProperty("episode") val episode: Int?, @@ -204,6 +231,41 @@ object DataStoreHelper { return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) } + fun getAllSubscriptions(): List { + return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull { + getKey(it) + } ?: emptyList() + } + + fun removeSubscribedData(id: Int?) { + if (id == null) return + AccountManager.localListApi.requireLibraryRefresh = true + removeKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) + } + + /** + * Set new seen episodes and update time + **/ + fun updateSubscribedData(id: Int?, data: SubscribedData?, episodeResponse: EpisodeResponse?) { + if (id == null || data == null || episodeResponse == null) return + val newData = data.copy( + latestUpdatedTime = unixTimeMS, + lastSeenEpisodeCount = episodeResponse.getLatestEpisodes() + ) + setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), newData) + } + + fun setSubscribedData(id: Int?, data: SubscribedData) { + if (id == null) return + setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), data) + AccountManager.localListApi.requireLibraryRefresh = true + } + + fun getSubscribedData(id: Int?): SubscribedData? { + if (id == null) return null + return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) + } + fun setViewPos(id: Int?, pos: Long, dur: Long) { if (id == null) return if (dur < 30_000) return // too short diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt index 1625981e..dcb1e047 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt @@ -14,6 +14,7 @@ import androidx.core.app.NotificationCompat import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import kotlinx.coroutines.delay @@ -47,24 +48,12 @@ class PackageInstallerService : Service() { .setSmallIcon(R.drawable.rdload) } - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val importance = NotificationManager.IMPORTANCE_DEFAULT - val channel = - NotificationChannel(UPDATE_CHANNEL_ID, UPDATE_CHANNEL_NAME, importance).apply { - description = UPDATE_CHANNEL_DESCRIPTION - } - - // Register the channel with the system - val notificationManager: NotificationManager = - this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - notificationManager.createNotificationChannel(channel) - } - } - override fun onCreate() { - createNotificationChannel() + this.createNotificationChannel( + UPDATE_CHANNEL_ID, + UPDATE_CHANNEL_NAME, + UPDATE_CHANNEL_DESCRIPTION + ) startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build()) } 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 a629dad9..2902b76b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -20,6 +20,7 @@ import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager +import com.bumptech.glide.load.model.GlideUrl import com.fasterxml.jackson.annotation.JsonProperty import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull @@ -213,7 +214,7 @@ object VideoDownloadManager { } private val cachedBitmaps = hashMapOf() - private fun Context.getImageBitmapFromUrl(url: String): Bitmap? { + fun Context.getImageBitmapFromUrl(url: String, headers: Map? = null): Bitmap? { try { if (cachedBitmaps.containsKey(url)) { return cachedBitmaps[url] @@ -221,12 +222,14 @@ object VideoDownloadManager { val bitmap = GlideApp.with(this) .asBitmap() - .load(url).into(720, 720) + .load(GlideUrl(url) { headers ?: emptyMap() }) + .into(720, 720) .get() + if (bitmap != null) { cachedBitmaps[url] = bitmap } - return null + return bitmap } catch (e: Exception) { logError(e) return null @@ -426,7 +429,7 @@ object VideoDownloadManager { } private const val reservedChars = "|\\?*<\":>+[]/\'" - fun sanitizeFilename(name: String, removeSpaces: Boolean= false): String { + fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String { var tempName = name for (c in reservedChars) { tempName = tempName.replace(c, ' ') @@ -1612,7 +1615,7 @@ object VideoDownloadManager { .mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } .toTypedArray() setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) - } catch (t : Throwable) { + } catch (t: Throwable) { logError(t) } } diff --git a/app/src/main/res/drawable/baseline_notifications_none_24.xml b/app/src/main/res/drawable/baseline_notifications_none_24.xml new file mode 100644 index 00000000..cf589c6d --- /dev/null +++ b/app/src/main/res/drawable/baseline_notifications_none_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml b/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml new file mode 100644 index 00000000..4b8964f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result_swipe.xml b/app/src/main/res/layout/fragment_result_swipe.xml index 9d21547a..27729bf8 100644 --- a/app/src/main/res/layout/fragment_result_swipe.xml +++ b/app/src/main/res/layout/fragment_result_swipe.xml @@ -57,6 +57,7 @@ android:layout_width="match_parent" android:layout_height="50dp" android:id="@+id/media_route_button_holder" + android:animateLayoutChanges="true" android:layout_gravity="center_vertical|end"> + + Looks like your library is empty :(\nLogin to a library account or add shows to your local library Looks like this list is empty, try switching to another one Safe mode file found!\nNot loading any extensions on startup until file is removed. + Updating subscribed shows + Subscribed + Subscribed to %s + Unsubscribed from %s + Episode %d released! \ No newline at end of file From 5f12d067f92fbbe70e2d5dc2ae54de4483988328 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 21 Feb 2023 18:28:21 +0100 Subject: [PATCH 3/5] Translated using Weblate (Indonesian) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Slovak) Currently translated at 31.7% (191 of 602 strings) Translated using Weblate (Czech) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (602 of 602 strings) Translated using Weblate (German) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Japanese) Currently translated at 44.8% (268 of 597 strings) Translated using Weblate (Italian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Japanese) Currently translated at 40.5% (242 of 597 strings) Translated using Weblate (Japanese) Currently translated at 34.5% (206 of 597 strings) Translated using Weblate (Japanese) Currently translated at 33.1% (198 of 597 strings) Translated using Weblate (Kannada) Currently translated at 35.5% (212 of 597 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Czech) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Japanese) Currently translated at 25.7% (154 of 597 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (German) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Russian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Russian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Romanian) Currently translated at 73.7% (440 of 597 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (597 of 597 strings) Translated using Weblate (Japanese) Currently translated at 22.7% (134 of 589 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (589 of 589 strings) Translated using Weblate (Italian) Currently translated at 100.0% (589 of 589 strings) Translated using Weblate (Japanese) Currently translated at 17.8% (105 of 589 strings) Translated using Weblate (Ukrainian) Currently translated at 99.3% (585 of 589 strings) Translated using Weblate (Japanese) Currently translated at 16.6% (98 of 589 strings) Translated using Weblate (Russian) Currently translated at 99.6% (587 of 589 strings) Translated using Weblate (Russian) Currently translated at 99.4% (586 of 589 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 96.0% (566 of 589 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (589 of 589 strings) Translated using Weblate (German) Currently translated at 100.0% (589 of 589 strings) Translated using Weblate (French) Currently translated at 99.3% (585 of 589 strings) Added translation using Weblate (Japanese) Translated using Weblate (Croatian) Currently translated at 100.0% (589 of 589 strings) Translated using Weblate (Russian) Currently translated at 98.9% (583 of 589 strings) Translated using Weblate (French) Currently translated at 98.6% (581 of 589 strings) Translated using Weblate (French) Currently translated at 96.6% (569 of 589 strings) Translated using Weblate (Greek) Currently translated at 98.9% (583 of 589 strings) Translated using Weblate (Polish) Currently translated at 100.0% (589 of 589 strings) Translated using Weblate (Polish) Currently translated at 100.0% (589 of 589 strings) jsdelivr wrapper to githubusercontent Co-authored-by: Anarchydr Co-authored-by: Andrey Zapolsky Co-authored-by: Cliff Heraldo <123844876+clxf12@users.noreply.github.com> Co-authored-by: Cliff Heraldo Co-authored-by: Cliff Heraldo Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com> Co-authored-by: Dan Co-authored-by: Deleted User Co-authored-by: Fjuro Co-authored-by: Gabriel Cnudde Co-authored-by: Hosted Weblate Co-authored-by: Julian Co-authored-by: Juraj Liso Co-authored-by: Massimo Pissarello Co-authored-by: MedRAM Co-authored-by: Piotr Strebski Co-authored-by: Prathap Rathod Co-authored-by: Rex_sa Co-authored-by: Sdarfeesh Co-authored-by: Skrripy Co-authored-by: Translator-3000 Co-authored-by: abcabcc Co-authored-by: eightyy8 Co-authored-by: gallegonovato Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ar/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/de/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/el/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/hr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/id/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/it/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ja/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/kn/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ro/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ru/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hant/ Translation: Cloudstream/App --- .../cloudstream3/plugins/RepositoryManager.kt | 13 +- app/src/main/res/values-ar/strings.xml | 22 ++- app/src/main/res/values-cs/strings.xml | 12 ++ app/src/main/res/values-de/strings.xml | 22 ++- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 12 ++ app/src/main/res/values-fr/strings.xml | 32 ++- app/src/main/res/values-hr/strings.xml | 17 ++ app/src/main/res/values-in/strings.xml | 27 ++- app/src/main/res/values-it/strings.xml | 12 ++ app/src/main/res/values-ja/strings.xml | 185 ++++++++++++++++++ app/src/main/res/values-kn/strings.xml | 127 +++++++++++- app/src/main/res/values-pl/strings.xml | 7 +- app/src/main/res/values-ro/strings.xml | 10 +- app/src/main/res/values-ru/strings.xml | 12 ++ app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 16 ++ app/src/main/res/values-zh-rTW/strings.xml | 3 +- app/src/main/res/values-zh/strings.xml | 17 ++ 19 files changed, 517 insertions(+), 33 deletions(-) create mode 100644 app/src/main/res/values-ja/strings.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index e77b2d54..a5c2d346 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -71,6 +71,13 @@ object RepositoryManager { val PREBUILT_REPOSITORIES: Array by lazy { getKey("PREBUILT_REPOSITORIES") ?: emptyArray() } + val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$") + + fun convertRawGitUrl(url: String): String { + val match = GH_REGEX.find(url) ?: return url + val (user, repo, rest) = match.destructured + return "https://cdn.jsdelivr.net/gh/$user/$repo@$rest" + } suspend fun parseRepoUrl(url: String): String? { val fixedUrl = url.trim() @@ -97,14 +104,14 @@ object RepositoryManager { suspend fun parseRepository(url: String): Repository? { return suspendSafeApiCall { // Take manifestVersion and such into account later - app.get(url).parsedSafe() + app.get(convertRawGitUrl(url)).parsedSafe() } } private suspend fun parsePlugins(pluginUrls: String): List { // Take manifestVersion and such into account later return try { - val response = app.get(pluginUrls) + val response = app.get(convertRawGitUrl(pluginUrls)) // Normal parsed function not working? // return response.parsedSafe() tryParseJson>(response.text)?.toList() ?: emptyList() @@ -139,7 +146,7 @@ object RepositoryManager { } file.createNewFile() - val body = app.get(pluginUrl).okhttpResponse.body + val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body write(body.byteStream(), file.outputStream()) file } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 652df937..1e9bcfcc 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -103,7 +103,7 @@ مزيد من المعلومات قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة - لا يتم توفير البيانات الوصفية بواسطة الموقع ، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع. + لا يتم توفير البيانات الوصفية بواسطة الموقع، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع. الوصف لم يتم العثور على وصف لم يتم العثور على وصف @@ -170,7 +170,7 @@ تم نسخ الرابط إلى الحافظة تشغيل الحلقة إعادة التعيين إلى القيمة الافتراضية - عذرا ، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين + عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين موسم لا موسم حلقة @@ -266,8 +266,8 @@ طول التخزين المؤقت التخزين المؤقت للفيديو على القرص مسح التخزين المؤقت للصورة والفيديو - يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة ، مثل Android TV. - يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة ، مثل Android TV. + يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة، مثل تلفزيون أندرويد. + يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة، مثل تلفزيون أندرويد. إستخدام DNS بدلا من HTTPS مفيد لتجاوز حجب مزود خدمة الإنترنت موقع بديل (نسخة) @@ -360,7 +360,7 @@ https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog --> نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق - مُوصي به + مُوصى به تم تحميل %s إختيار ملف تحميل من الانترنت @@ -543,4 +543,16 @@ تلفزيون أندرويد مدة التقديم عنما يكون المشغل مرئيا مدة التقديم- المشغل المرئي + فشل + نجح + إختبار المزود + إعادة التشغيل + سجل + بَدأ + إيقاف + تحديث العروض التي تم الاشتراك فيها + إلغاء الاشتراك من %s + تم إصدار الحلقة %d! + مشترك + مشترك في %s \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index a78da8a4..966cd7d9 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -535,4 +535,16 @@ Zobrazený přehrávač - doba hledání Android TV Množství vyhledávané doby při zobrazeném přehrávači + Protokol + Test poskytovatele + Neúspěšné + Úspěšné + Restart + Spustit + Zastavit + Aktualizace odebíraných pořadů + Přihlášeno k odběru %s + Odhlášen odběr od %s + Byla vydána epizoda %d! + Odebíráno \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e190aa9c..f6583c20 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -53,7 +53,7 @@ Abgebrochen Geplant Nichts - Erneut anschauen + Erneut schauen Film abspielen Livestream abspielen Torrent streamen @@ -212,7 +212,7 @@ Keine Untertitel Standard Frei - Benutzt + Belegt App Filme TV-Serien @@ -284,7 +284,7 @@ Strecken Vergrößern Haftungsausschluss - General + Allgemein Zufalls-Button Zufallsbutton auf der Startseite anzeigen Anbieter-Sprachen @@ -460,11 +460,11 @@ Automatische Installation aller noch nicht installierten Plugins aus hinzugefügten Repositories. Einrichtungsvorgang wiederholen APK-Installer - Einige Telefone unterstützen das neue Installationsprogramm für Pakete nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen. + Einige Telefone unterstützen den neuen Package-Installer nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen. %s %d%s Links App-Updates - Back-Up + Sicherung Erweiterungen Wartung Cache @@ -506,4 +506,16 @@ Diese Liste scheint leer zu sein. Versuche, zu einer anderen Liste zu wechseln. Datei für abgesicherten Modus gefunden! \nBeim Start werden keine Erweiterungen geladen, bis die Datei entfernt wird. + Player ausgeblendet - Betrag zum vor- und zurückspulen + Der Betrag, welcher verwendet wird, wenn der Player eingeblendet ist + Der Betrag, welcher verwendet wird, wenn der Player ausgeblendet ist + Android-TV + Player eingeblendet - Betrag zum vor- und zurückspulen + Fehlgeschlagen + Erfolgreich + Anbieter-Test + Stopp + Log + Start + Neustarten \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 0d0b7fb2..5e9dafd8 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -150,7 +150,7 @@ Επεισόδια %d-%d %d %s - Κ + Σ E Δεν βρέθηκαν επεισόδια Διαγραφή αρχείου diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 8366b294..2040169b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -511,4 +511,16 @@ Android TV La cantidad de búsqueda utilizada cuando la jugadora es visible La cantidad de búsqueda utilizada cuando el jugador está oculto + Parar + Falló + Registro + Empezar + Aprobado + Prueba del proveedor + Reiniciar + Suscrito + Suscrito a %s + Darse de baja de %s + Actualizando los programas suscritos + ¡Episodio %d publicado! \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f0e112a8..18255b3b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,12 +6,12 @@ Téléchargements Paramètres Rechercher… - Miniature + Affiche Aucune Donnée Plus d\'options Retour Épisode suivant - Miniature + Affiche Genres Partager Ouvrir dans le navigateur @@ -29,7 +29,7 @@ Sous-titres Réessayer la connection… Retour - Miniature de l\'Épisode + Affiche de l\'épisode Lire l\'Épisode Télécharger @@ -51,10 +51,10 @@ Désactiver le rapport de bug automatique Plus d\'informations Cacher - Poster principal + Affiche principale Lecture - Info - Suivant Aléatoire + Infos + Aléatoire suivant Changer le fournisseur Filtrer les marques-pages Marque-pages @@ -211,7 +211,7 @@ Arrière plan Source Aléatoire - À venir … + Bientôt disponible… Image de l\'affiche %s Connecté Définir le statut de visionage @@ -490,4 +490,22 @@ L\'application sera mise à jour dès la fin de la session Plugin Téléchargé Retirer de la vue + Bibliothèque + Navigateur + Trier + Note (basse à haute) + Note (haut à bas) + Alphabétique (A à Z) + On dirait que votre bibliothèque est vide :( +\nConnectez-vous à un compte ou ajoutez des séries à votre bibliothèque locale + Il semble que cette liste soit vide, essayez d\'en choisir une autre + Android TV + Trié par + Alphabétique (Z à A) + Sélectionnez la bibliothèque + Ouvrir avec + Mis à jour (Nouveau vers ancien) + Mis à jour (ancien vers nouveau) + Fichier du mode sans échec trouvé ! +\nAucune extension ne sera chargée au démarrage avant que le fichier ne soit enlevé. \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 0f3e36bc..926c7f57 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -531,4 +531,21 @@ Čini se da je ova lista prazna, pokušajte se prebaciti na drugu Pronađena datoteka sigurnog načina rada! \nNe učitavaju se ekstenzije pri pokretanju dok se datoteka ne ukloni. + Prikazan player- iznos preskakanja + Količina preskakanja koja se koristi kada je player vidljiv + Player skriven - Količina preskakanja + Količina preskakanja koja se koristi kada je player skriven + Android TV + Prošlo + Restart + Log + Početak + Neuspješno + Stop + Test pružatelja usluga + Ažuriram pretplaćene serije + Epizoda %d izbačena! + Pretplaćeno + Pretplaćen na %s + Otkazana pretplata sa %s \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index d54e4fa9..46d61e44 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -35,7 +35,7 @@ Skip Loading Loading… Sedang Menonton - Tertahan + Tertunda Selesai Dihentikan Rencana untuk Menonton @@ -387,7 +387,7 @@ %d %s 17+ Lainnya - Vidio + Video Duplikasi Website Duplikasi website yang telah ada, dengan alamat berbeda Tautan @@ -395,7 +395,7 @@ Cadangkan Fitur Tambahan Putar di CloudStream - Sembunyikan kualitas vidio terpilih di pencarian + Sembunyikan kualitas video terpilih di pencarian %s %d%s Siaran langsung Hapus Website @@ -444,7 +444,7 @@ Peringkat: %s Pembuat Bahasa - Pemutar vidio utama + Pemutar video utama Pemutar Bawaan VLC MPV @@ -475,7 +475,7 @@ Hapus teks tertutup dari subtitel Hapus karakter sampah dari subtitel Audio Trek - Vidio Trek + Video Trek Dukungan Daftar putar HLS Penginstal APK @@ -529,4 +529,21 @@ Yahh daftar ini kosong, coba ganti ke yang lain Mode aman file ditemukan! \nTidak memuat ekstensi pada startup sampai berkas dihapus. + Sembunyikan Pemutaran - Geser + Pemutar terlihat - Geser + Geser untuk menghilangkan + Geser untuk menghilangkan + Android TV + Log + Berhasil + Tes provider + Berhenti + Mulai + Mulai lagi + Gagal + Memperbarui acara langganan + Berlangganan + Berlangganan ke %s + Berhenti berlangganan di %s + Episode %d telah rilis! \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9dbc627f..89f6b4ee 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -528,4 +528,16 @@ Sembra che questa lista sia vuota, prova a passare a un\'altra File \"safe mode\" trovato! \nAll\'avvio non sarà caricata alcuna estensione finchè il file non verrà rimosso. + Quantità di ricerca usata quando il player è nascosto + TV Android + Quantità di ricerca usata quando il player è visibile + Player visibile - Quantità di ricerca + Player nascosto - Quantità di ricerca + Registro + Avvia + Test del provider + Riavvia + Ferma + Superato + Fallito \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..a3d1d434 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,185 @@ + + + %d分 + ダウンロード + 検索 + 設定 + シェア + 映画 + ホーム + ライブラリ + 再生 + %d日 %d時間%d分 + %d時間%d分 + 検索… + ダウンロード + 情報 + シーズン + 予告編 + シリーズ + エピソード + 再生速度 (%.2fx) + 次のエピソード + 適用 + アカウント + カートゥーン + TVシリーズ + トレント + ドキュメンタリー + OVA + アジアドラマ + ライブ配信 + 映画 + その他 + カートゥーン + トレント + ドキュメンタリー + アジアドラマ + ライブ配信 + NSFW + キャンセル + アニメ + ロック + ソース + NSFW + 履歴を削除 + 視聴中コンテンツ + 全般 + 動画 + プレーヤー + 懐う + 予告編を再生 + エピソード + 視聴 + ジャンル + 映画を再生 + 字幕 + CloudStream + CloudStreamで再生 + ブラウザ + 完成 + 放置 + 保留 + ローディング… + ブラウザで開く + シーズン + 残り +\n%d分 + 再生エピソード + ダウンロード済 + バックアップ + ソース + 履歴 + ポスター + なし + コピー + 閉じる + 保存 + 消去 + %sエピ%d + 出演者:%s + ポスター + エピソードポスター + 主要ポスター + 次のランダム + 戻り + 視聴率 %.1f + 新しいアップデートを発見! +\n%s -> %s + %d分 + %sを検索… + ソース + ろくごうきじ + 接続を再試行… + 戻り + 削除 + 詳細情報 + 閉じる + アップデート・バックアップ + アプリ言語 + GitHub(ギットハブ) + -30 + +30 + 免責 + 拡張機能 + アプリ更新 + 提供者 + 字幕 + 特徴 + デフォルト + 自動 + 任意 + 拡張機能 + リンク + Android TV + ログイン + ログアウト + 最大 + 最小 + なし + + 18+ + + で開く + エピソード + 時間 + 概要 + サイト + 使用 + アプリ + 詳細情報 + 削除 + ピクチャーインピクチャー + 字幕 + 情報 + 一時停止 + 再生エピソード + 削除 + 開始 + 状態 + + 再開 + 失敗 + 合格 + 空き + 完成 + 進行中 + デフォルト + ウェブブラウザ + VLC + MPV + 言語 + 作成者 + サイズ + 状態 + バージョン + 視聴率 %s + 視聴率 + デフォルト + ダウンロード失敗 + ダウンロード開始 + ダウンロード完了 + ダウンロード終了 + ストリーム + アップデート開始 + シーズンなし + 字幕なし + アスペクト比 + ロードをスキップする + その他のオプション + データなし + ダウンロード中 + ブックマーク + 内部記憶装置 + ダウンロードが一時停止 + メタデータはこのサイトでは提供されません。メタデータがサイト上に存在しない場合、ビデオの読み込みに失敗します。 + 記述 + Logcat 🐈を表示 + ログ + 検索 + Discordに参加 + アップデート + アップデートを確認 + 作品名 + アプリのアップデートをインストール中… + \ No newline at end of file diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index efe0a1d8..c36459b7 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -1,3 +1,128 @@ - \ No newline at end of file + %sಎಪಿ%d + ಕ್ಯಾಸ್ಟ್:%s + ಹಿಂದೆ ಹೋಗು + ಫಿಲ್ಲರ್ + ಹುಡುಕು + ಡೌನ್ಲೋಡ್ + ಫಾಂಟ್ + ಪೂರೈಕೆದಾರರನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ + ಪ್ರಕಾರಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ + ಯಾವುದೇ ಬೆನೆನ್ಸ್ ನೀಡಿಲ್ಲ + ಸ್ವಯಂ-ಆಯ್ಕೆ ಭಾಷೆ + ಹೆಚ್ಚಿನ ಮಾಹಿತಿ + \@ಸ್ಟ್ರಿಂಗ್/ಹೋಮ್_ಪ್ಲೇ + ಈ ಪೂರೈಕೆದಾರರು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡಲು VPN ಬೇಕಾಗಬಹುದು + ಕಪ್ಪು ಗಡಿಗಳನ್ನು ತೆಗೆದುಹಾಕಿ + ಸಂಚಿಕೆ%d ಬಿಡುಗಡೆಯಾಗಲಿದೆ + %dh %dm + ಪೋಸ್ಟರ್ + ಪೋಸ್ಟರ್ + ಸಂಚಿಕೆ ಪೋಸ್ಟರ್ + ಮೇನ್ ಪೋಸ್ಟರ್ + ಅಪ್ಡೇಟ್ ಪ್ರಾರಂಭವಾಗಿದೆ + ಲೋಡಿಂಗ್ ಲಿಂಕ್ ಎರರ್ ಬಂದಿದೆ + ಇಂಟರ್ನಲ್ ಸ್ಟೋರೇಜ್ + ಡಬ್ + ಸಬ್ + ಸ್ವಯಂಚಾಲಿತ ದೋಷ ವರದಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ + ಹೈಡ್ + ಪ್ಲೇ + ಮಾಹಿತಿ + ಸೆಟ್ ವಾಚ್ ಸ್ಟೇಟಸ್ + ಅನ್ವಯಿಸು + ರದ್ದುಮಾಡು + ಸಬ್ ಟೈಟಲ್ಸ್ ಎಲೆವಷನ್ + ಫಾಂಟ್ ಸೈಜ್ + ಸಬ್ ಟೈಟಲ್ಸ್ ಭಾಷೆ + ತೆಗೆದುಹಾಕಿ + ಈ ಪೂರೈಕೆದಾರರು ಟೊರೆಂಟ್ ಆಗಿದೆ, VPN ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ + ಯಾವುದೇ ಪ್ಲಾಟ್ ಕಂಡುಬಂದಿಲ್ಲ + ಲಾಗ್‌ಕ್ಯಾಟ್ 🐈 ತೋರಿಸಿ + ಲಾಗ್ + ಚಿತ್ರದಲ್ಲಿ-ಚಿತ್ರದಲ್ಲಿ + ಪ್ಲೇಯರ್ ಮರುಗಾತ್ರಗೊಳಿಸಿ ಬಟನ್ + ಸಬ್ ಟೈಟಲ್ಸ್ + ಪ್ಲೇಯರ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು + ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್ + ಹಿಂದೆ ಹೋಗು + ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಿ + ಬುಕ್‌ಮಾರ್ಕ್‌ + ಬ್ಯಾಕ್ ಗ್ರೌಂಡ್ ಕಲರ್ + %d ಡೇವ್‌ಗಳಿಗೆ ಬೆನೆನೆಸ್ ನೀಡಲಾಗಿದೆ + ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸಲು ಹಿಡಿದುಕೊಳ್ಳಿ + ಸೈಟ್‌ನಿಂದ ಮೆಟಾಡೇಟಾವನ್ನು ಒದಗಿಸಲಾಗಿಲ್ಲ, ಅದು ಸೈಟ್‌ನಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲದಿದ್ದರೆ ವೀಡಿಯೊ ಲೋಡಿಂಗ್ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ. + ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಮೇಲೆ ಚಿಕಣಿ ಪ್ಲೇಯರ್‌ನಲ್ಲಿ ಪ್ಲೇಬ್ಯಾಕ್ ಅನ್ನು ಮುಂದುವರಿಸುತ್ತದೆ + ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ + ರೇಟೆಡ್:%.1f + ತೆಗೆದುಹಾಕಿ + ಡೌನ್‌ಲೋಡ್ ಅನ್ನು ಪುನರಾರಂಭಿಸಿ + ಕ್ಲೋಸ್ + ಕ್ಲಿಯರ್ + ಸೇವ್ + ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್ + ಫೈಲ್ ಪ್ಲೇ + ಟೆಕ್ಸ್ಟ್ ಕಲರ್ + ಔಟ್ ಲೈನ್ ಕಲರ್ + ವಿಂಡೋ ಕಲರ್ + ಎಡ್ಜ್ ಟೈಪ್ + ಪ್ರೊವೈಡರ್ ಬದಲಾಯಿಸಿ + %dಮಿನ + ವಿವರಣೆ + ಸ್ಪೀಡ್(%.2fx) + ಹೋಂ + ಸಬ್ ಟೈಟಲ್ಸ್ + ಸೆಟ್ಟಿಂಗ್ಸ್ + ಬುಕ್‌ಮಾರ್ಕ್‌ಗಳನ್ನು ಫಿಲ್ಟರ್ ಮಾಡಿ + ಹುಡುಕು… + ಚಲನಚಿತ್ರವನ್ನು ಪ್ಲೇ ಮಾಡಿ + ಪ್ರಿವ್ಯೂ ಹಿನ್ನೆಲೆ + ಮುಂದಿನ ಸಂಚಿಕೆ + ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ + ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ + ಸ್ಟ್ರೀಮ್ + ಶೇರ್ + ಫೈಲ್ ಅಳಿಸಿ + ಹೆಚ್ಚಿನ ಮಾಹಿತಿ + ಹೊಸ ಅಪ್ಡೇಟ್ ಬಂದಿದೆ +\n%s-%s + ಲೋಡಿಂಗ್… + ಡೌನ್‌ಲೋಡ್ ಭಾಷೆಗಳನ್ನು ಮಾಡಿ + ಲೈವ್‌ಸ್ಟ್ರೀಮ್ ಪ್ಲೇ ಮಾಡಿ + ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ ಇದರೊಂದಿಗೆ ಪ್ಲೇ ಮಾಡಿ + ವೀಕ್ಷಿಸಲು ಯೋಜನೆ + ಸಂಚಿಕೆಯನ್ನು ಪ್ಲೇ ಮಾಡಿ + ಕಂಟಿನ್ಯೂ ವಾಟಚಿಂಗ್ + ಯಾವುದೇ ವಿವರಣೆ ಕಂಡುಬಂದಿಲ್ಲ + ಸ್ಟ್ರೀಮ್ ಟೊರೆಂಟ್ + ಡೌನ್‌ಲೋಡ್ + ಕಾಪಿ + ನೋ ಡೇಟಾ + ಪ್ಲೇಯರ್ ಸ್ಪೀಡ್ + %d %dh %dm + ಹುಡುಕು %s… + ಹೆಚ್ಚಿನ ಆಯ್ಕೆ + ಫಾಂಟ್‌ಗಳನ್ನು ಇರಿಸುವ ಮೂಲಕ ಆಮದು ಮಾಡಿ %s + %dm + ಪ್ರಕಾರಗಳು + ಬ್ರೌಸರ್ ತೆರೆಯಿರಿ + ಆನ್-ಹೋಲ್ಡ್ + ನನ್ + ಸಂಪರ್ಕವನ್ನು ಮರುಪ್ರಯತ್ನಿಸಿ… + ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ + ಡೌನ್‌ಲೋಡ್ ವಿಫಲವಾಗಿದೆ + ಡೌನ್‌ಲೋಡ್ ಮುಗಿದಿದೆ + ಬ್ರೌಸರ್ + ಸ್ಕಿಪ್ ಲೋಡಿಂಗ್ + ವಾಚಿಂಗ್ + ಪೂರ್ಣಗೊಂಡಿದೆ + ಕೈಬಿಡಲಾಯಿತು + ಪುನಃ ವೀಕ್ಷಿಸುತ್ತಿದೆ + ಟ್ರೈಲರ್ ಪ್ಲೇ ಮಾಡಿ + ಮೂಲಗಳು + ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ + ಡೌನ್‌ಲೋಡ್ ಪ್ರಾರಂಭವಾಗಿದೆ + ಡೌನ್‌ಲೋಡ್ ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ + ಮುಂದಿನ ರಾಂಡಮ್ + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c709f124..411f0b45 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -373,7 +373,7 @@ Pomiń setup Dostosuj wygląd aplikacji do urządzenia Zgłaszanie błędów - Co chciałbyś obejrzeć\? + Co chciałbyś obejrzeć Gotowe Rozszerzenia Dodaj repozytorium @@ -509,4 +509,9 @@ Wygląda na to, że ta lista jest pusta, spróbuj przełączyć się na inną Znaleziono plik trybu bezpiecznego. \nRozszerzenia nie zostaną wczytane, dopóki plik nie zostanie usunięty. + Używana ilość przewijania, gdy widoczny jest odtwarzacz + Ukryty odtwarzacz - ilość przewijania + Android TV + Pokazany odtwarzacz — ilość przewijania + Używana ilość przewijania, gdy ukryty jest odtwarzacz \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 982546bc..42d9b7c8 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -9,7 +9,7 @@ %dm Poster - \@string/result_poster_img_des + Poster Poster Episod Poster Principal Următorul la Întâmplare @@ -142,7 +142,7 @@ Fișier de rezervă încărcat Imposibilitatea de a restaura datele din %s Date stocate - Permisiuni de arhivare lipsă, vă rugăm să încercați din nou + Permisiunea de arhivare lipșe, vă rugăm să încercați din nou. Eroare de backup %s Căutare Conturi și credite @@ -154,7 +154,7 @@ Nu trimiteți niciun fel de date Afișează etichetele [filler] pentru anime Arată trailerul - Arată posterele de la Kitsu + Arată afișele de la Kitsu Afișați actualizările aplicației Căutați automat noi actualizări la pornire Actualizați la prerelease @@ -384,4 +384,8 @@ Începe următorul episod când se termină episodul curent Ascundeți calitatea video selectată în rezultatele căutării Redare Livestream + Librărie + Log + Browser + Joacă cu CloudStream \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6e9fb394..2812667a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -506,4 +506,16 @@ Плеер показан - Перемотки объем Плеер спрятан - Перемотки объем Удалять лишнее из субтитров + Местоположение ползунка, когда игрок скрыт + Android TV + Второго планa + Смешанный опенинг + Смешанный конец + Тест провайдер + Журнал + Запустить + Выполнено + Неудачный + Прекратить + Перезапустить \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 97039233..66d8ada9 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -17,7 +17,7 @@ %dd %dh %dm %dm %d min - \@string/result_poster_img_des + Plagát Plagát epizódy Hlavný plagát Prehrať s CloudStream diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 871e0a28..5330d3ec 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -507,4 +507,20 @@ Файл безпечного режиму знайдено! \nРозширеня не завантажуються під час запуску, доки файл не буде видалено. Android TV + Плеєр сховано - обсяг пошуку + Плеєр показано - обсяг пошуку + Обсяг пошуку, який використовується, коли плеєр видимий + Обсяг пошуку, який використовується, коли гравець прихований + Не вдалося + Пройдено + Перезапуск + Журнал + Старт + Стоп + Тест постачальника + Оновлення підписаних шоу + Підписано + Підписано на %s + Відписатися від %s + Епізод %d випущено! \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a8341d46..8a10208a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -19,7 +19,7 @@ %dm 封面 - \@string/result_poster_img_des + 封面 劇集封面 主封面 隨機下一個 @@ -533,4 +533,5 @@ 預設 外觀 功能 + 瀏覽器 \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c57e3ca1..9e2d6137 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -554,4 +554,21 @@ 看来您的库是空的 :( \n登录库账户或添加节目到您的本地库 看来此列表是空的,请尝试切换到另一个 + 播放器显示 - 快进快退秒数 + 播放器可见时使用的快进快退秒数 + 播放器隐藏 - 快进快退秒数 + 播放器隐藏时使用的快进快退秒数 + Android TV + 失败 + 片源测试 + 重启 + 停止 + 正在更新订阅节目 + 已订阅 + 已订阅 %s + 已取消订阅 %s + 开始 + 第 %d 集已发布! + 成功 + 日志 \ No newline at end of file From 51137701f2c6228660720fba94d9dfc53cefb582 Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Tue, 21 Feb 2023 18:43:35 +0100 Subject: [PATCH 4/5] add proxy to raw.githubusercontent.com (#368) --- .../lagradost/cloudstream3/MainActivity.kt | 34 ++++++++++++++++++ .../cloudstream3/plugins/RepositoryManager.kt | 30 ++++++++++++---- .../ui/settings/SettingsGeneral.kt | 13 ++++--- .../lagradost/cloudstream3/utils/AppUtils.kt | 8 ++++- app/src/main/res/values/strings.xml | 6 ++++ app/src/main/res/xml/settins_general.xml | 36 ++++++++++++------- 6 files changed, 102 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 28419e7a..e626dcd6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -32,7 +32,9 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.android.gms.cast.framework.* import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.navigationrail.NavigationRailView +import com.google.android.material.snackbar.Snackbar import com.jaredrummler.android.colorpicker.ColorPickerDialogListener +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings @@ -79,6 +81,7 @@ import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable +import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadResult @@ -86,6 +89,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching @@ -717,6 +721,28 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { changeStatusBarState(isEmulatorSettings()) + // Automatically enable jsdelivr if cant connect to raw.githubusercontent.com + if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) { + main { + if (checkGithubConnectivity()) { + this.setKey(getString(R.string.jsdelivr_proxy_key), false) + } else { + this.setKey(getString(R.string.jsdelivr_proxy_key), true) + val parentView: View = findViewById(android.R.id.content) + Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG).let { snackbar -> + snackbar.setAction(R.string.revert) { + setKey(getString(R.string.jsdelivr_proxy_key), false) + } + snackbar.setBackgroundTint(colorFromAttribute(R.attr.primaryGrayBackground)) + snackbar.setTextColor(colorFromAttribute(R.attr.textColor)) + snackbar.setActionTextColor(colorFromAttribute(R.attr.colorPrimary)) + snackbar.show() + } + } + + } + } + if (PluginManager.checkSafeModeFile()) { normalSafeApiCall { @@ -1090,4 +1116,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // } } + + suspend fun checkGithubConnectivity(): Boolean { + return try { + app.get("https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck", timeout = 5).text.trim() == "ok" + } catch (t: Throwable) { + false + } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index e77b2d54..742bf308 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -2,8 +2,10 @@ package com.lagradost.cloudstream3.plugins import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError @@ -71,6 +73,15 @@ object RepositoryManager { val PREBUILT_REPOSITORIES: Array by lazy { getKey("PREBUILT_REPOSITORIES") ?: emptyArray() } + val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$") + + /* Convert raw.githubusercontent.com urls to cdn.jsdelivr.net if enabled in settings */ + fun convertRawGitUrl(url: String): String { + if (getKey(context!!.getString(R.string.jsdelivr_proxy_key)) != true) return url + val match = GH_REGEX.find(url) ?: return url + val (user, repo, rest) = match.destructured + return "https://cdn.jsdelivr.net/gh/$user/$repo@$rest" + } suspend fun parseRepoUrl(url: String): String? { val fixedUrl = url.trim() @@ -84,10 +95,15 @@ object RepositoryManager { } } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { suspendSafeApiCall { - app.get("https://l.cloudstream.cf/${fixedUrl}").let { - return@let if (it.isSuccessful && !it.url.startsWith("https://cutt.ly/branded-domains")) it.url - else app.get("https://cutt.ly/${fixedUrl}").let let2@{ it2 -> - return@let2 if (it2.isSuccessful) it2.url else null + app.get("https://l.cloudstream.cf/${fixedUrl}", allowRedirects = false).let { + it.headers["Location"]?.let { url -> + return@suspendSafeApiCall if (!url.startsWith("https://cutt.ly/branded-domains")) url + else null + } + app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 -> + it2.headers["Location"]?.let { url -> + return@suspendSafeApiCall if (url.startsWith("https://cutt.ly/404")) url else null + } } } } @@ -97,14 +113,14 @@ object RepositoryManager { suspend fun parseRepository(url: String): Repository? { return suspendSafeApiCall { // Take manifestVersion and such into account later - app.get(url).parsedSafe() + app.get(convertRawGitUrl(url)).parsedSafe() } } private suspend fun parsePlugins(pluginUrls: String): List { // Take manifestVersion and such into account later return try { - val response = app.get(pluginUrls) + val response = app.get(convertRawGitUrl(pluginUrls)) // Normal parsed function not working? // return response.parsedSafe() tryParseJson>(response.text)?.toList() ?: emptyList() @@ -139,7 +155,7 @@ object RepositoryManager { } file.createNewFile() - val body = app.get(pluginUrl).okhttpResponse.body + val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body write(body.byteStream(), file.outputStream()) file } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 354dc89c..c5a11cce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -82,7 +82,7 @@ val appLanguages = arrayListOf( Triple("", "norsk bokmål", "no"), Triple("", "polski", "pl"), Triple("\uD83C\uDDF5\uD83C\uDDF9", "português", "pt"), - Triple("🦍", "mmmm... monke", "qt"), + Triple("\uD83E\uDD8D", "mmmm... monke", "qt"), Triple("", "română", "ro"), Triple("", "русский", "ru"), Triple("", "slovenčina", "sk"), @@ -97,7 +97,7 @@ val appLanguages = arrayListOf( Triple("", "中文", "zh"), Triple("\uD83C\uDDF9\uD83C\uDDFC", "文言", "zh-rTW"), /* end language list */ -).sortedBy { it.second?.toLowerCase() } //ye, we go alphabetical, so ppl don't put their lang on top +).sortedBy { it.second.lowercase() } //ye, we go alphabetical, so ppl don't put their lang on top class SettingsGeneral : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -157,9 +157,6 @@ class SettingsGeneral : PreferenceFragmentCompat() { getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> val tempLangs = appLanguages.toMutableList() - //if (beneneCount > 100) { - // tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo")) - //} val current = getCurrentLocale(pref.context) val languageCodes = tempLangs.map { (_, _, iso) -> iso } val languageNames = tempLangs.map { (emoji, name, iso) -> @@ -316,6 +313,12 @@ class SettingsGeneral : PreferenceFragmentCompat() { } ?: emptyList() } + settingsManager.edit().putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false).apply() + getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue -> + setKey(getString(R.string.jsdelivr_proxy_key), newValue) + return@setOnPreferenceChangeListener true + } + getPref(R.string.download_path_key)?.setOnPreferenceClickListener { val dirs = getDownloadDirs() diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index 860144ee..205f0a6b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -491,6 +491,12 @@ object AppUtils { } } + fun Context.isNetworkAvailable(): Boolean { + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetworkInfo = manager.activeNetworkInfo + return activeNetworkInfo != null && activeNetworkInfo.isConnected || manager.allNetworkInfo?.any { it.isConnected } ?: false + } + fun splitQuery(url: URL): Map { val queryPairs: MutableMap = LinkedHashMap() val query: String = url.query @@ -815,4 +821,4 @@ object AppUtils { } return currentAudioFocusRequest } -} \ No newline at end of file +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0858fdfa..2d46a70d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,6 +43,7 @@ random_button_key provider_lang_key dns_key + jsdelivr_proxy_key download_path_key Cloudstream app_layout_key @@ -378,6 +379,9 @@ Causes problems if set too high on devices with low storage space, such as Android TV. DNS over HTTPS Useful for bypassing ISP blocks + raw.githubusercontent.com Proxy + Failed to reach GitHub, enabling jsdelivr proxy. + Bypasses blocking of GitHub using jsdelivr, may cause updates to be delayed by few days. Clone site Remove site Add a clone of an existing site, with a different URL @@ -405,6 +409,7 @@ 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 Backup @@ -644,6 +649,7 @@ Looks like your library is empty :(\nLogin to a library account or add shows to your local library Looks like this list is empty, try switching to another one Safe mode file found!\nNot loading any extensions on startup until file is removed. + Revert Updating subscribed shows Subscribed Subscribed to %s diff --git a/app/src/main/res/xml/settins_general.xml b/app/src/main/res/xml/settins_general.xml index 726f3fd0..c4900bca 100644 --- a/app/src/main/res/xml/settins_general.xml +++ b/app/src/main/res/xml/settins_general.xml @@ -6,18 +6,6 @@ android:title="@string/app_language" android:icon="@drawable/ic_baseline_language_24" /> - - - - + + + + + + + + + + From 1da6a925692de08cb8e8ec00d93d1e5d1f76b8c6 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 18:07:04 +0000 Subject: [PATCH 5/5] update list of locales --- .../com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index c5a11cce..078419e2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -74,6 +74,7 @@ val appLanguages = arrayListOf( Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"), Triple("", "italiano", "it"), Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"), + Triple("", "日本語 (にほんご)", "ja"), Triple("", "ಕನ್ನಡ", "kn"), Triple("", "македонски", "mk"), Triple("", "മലയാളം", "ml"),