Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,22 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.SEND" />

<category android:name="android.intent.category.DEFAULT" />

<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>

<!-- cloudstreamplayer://encodedUrl?name=Dune -->
<intent-filter>
Expand Down
108 changes: 108 additions & 0 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Rect
import android.os.Handler
import android.os.Bundle
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
Expand Down Expand Up @@ -156,6 +158,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.ImageLoader.loadImage
import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
Expand Down Expand Up @@ -217,6 +220,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa

private const val FILE_DELETE_KEY = "FILES_TO_DELETE_KEY"
const val API_NAME_EXTRA_KEY = "API_NAME_EXTRA_KEY"
const val EXTRA_SHARED_URL = "EXTRA_SHARED_URL"
const val EXTRA_SHARED_URL_ID = "EXTRA_SHARED_URL_ID"

/**
* Transient files to delete on application exit.
Expand Down Expand Up @@ -726,6 +731,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
this.sendBroadcast(broadcastIntent)
afterPluginsLoadedEvent -= ::onAllPluginsLoaded
sharedUrlPluginObserver?.let { afterPluginsLoadedEvent -= it }
sharedUrlPluginObserver = null
sharedUrlHandler.removeCallbacksAndMessages(null)
detachBackPressedCallback("MainActivityDefault")
super.onDestroy()
}
Expand All @@ -740,9 +748,103 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
val str = intent.dataString
loadCache()

if (handleSharedUrlIntent(intent)) return
handleAppIntentUrl(this, str, false, intent.extras)
}

private fun handleSharedUrlIntent(intent: Intent): Boolean {
val sharedUrl = intent.getStringExtra(EXTRA_SHARED_URL) ?: return false
val sharedUrlId = intent.getStringExtra(EXTRA_SHARED_URL_ID) ?: sharedUrl
if (!handledSharedUrlIds.add(sharedUrlId)) return true

var hasHandledSharedUrl = false
var timeoutRunnable: Runnable? = null
fun tryRouteSharedUrl(showUnsupported: Boolean): Boolean {
if (hasHandledSharedUrl) return true
hasHandledSharedUrl = routeSharedUrl(sharedUrl, showUnsupported)
if (hasHandledSharedUrl) timeoutRunnable?.let(sharedUrlHandler::removeCallbacks)
return hasHandledSharedUrl
}

if (tryRouteSharedUrl(showUnsupported = false)) return true
if (sharedLinkPluginsLoaded) {
tryRouteSharedUrl(showUnsupported = true)
return true
}

sharedUrlPluginObserver?.let { afterPluginsLoadedEvent -= it }
lateinit var observer: (Boolean) -> Unit
observer = {
main {
afterPluginsLoadedEvent -= observer
sharedUrlPluginObserver = null
tryRouteSharedUrl(showUnsupported = false)
}
}
sharedUrlPluginObserver = observer
afterPluginsLoadedEvent += observer

timeoutRunnable = Runnable {
afterPluginsLoadedEvent -= observer
sharedUrlPluginObserver = null
if (!tryRouteSharedUrl(showUnsupported = false)) {
tryRouteSharedUrl(showUnsupported = true)
}
}
sharedUrlHandler.postDelayed(timeoutRunnable, 2500)

return true
}

private fun routeSharedUrl(sharedUrl: String, showUnsupported: Boolean): Boolean {
val normalizedUrl = normalizeSharedUrl(sharedUrl)
val provider = APIHolder.getApiFromUrlNull(sharedUrl)

if (provider != null) {
loadResult(sharedUrl, provider.name, "")
return true
}

val extractor = synchronized(extractorApis) {
extractorApis.asReversed()
.firstOrNull { normalizedUrl.matchesSharedUrlBase(normalizeSharedUrl(it.mainUrl)) }
}

if (extractor != null) {
sharedUrlHandler.post {
navigate(
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
LinkGenerator(
listOf(BasicLink(sharedUrl)),
extract = true,
id = sharedUrl.hashCode()
),
0
)
)
}
return true
}

if (showUnsupported) {
showToast(this, "Unsupported shared link", Toast.LENGTH_LONG)
return true
}

return false
}

private fun normalizeSharedUrl(url: String): String {
return url.lowercase()
.replace(Regex("""^(https?:)?//(www\.)?"""), "")
.trimEnd('/')
}

private fun String.matchesSharedUrlBase(baseUrl: String): Boolean {
return this == baseUrl || startsWith("$baseUrl/")
}

private fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
hierarchy.any { it.id == destId }

Expand Down Expand Up @@ -811,7 +913,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa


private val pluginsLock = Mutex()
private var sharedLinkPluginsLoaded = false

private fun onAllPluginsLoaded(success: Boolean = false) {
sharedLinkPluginsLoaded = true
ioSafe {
pluginsLock.withLock {
synchronized(allProviders) {
Expand Down Expand Up @@ -847,6 +952,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
lateinit var viewModel: ResultViewModel2
lateinit var syncViewModel: SyncViewModel
private var libraryViewModel: LibraryViewModel? = null
private var sharedUrlPluginObserver: ((Boolean) -> Unit)? = null
private val sharedUrlHandler = Handler(Looper.getMainLooper())
private val handledSharedUrlIds = mutableSetOf<String>()

/** kinda dirty, however it signals that we should use the watch status as sync or not*/
var isLocalList: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.account

import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.FragmentActivity
Expand All @@ -11,6 +12,8 @@ import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.CommonActivity.loadThemes
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL
import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL_ID
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ActivityAccountSelectBinding
import com.lagradost.cloudstream3.mvvm.observe
Expand All @@ -35,6 +38,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.enableEdgeToEdgeCompat
import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
import com.lagradost.cloudstream3.utils.UIHelper.openActivity
import com.lagradost.cloudstream3.utils.UIHelper.setNavigationBarColorCompat
import java.util.UUID

class AccountSelectActivity : FragmentActivity(), BiometricCallback {

Expand Down Expand Up @@ -205,10 +209,35 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback {
private fun navigateToMainActivity() {
hasLoggedIn = true
// We want to propagate any intent we get here to MainActivity since this is just an intermediary
openActivity(MainActivity::class.java, baseIntent = intent)
openActivity(MainActivity::class.java, baseIntent = intent.withExtractedSharedUrl())
finish() // Finish the account selection activity
}

private fun Intent.withExtractedSharedUrl(): Intent {
val url = extractSharedUrl() ?: return this
return Intent(this).apply {
putExtra(EXTRA_SHARED_URL, url)
putExtra(EXTRA_SHARED_URL_ID, getStringExtra(EXTRA_SHARED_URL_ID) ?: UUID.randomUUID().toString())
}
}

private fun Intent.extractSharedUrl(): String? {
val candidates = listOfNotNull(
dataString,
getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString(),
@Suppress("DEPRECATION")
getParcelableExtra<android.net.Uri>(Intent.EXTRA_STREAM)?.toString(),
)

return candidates.firstNotNullOfOrNull { value ->
value
.lineSequence()
.flatMap { it.splitToSequence(Regex("\\s+")) }
.map { it.trimEnd('.', ',', ';', ':', '!', '?', ')', ']', '}', '>', '\'', '"') }
.firstOrNull { it.startsWith("http://") || it.startsWith("https://") }
}
}

override fun onAuthenticationSuccess() {
Log.i(BiometricAuthenticator.TAG, "Authentication successful in AccountSelectActivity")
}
Expand Down