From ca918b1581c5b591570556cb5aad7be87a90a086 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Mon, 19 Feb 2024 17:43:41 +0200 Subject: [PATCH 1/7] [TV] More space around result description (#939) --- app/src/main/res/layout/fragment_result_tv.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index b8f5b5ca..a7ba4334 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -387,6 +387,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit Date: Mon, 19 Feb 2024 17:44:50 +0200 Subject: [PATCH 2/7] [TV] Limit Homepage Header Description to 3 lines & 6 Tags (#938) --- .../cloudstream3/ui/home/HomeParentItemAdapterPreview.kt | 2 +- app/src/main/res/layout/fragment_home_head_tv.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index b7f601ff..0e397f81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -317,7 +317,7 @@ class HomeParentItemAdapterPreview( homePreviewText.text = item.name populateChips( homePreviewTags, - item.tags ?: emptyList(), + item.tags?.take(6) ?: emptyList(), R.style.ChipFilledSemiTransparent ) diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index 6db7536f..ccc3a8e1 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -137,7 +137,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" - android:maxLines="5" + android:maxLines="3" android:paddingBottom="5dp" android:textSize="15sp" tools:text="very nice tv series" /> From 805f80b2acc6776b8882d0a92dfad2edf3003a42 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Mon, 19 Feb 2024 17:46:02 +0200 Subject: [PATCH 3/7] Long press Repo to copy URL (#934) --- .../ui/settings/extensions/RepoAdapter.kt | 18 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 2 files changed, 19 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt index 602b45e4..7ac7cbb2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt @@ -1,10 +1,17 @@ package com.lagradost.cloudstream3.ui.settings.extensions +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build import android.view.LayoutInflater import android.view.ViewGroup +import android.widget.Toast import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding @@ -112,6 +119,17 @@ class RepoAdapter( repositoryItemRoot.setOnClickListener { clickCallback(repositoryData) } + + repositoryItemRoot.setOnLongClickListener { + val clipboardManager = + activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager? + clipboardManager?.setPrimaryClip(ClipData.newPlainText("RepoUrl", repositoryData.url)) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + showToast(R.string.copyRepoUrl, Toast.LENGTH_SHORT) + } + return@setOnLongClickListener true + } + mainText.text = repositoryData.name subText.text = repositoryData.url } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e68aea7b..14bb9552 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -174,6 +174,7 @@ Clear Save Title copied! + Repo URL copied! New episode notification Search in other extensions Show recommendations From e007714701393957f75abff48d84699d3b2b0180 Mon Sep 17 00:00:00 2001 From: Sofie <117321707+Sofie99@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:06:55 +0700 Subject: [PATCH 4/7] fix Rabbitstream (#936) * fix Rabbitstream * . --- .../cloudstream3/extractors/Rabbitstream.kt | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt index 74009b70..2df81bc6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64DecodeArray +import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorApi @@ -16,13 +17,52 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec -// Code found in https://github.com/theonlymo/keys -// special credits to @theonlymo for providing key class Megacloud : Rabbitstream() { override val name = "Megacloud" override val mainUrl = "https://megacloud.tv" override val embed = "embed-2/ajax/e-1" - override val key = "https://raw.githubusercontent.com/theonlymo/keys/e1/key" + private val scriptUrl = "$mainUrl/js/player/a/prod/e1-player.min.js" + + override suspend fun extractRealKey(sources: String): Pair { + val rawKeys = getKeys() + val sourcesArray = sources.toCharArray() + + var extractedKey = "" + var currentIndex = 0 + for (index in rawKeys) { + val start = index[0] + currentIndex + val end = start + index[1] + for (i in start until end) { + extractedKey += sourcesArray[i].toString() + sourcesArray[i] = ' ' + } + currentIndex += index[1] + } + + return extractedKey to sourcesArray.joinToString("").replace(" ", "") + } + + private suspend fun getKeys(): List> { + val script = app.get(scriptUrl).text + fun matchingKey(value: String): String { + return Regex(",$value=((?:0x)?([0-9a-fA-F]+))").find(script)?.groupValues?.get(1) + ?.removePrefix("0x") ?: throw ErrorLoadingException("Failed to match the key") + } + + val regex = Regex("case\\s*0x[0-9a-f]+:(?![^;]*=partKey)\\s*\\w+\\s*=\\s*(\\w+)\\s*,\\s*\\w+\\s*=\\s*(\\w+);") + val indexPairs = regex.findAll(script).toList().map { match -> + val matchKey1 = matchingKey(match.groupValues[1]) + val matchKey2 = matchingKey(match.groupValues[2]) + try { + listOf(matchKey1.toInt(16), matchKey2.toInt(16)) + } catch (e: NumberFormatException) { + emptyList() + } + }.filter { it.isNotEmpty() } + + return indexPairs + } + } class Dokicloud : Rabbitstream() { @@ -30,12 +70,14 @@ class Dokicloud : Rabbitstream() { override val mainUrl = "https://dokicloud.one" } +// Code found in https://github.com/eatmynerds/key +// special credits to @eatmynerds for providing key open class Rabbitstream : ExtractorApi() { override val name = "Rabbitstream" override val mainUrl = "https://rabbitstream.net" override val requiresReferer = false open val embed = "ajax/embed-4" - open val key = "https://raw.githubusercontent.com/theonlymo/keys/e4/key" + open val key = "https://raw.githubusercontent.com/eatmynerds/key/e4/key.txt" override suspend fun getUrl( url: String, @@ -47,7 +89,7 @@ open class Rabbitstream : ExtractorApi() { val response = app.get( "$mainUrl/$embed/getSources?id=$id", - referer = url, + referer = mainUrl, headers = mapOf("X-Requested-With" to "XMLHttpRequest") ) @@ -56,7 +98,7 @@ open class Rabbitstream : ExtractorApi() { val decryptedSources = if (sources == null || encryptedMap.encrypted == false) { response.parsedSafe() } else { - val (key, encData) = extractRealKey(sources, getRawKey()) + val (key, encData) = extractRealKey(sources) val decrypted = decryptMapped>(encData, key) SourcesResponses( sources = decrypted, @@ -72,11 +114,11 @@ open class Rabbitstream : ExtractorApi() { ).forEach(callback) } - decryptedSources?.tracks?.filter { it?.kind == "captions" }?.map { track -> + decryptedSources?.tracks?.map { track -> subtitleCallback.invoke( SubtitleFile( - track?.label ?: "", - track?.file ?: return@map + track?.label ?: return@map, + track.file ?: return@map ) ) } @@ -84,25 +126,10 @@ open class Rabbitstream : ExtractorApi() { } - private suspend fun getRawKey(): String = app.get(key).text - - private fun extractRealKey(sources: String, stops: String): Pair { - val decryptKey = parseJson>>(stops) - val sourcesArray = sources.toCharArray() - - var extractedKey = "" - var currentIndex = 0 - for (index in decryptKey) { - val start = index[0] + currentIndex - val end = start + index[1] - for (i in start until end) { - extractedKey += sourcesArray[i].toString() - sourcesArray[i] = ' ' - } - currentIndex += index[1] - } - - return extractedKey to sourcesArray.joinToString("") + open suspend fun extractRealKey(sources: String): Pair { + val rawKeys = parseJson>(app.get(key).text) + val extractedKey = base64Encode(rawKeys.map { it.toByte() }.toByteArray()) + return extractedKey to sources } private inline fun decryptMapped(input: String, key: String): T? { From 93d81ea038ca3decc15b97d582c09eb8e4303c31 Mon Sep 17 00:00:00 2001 From: Sofie <117321707+Sofie99@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:18:36 +0700 Subject: [PATCH 5/7] WebViewResolver: added timeout (#941) --- .../cloudstream3/network/WebViewResolver.kt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt index 2c11bcdd..90872d94 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt @@ -31,6 +31,7 @@ import java.net.URI * @param useOkhttp will try to use the okhttp client as much as possible, but this might cause some requests to fail. Disable for cloudflare. * @param script pass custom js to execute * @param scriptCallback will be called with the result from custom js + * @param timeout close webview after timeout * */ class WebViewResolver( val interceptUrl: Regex, @@ -38,18 +39,29 @@ class WebViewResolver( val userAgent: String? = USER_AGENT, val useOkhttp: Boolean = true, val script: String? = null, - val scriptCallback: ((String) -> Unit)? = null + val scriptCallback: ((String) -> Unit)? = null, + val timeout: Long = DEFAULT_TIMEOUT ) : Interceptor { + constructor( + interceptUrl: Regex, + additionalUrls: List = emptyList(), + userAgent: String? = USER_AGENT, + useOkhttp: Boolean = true, + script: String? = null, + scriptCallback: ((String) -> Unit)? = null, + ) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, script, scriptCallback, DEFAULT_TIMEOUT) + constructor( interceptUrl: Regex, additionalUrls: List = emptyList(), userAgent: String? = USER_AGENT, useOkhttp: Boolean = true - ) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, null, null) + ) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, null, null, DEFAULT_TIMEOUT) companion object { + private const val DEFAULT_TIMEOUT = 60_000L var webViewUserAgent: String? = null @JvmName("getWebViewUserAgent1") @@ -262,7 +274,7 @@ class WebViewResolver( var loop = 0 // Timeouts after this amount, 60s - val totalTime = 60000L + val totalTime = timeout val delayTime = 100L From 1a380a3239428d26e6510ed272bc8dd033a1f55a Mon Sep 17 00:00:00 2001 From: KingLucius Date: Thu, 29 Feb 2024 18:07:45 +0200 Subject: [PATCH 6/7] TV show airing status for phone (#953) * TV show airing status for phone * Bump Nicehttp & remove toImmutableList --- app/build.gradle.kts | 2 +- .../com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt | 1 + .../lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 80f6fd73..83100db8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -230,7 +230,7 @@ dependencies { // Downloading & Networking implementation("androidx.work:work-runtime:2.9.0") implementation("androidx.work:work-runtime-ktx:2.9.0") - implementation("com.github.Blatzar:NiceHttp:0.4.5") // HTTP Lib + implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib } tasks.register("androidSourcesJar", Jar::class) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index e7e50b9d..76066c2e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -681,6 +681,7 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultMetaYear.setText(d.yearText) resultMetaDuration.setText(d.durationText) resultMetaRating.setText(d.ratingText) + resultMetaStatus.setText(d.onGoingText) resultMetaContentRating.setText(d.contentRatingText) resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt index 4fd24afe..9e6f8a06 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt @@ -10,7 +10,6 @@ import com.lagradost.cloudstream3.utils.TestingUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel -import okhttp3.internal.toImmutableList class TestViewModel : ViewModel() { data class TestProgress( From 809a38507bdfc73262d0dd9e6da8c6cc1028341f Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Sat, 2 Mar 2024 22:45:18 +0000 Subject: [PATCH 7/7] Update SimklApi.kt (#961) --- .../syncproviders/providers/SimklApi.kt | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index e0b13ba6..08c8588b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -440,9 +440,9 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { interceptor = interceptor ).isSuccessful } else { - val statusResponse = status?.let { setStatus -> + val statusResponse = this.status?.let { setStatus -> val newStatus = - SimklListStatusType.values() + SimklListStatusType.entries .firstOrNull { it.value == setStatus }?.originalName ?: SimklListStatusType.Watching.originalName!! @@ -479,9 +479,14 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { ).isSuccessful } ?: true + // You cannot rate if you are planning to watch it. + val shouldRate = + score != null && status != SimklListStatusType.Planning.value + val realScore = if (shouldRate) score else null + val historyResponse = // Only post if there are episodes or score to upload - if (addEpisodes != null || score != null) { + if (addEpisodes != null || shouldRate) { app.post( "${this.url}/sync/history", json = StatusRequest( @@ -492,8 +497,8 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { ids, addEpisodes?.first, addEpisodes?.second, - score, - score?.let { time }, + realScore, + realScore?.let { time }, ) ), movies = emptyList() ), @@ -827,7 +832,13 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { if (foundItem != null) { return SimklSyncStatus( - status = foundItem.status?.let { SyncWatchType.fromInternalId(SimklListStatusType.fromString(it)?.value) } + status = foundItem.status?.let { + SyncWatchType.fromInternalId( + SimklListStatusType.fromString( + it + )?.value + ) + } ?: return null, score = foundItem.user_rating, watchedEpisodes = foundItem.watched_episodes_count, @@ -839,7 +850,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { ) } else { return SimklSyncStatus( - status = SyncWatchType.fromInternalId(SimklListStatusType.None.value) , + status = SyncWatchType.fromInternalId(SimklListStatusType.None.value), score = 0, watchedEpisodes = 0, maxEpisodes = if (searchResult.type == "movie") 0 else searchResult.total_episodes, @@ -859,11 +870,13 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { val builder = SimklScoreBuilder.Builder() .apiUrl(this.mainUrl) .score(status.score, simklStatus?.oldScore) - .status(status.status.internalId, (status as? SimklSyncStatus)?.oldStatus?.let { oldStatus -> - SimklListStatusType.values().firstOrNull { - it.originalName == oldStatus - }?.value - }) + .status( + status.status.internalId, + (status as? SimklSyncStatus)?.oldStatus?.let { oldStatus -> + SimklListStatusType.entries.firstOrNull { + it.originalName == oldStatus + }?.value + }) .interceptor(interceptor) .ids(MediaObject.Ids.fromMap(parsedId)) @@ -996,7 +1009,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { val list = getSyncListSmart() ?: return null val baseMap = - SimklListStatusType.values() + SimklListStatusType.entries .filter { it.value >= 0 && it.value != SimklListStatusType.ReWatching.value } .associate { it.stringRes to emptyList()