From 9206d686c9d98cab1082fc04acfa314189e71dd8 Mon Sep 17 00:00:00 2001 From: IstarVin Date: Mon, 18 May 2026 01:58:20 +0800 Subject: [PATCH 1/4] feat: add ShareLinkActivity to handle shared URLs for direct routing to Result View --- app/src/main/AndroidManifest.xml | 23 ++++ .../lagradost/cloudstream3/MainActivity.kt | 115 ++++++++++++++++++ .../cloudstream3/ui/ShareLinkActivity.kt | 49 ++++++++ 3 files changed, 187 insertions(+) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee4c978f2be..de3f07c03b9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,6 +98,29 @@ android:shell="true" tools:targetApi="q" /> + + + + + + + + + + + + + + + + + + + Unit + observer = { + main { + afterPluginsLoadedEvent -= observer + sharedUrlPluginObserver = null + tryRouteSharedUrl(showUnsupported = false) + } + } + sharedUrlPluginObserver = observer + afterPluginsLoadedEvent += observer + + sharedUrlHandler.postDelayed({ + afterPluginsLoadedEvent -= observer + sharedUrlPluginObserver = null + if (!tryRouteSharedUrl(showUnsupported = false)) { + tryRouteSharedUrl(showUnsupported = true) + } + }, 2500) + + return true + } + + private fun routeSharedUrl(sharedUrl: String, showUnsupported: Boolean): Boolean { + val normalizedUrl = normalizeSharedUrl(sharedUrl) + val provider = synchronized(allProviders) { + apis.firstOrNull { normalizedUrl.startsWith(normalizeSharedUrl(it.mainUrl)) } + ?: allProviders.firstOrNull { normalizedUrl.startsWith(normalizeSharedUrl(it.mainUrl)) } + } + + if (provider != null) { + sharedUrlHandler.post { + navigate( + getSharedResultsId(), + ResultFragment.newInstance(sharedUrl, provider.name, sharedUrl) + ) + } + return true + } + + val extractor = synchronized(extractorApis) { + extractorApis.asReversed() + .firstOrNull { normalizedUrl.startsWith(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 getSharedResultsId(): Int { + return if (isLayout(TV or EMULATOR)) { + R.id.global_to_navigation_results_tv + } else { + R.id.global_to_navigation_results_phone + } + } + private fun NavDestination.matchDestination(@IdRes destId: Int): Boolean = hierarchy.any { it.id == destId } @@ -811,7 +921,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) { @@ -847,6 +960,8 @@ 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()) /** kinda dirty, however it signals that we should use the watch status as sync or not*/ var isLocalList: Boolean = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt new file mode 100644 index 00000000000..74bbfb25bcb --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt @@ -0,0 +1,49 @@ +package com.lagradost.cloudstream3.ui + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL +import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL_HANDLED +import com.lagradost.cloudstream3.ui.account.AccountSelectActivity + +class ShareLinkActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + forwardShareIntent(intent) + finish() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + forwardShareIntent(intent) + finish() + } + + private fun forwardShareIntent(sourceIntent: Intent?) { + val url = sourceIntent?.extractSharedUrl() ?: return + val targetIntent = Intent(sourceIntent).apply { + setClass(this@ShareLinkActivity, AccountSelectActivity::class.java) + putExtra(EXTRA_SHARED_URL, url) + putExtra(EXTRA_SHARED_URL_HANDLED, false) + } + startActivity(targetIntent) + } + + private fun Intent.extractSharedUrl(): String? { + val candidates = listOfNotNull( + dataString, + getStringExtra(Intent.EXTRA_TEXT), + getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString(), + @Suppress("DEPRECATION") + getParcelableExtra(Intent.EXTRA_STREAM)?.toString(), + ) + + return candidates.firstNotNullOfOrNull { value -> + value + .lineSequence() + .flatMap { it.splitToSequence(Regex("\\s+")) } + .firstOrNull { it.startsWith("http://") || it.startsWith("https://") } + } + } +} From 502ffb5eacab99807f3e74e7b53a6a178288225c Mon Sep 17 00:00:00 2001 From: IstarVin Date: Mon, 18 May 2026 02:15:51 +0800 Subject: [PATCH 2/4] fix: update shared URL handling to use unique IDs and improve intent forwarding --- .../lagradost/cloudstream3/MainActivity.kt | 27 ++++++++++++------- .../cloudstream3/ui/ShareLinkActivity.kt | 6 +++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index ecf61305bc1..aa80868d80b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -222,7 +222,7 @@ 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_HANDLED = "EXTRA_SHARED_URL_HANDLED" + const val EXTRA_SHARED_URL_ID = "EXTRA_SHARED_URL_ID" /** * Transient files to delete on application exit. @@ -752,14 +752,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa private fun handleSharedUrlIntent(intent: Intent): Boolean { val sharedUrl = intent.getStringExtra(EXTRA_SHARED_URL) ?: return false - if (intent.getBooleanExtra(EXTRA_SHARED_URL_HANDLED, false)) return true - - intent.putExtra(EXTRA_SHARED_URL_HANDLED, true) + 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 } @@ -781,13 +782,14 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa sharedUrlPluginObserver = observer afterPluginsLoadedEvent += observer - sharedUrlHandler.postDelayed({ + timeoutRunnable = Runnable { afterPluginsLoadedEvent -= observer sharedUrlPluginObserver = null if (!tryRouteSharedUrl(showUnsupported = false)) { tryRouteSharedUrl(showUnsupported = true) } - }, 2500) + } + sharedUrlHandler.postDelayed(timeoutRunnable, 2500) return true } @@ -795,15 +797,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa private fun routeSharedUrl(sharedUrl: String, showUnsupported: Boolean): Boolean { val normalizedUrl = normalizeSharedUrl(sharedUrl) val provider = synchronized(allProviders) { - apis.firstOrNull { normalizedUrl.startsWith(normalizeSharedUrl(it.mainUrl)) } - ?: allProviders.firstOrNull { normalizedUrl.startsWith(normalizeSharedUrl(it.mainUrl)) } + apis.firstOrNull { normalizedUrl.matchesSharedUrlBase(normalizeSharedUrl(it.mainUrl)) } + ?: allProviders.firstOrNull { normalizedUrl.matchesSharedUrlBase(normalizeSharedUrl(it.mainUrl)) } } if (provider != null) { sharedUrlHandler.post { navigate( getSharedResultsId(), - ResultFragment.newInstance(sharedUrl, provider.name, sharedUrl) + ResultFragment.newInstance(sharedUrl, provider.name, provider.name) ) } return true @@ -811,7 +813,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa val extractor = synchronized(extractorApis) { extractorApis.asReversed() - .firstOrNull { normalizedUrl.startsWith(normalizeSharedUrl(it.mainUrl)) } + .firstOrNull { normalizedUrl.matchesSharedUrlBase(normalizeSharedUrl(it.mainUrl)) } } if (extractor != null) { @@ -845,6 +847,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa .trimEnd('/') } + private fun String.matchesSharedUrlBase(baseUrl: String): Boolean { + return this == baseUrl || startsWith("$baseUrl/") + } + private fun getSharedResultsId(): Int { return if (isLayout(TV or EMULATOR)) { R.id.global_to_navigation_results_tv @@ -962,6 +968,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa private var libraryViewModel: LibraryViewModel? = null private var sharedUrlPluginObserver: ((Boolean) -> Unit)? = null private val sharedUrlHandler = Handler(Looper.getMainLooper()) + private val handledSharedUrlIds = mutableSetOf() /** kinda dirty, however it signals that we should use the watch status as sync or not*/ var isLocalList: Boolean = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt index 74bbfb25bcb..9ab9ed5f1e0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt @@ -4,8 +4,9 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL -import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL_HANDLED +import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL_ID import com.lagradost.cloudstream3.ui.account.AccountSelectActivity +import java.util.UUID class ShareLinkActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -25,7 +26,7 @@ class ShareLinkActivity : Activity() { val targetIntent = Intent(sourceIntent).apply { setClass(this@ShareLinkActivity, AccountSelectActivity::class.java) putExtra(EXTRA_SHARED_URL, url) - putExtra(EXTRA_SHARED_URL_HANDLED, false) + putExtra(EXTRA_SHARED_URL_ID, sourceIntent.getStringExtra(EXTRA_SHARED_URL_ID) ?: UUID.randomUUID().toString()) } startActivity(targetIntent) } @@ -43,6 +44,7 @@ class ShareLinkActivity : Activity() { value .lineSequence() .flatMap { it.splitToSequence(Regex("\\s+")) } + .map { it.trimEnd('.', ',', ';', ':', '!', '?', ')', ']', '}', '>', '\'', '"') } .firstOrNull { it.startsWith("http://") || it.startsWith("https://") } } } From 7f06c0d0d26a155fe38277e763326fa4efc56764 Mon Sep 17 00:00:00 2001 From: IstarVin Date: Mon, 18 May 2026 11:13:36 +0800 Subject: [PATCH 3/4] fix: clean up shared URL handling by removing unused methods and optimizing intent extraction --- .../lagradost/cloudstream3/MainActivity.kt | 24 ++++--------------- .../cloudstream3/ui/ShareLinkActivity.kt | 1 - 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index aa80868d80b..adb9d08cccd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -113,7 +113,6 @@ import com.lagradost.cloudstream3.ui.player.BasicLink import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.LinkGenerator import com.lagradost.cloudstream3.ui.result.LinearListLayout -import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultViewModel2 import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.SyncViewModel @@ -732,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() } @@ -796,18 +798,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa private fun routeSharedUrl(sharedUrl: String, showUnsupported: Boolean): Boolean { val normalizedUrl = normalizeSharedUrl(sharedUrl) - val provider = synchronized(allProviders) { - apis.firstOrNull { normalizedUrl.matchesSharedUrlBase(normalizeSharedUrl(it.mainUrl)) } - ?: allProviders.firstOrNull { normalizedUrl.matchesSharedUrlBase(normalizeSharedUrl(it.mainUrl)) } - } + val provider = APIHolder.getApiFromUrlNull(sharedUrl) if (provider != null) { - sharedUrlHandler.post { - navigate( - getSharedResultsId(), - ResultFragment.newInstance(sharedUrl, provider.name, provider.name) - ) - } + loadResult(sharedUrl, provider.name, "") return true } @@ -851,14 +845,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa return this == baseUrl || startsWith("$baseUrl/") } - private fun getSharedResultsId(): Int { - return if (isLayout(TV or EMULATOR)) { - R.id.global_to_navigation_results_tv - } else { - R.id.global_to_navigation_results_phone - } - } - private fun NavDestination.matchDestination(@IdRes destId: Int): Boolean = hierarchy.any { it.id == destId } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt index 9ab9ed5f1e0..b842e5b172f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt @@ -34,7 +34,6 @@ class ShareLinkActivity : Activity() { private fun Intent.extractSharedUrl(): String? { val candidates = listOfNotNull( dataString, - getStringExtra(Intent.EXTRA_TEXT), getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString(), @Suppress("DEPRECATION") getParcelableExtra(Intent.EXTRA_STREAM)?.toString(), From 358b12aedc7364429a0465d898e9cc5c5cb5b05c Mon Sep 17 00:00:00 2001 From: IstarVin Date: Tue, 19 May 2026 08:13:29 +0800 Subject: [PATCH 4/4] refactor: remove ShareLinkActivity and integrate shared URL handling into AccountSelectActivity --- app/src/main/AndroidManifest.xml | 39 ++++++--------- .../cloudstream3/ui/ShareLinkActivity.kt | 50 ------------------- .../ui/account/AccountSelectActivity.kt | 31 +++++++++++- 3 files changed, 46 insertions(+), 74 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index de3f07c03b9..29d5523ff35 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,29 +98,6 @@ android:shell="true" tools:targetApi="q" /> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt deleted file mode 100644 index b842e5b172f..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ShareLinkActivity.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.lagradost.cloudstream3.ui - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL -import com.lagradost.cloudstream3.MainActivity.Companion.EXTRA_SHARED_URL_ID -import com.lagradost.cloudstream3.ui.account.AccountSelectActivity -import java.util.UUID - -class ShareLinkActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - forwardShareIntent(intent) - finish() - } - - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - forwardShareIntent(intent) - finish() - } - - private fun forwardShareIntent(sourceIntent: Intent?) { - val url = sourceIntent?.extractSharedUrl() ?: return - val targetIntent = Intent(sourceIntent).apply { - setClass(this@ShareLinkActivity, AccountSelectActivity::class.java) - putExtra(EXTRA_SHARED_URL, url) - putExtra(EXTRA_SHARED_URL_ID, sourceIntent.getStringExtra(EXTRA_SHARED_URL_ID) ?: UUID.randomUUID().toString()) - } - startActivity(targetIntent) - } - - private fun Intent.extractSharedUrl(): String? { - val candidates = listOfNotNull( - dataString, - getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString(), - @Suppress("DEPRECATION") - getParcelableExtra(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://") } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index ad323c7d124..6d01322f908 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -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 @@ -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 @@ -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 { @@ -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(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") }