From b459126f58944a04cd3c98aebf22d8ca393ac964 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Sat, 8 Jun 2024 10:23:16 +0300 Subject: [PATCH] feat(TV UI): Accounts PIN login support --- app/build.gradle.kts | 1 + .../cloudstream3/syncproviders/OAuth2API.kt | 16 +++ .../syncproviders/providers/AniListApi.kt | 1 + .../syncproviders/providers/DropboxApi.kt | 1 + .../syncproviders/providers/LocalList.kt | 1 + .../syncproviders/providers/MALApi.kt | 1 + .../syncproviders/providers/SimklApi.kt | 55 +++++++++++ .../cloudstream3/ui/result/UiText.kt | 13 +++ .../ui/settings/SettingsAccount.kt | 91 +++++++++++++++++- app/src/main/res/drawable/example_qr.png | Bin 0 -> 46354 bytes app/src/main/res/layout/device_auth.xml | 59 ++++++++++++ app/src/main/res/values/strings.xml | 6 ++ 12 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/example_qr.png create mode 100644 app/src/main/res/layout/device_auth.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 61a0634f..fc2e9131 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -217,6 +217,7 @@ dependencies { implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview + implementation("io.github.g0dkar:qrcode-kotlin:4.1.1") // QR code for PIN Auth on TV // Extensions & Other Libs implementation("org.mozilla:rhino:1.7.15") // run JavaScript diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt index ef74edfc..3d0bb940 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt @@ -5,7 +5,23 @@ import androidx.fragment.app.FragmentActivity interface OAuth2API : AuthAPI { val key: String val redirectUrl: String + val supportDeviceAuth: Boolean suspend fun handleRedirect(url: String) : Boolean fun authenticate(activity: FragmentActivity?) + suspend fun getDevicePin() : PinAuthData? { + return null + } + + suspend fun handleDeviceAuth(pinAuthData: PinAuthData) : Boolean { + return false + } + + data class PinAuthData( + val deviceCode: String, + val userCode: String, + val verificationUrl: String, + val expiresIn: Int, + val interval: Int, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 5c02e7f7..0551fe6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -32,6 +32,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { override val redirectUrl = "anilistlogin" override val idPrefix = "anilist" override var requireLibraryRefresh = true + override val supportDeviceAuth = false override var mainUrl = "https://anilist.co" override val icon = R.drawable.ic_anilist_icon override val requiresLogin = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt index 7ec168da..94537ea3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt @@ -11,6 +11,7 @@ class Dropbox : OAuth2API { override val key = "zlqsamadlwydvb2" override val redirectUrl = "dropboxlogin" override val requiresLogin = true + override val supportDeviceAuth = false override val createAccountUrl: String? = null override val icon: Int 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 7552fe9d..00f8d00c 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 @@ -21,6 +21,7 @@ class LocalList : SyncAPI { override val name = "Local" override val icon: Int = R.drawable.ic_baseline_storage_24 override val requiresLogin = false + override val supportDeviceAuth = false override val createAccountUrl: Nothing? = null override val idPrefix = "local" override var requireLibraryRefresh = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index fdbe763a..4249f949 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -40,6 +40,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { private val apiUrl = "https://api.myanimelist.net" override val icon = R.drawable.mal_logo override val requiresLogin = false + override val supportDeviceAuth = false override val syncIdName = SyncIdName.MyAnimeList override var requireLibraryRefresh = true override val createAccountUrl = "$mainUrl/register.php" 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 08c8588b..4385fa5e 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 @@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI +import com.lagradost.cloudstream3.syncproviders.OAuth2API import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.SyncWatchType @@ -45,6 +46,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { override var name = "Simkl" override val key = "simkl-key" override val redirectUrl = "simkl" + override val supportDeviceAuth = true override val idPrefix = "simkl" override var requireLibraryRefresh = true override var mainUrl = "https://api.simkl.com" @@ -267,6 +269,21 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { ) } + data class PinAuthResponse( + @JsonProperty("result") val result: String, + @JsonProperty("device_code") val deviceCode: String, + @JsonProperty("user_code") val userCode: String, + @JsonProperty("verification_url") val verificationUrl: String, + @JsonProperty("expires_in") val expiresIn: Int, + @JsonProperty("interval") val interval: Int, + ) + + data class PinExchangeResponse( + @JsonProperty("result") val result: String, + @JsonProperty("message") val message: String? = null, + @JsonProperty("access_token") val accessToken: String? = null, + ) + // ------------------- data class ActivitiesResponse( val all: String?, @@ -1045,6 +1062,44 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { return simklUrlRegex.find(url)?.groupValues?.get(1) ?: "" } + override suspend fun getDevicePin(): OAuth2API.PinAuthData? { + val pinAuthResp = app.get( + "$mainUrl/oauth/pin?client_id=$clientId&redirect_uri=$appString://${redirectUrl}" + ).parsedSafe() ?: return null + + return OAuth2API.PinAuthData( + deviceCode = pinAuthResp.deviceCode, + userCode = pinAuthResp.userCode, + verificationUrl = pinAuthResp.verificationUrl, + expiresIn = pinAuthResp.expiresIn, + interval = pinAuthResp.interval + ) + } + + override suspend fun handleDeviceAuth(pinAuthData: OAuth2API.PinAuthData): Boolean { + val pinAuthResp = app.get( + "$mainUrl/oauth/pin/${pinAuthData.userCode}?client_id=$clientId" + ).parsedSafe() ?: return false + + if (pinAuthResp.accessToken != null) { + switchToNewAccount() + setKey(accountId, SIMKL_TOKEN_KEY, pinAuthResp.accessToken) + + val user = getUser() + if (user == null) { + removeKey(accountId, SIMKL_TOKEN_KEY) + switchToOldAccount() + return false + } + + setKey(accountId, SIMKL_USER_KEY, user) + registerAccount() + requireLibraryRefresh = true + return true + } + return false + } + override suspend fun handleRedirect(url: String): Boolean { val uri = url.toUri() val state = uri.getQueryParameter("state") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt index 0e8160db..e0762cc5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.result import android.content.Context +import android.graphics.Bitmap import android.util.Log import android.widget.ImageView import android.widget.TextView @@ -84,12 +85,14 @@ sealed class UiImage { ) : UiImage() data class Drawable(@DrawableRes val resId: Int) : UiImage() + data class Bitmap(val bitmap: android.graphics.Bitmap) : UiImage() } fun ImageView?.setImage(value: UiImage?, fadeIn: Boolean = true) { when (value) { is UiImage.Image -> setImageImage(value, fadeIn) is UiImage.Drawable -> setImageDrawable(value) + is UiImage.Bitmap -> setImageBitmap(value) null -> { this?.isVisible = false } @@ -107,6 +110,12 @@ fun ImageView?.setImageDrawable(value: UiImage.Drawable) { this.setImage(UiImage.Drawable(value.resId)) } +fun ImageView?.setImageBitmap(value: UiImage.Bitmap) { + if (this == null) return + this.isVisible = true + this.setImageBitmap(value.bitmap) +} + @JvmName("imgNull") fun img( url: String?, @@ -129,6 +138,10 @@ fun img(@DrawableRes drawable: Int): UiImage { return UiImage.Drawable(drawable) } +fun img(bitmap: Bitmap): UiImage { + return UiImage.Bitmap(bitmap) +} + fun txt(value: String): UiText { return UiText.DynamicString(value) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 27233525..1a6053ce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -1,8 +1,9 @@ package com.lagradost.cloudstream3.ui.settings +import android.graphics.Bitmap import android.os.Bundle import android.view.View -import android.view.View.* +import android.view.View.FOCUS_DOWN import android.view.inputmethod.EditorInfo import android.widget.TextView import androidx.annotation.UiThread @@ -21,6 +22,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.AccountManagmentBinding import com.lagradost.cloudstream3.databinding.AccountSwitchBinding import com.lagradost.cloudstream3.databinding.AddAccountInputBinding +import com.lagradost.cloudstream3.databinding.DeviceAuthBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi @@ -31,6 +33,10 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlAp import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.OAuth2API +import com.lagradost.cloudstream3.ui.result.img +import com.lagradost.cloudstream3.ui.result.setImage +import com.lagradost.cloudstream3.ui.result.setText +import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -50,9 +56,12 @@ import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.setImage +import kotlinx.coroutines.delay +import qrcode.QRCode class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.BiometricAuthCallback { companion object { @@ -133,7 +142,85 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.Biome try { when (api) { is OAuth2API -> { - api.authenticate(activity) + if (isLayout(TV or EMULATOR) && api.supportDeviceAuth && activity != null) { + val binding: DeviceAuthBinding = + DeviceAuthBinding.inflate(activity.layoutInflater, null, false) + val builder = + AlertDialog.Builder(activity) + .setView(binding.root) + + builder.setNegativeButton(R.string.cancel) { _, _ -> } + builder.setPositiveButton(R.string.auth_locally) { _, _ -> + api.authenticate(activity) + } + val dialog = builder.create() + + ioSafe { + try { + val pinCodeData = api.getDevicePin() + if (pinCodeData == null) { + activity.runOnUiThread { + showToast( + activity.getString(R.string.device_pin_error_message) + ) + } + api.authenticate(activity) + return@ioSafe + } + + val qrCodeImage = QRCode.ofRoundedSquares() + .withColor(activity.colorFromAttribute(R.attr.colorPrimary)) + .build(pinCodeData.verificationUrl) + .render().nativeImage() as Bitmap + + activity.runOnUiThread { + dialog.show() + binding.devicePinCode.setText(txt(pinCodeData.userCode)) + binding.deviceAuthMessage.setText(txt(R.string.device_pin_url_message, pinCodeData.verificationUrl)) + binding.deviceAuthQrcode.setImage( + img(qrCodeImage) + ) + } + + var expirationCounter = pinCodeData.expiresIn + + while (expirationCounter > 0) { + activity.runOnUiThread { + binding.deviceAuthValidationCounter.setText( + txt( + R.string.device_pin_counter_text, expirationCounter.div(60) , expirationCounter.rem(60) + ) + ) + } + + if (expirationCounter.rem(pinCodeData.interval) == 0 && api.handleDeviceAuth(pinCodeData)) { + activity.runOnUiThread { + showToast( + activity.getString(R.string.authenticated_user) + .format( + api.name + ) + ) + dialog.dismissSafe(activity) + } + return@ioSafe + } + delay(1000) + expirationCounter-- + } + activity.runOnUiThread { + showToast( + activity.getString(R.string.device_pin_expired_message) + ) + dialog.dismissSafe(activity) + } + } catch (e: Exception) { + logError(e) + } + } + } else { + api.authenticate(activity) + } } is InAppAuthAPI -> { diff --git a/app/src/main/res/drawable/example_qr.png b/app/src/main/res/drawable/example_qr.png new file mode 100644 index 0000000000000000000000000000000000000000..18decbac49533dcd2890ac8112305b19d05cfa11 GIT binary patch literal 46354 zcmeFa3pmtk_cxv~gJ$H^FvwvhVn_xlr-X5?90nDoCgqfBqm !=O#FGnJfjN;>Qk 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 + + + + + + + + + + + \ 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 deee5ad2..09a331ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -492,6 +492,7 @@ account Log out Log in + Auth Locally Switch account Add account Create account @@ -557,6 +558,7 @@ SDR Web Poster Image + QR Code Image Player Resolution and title Title @@ -775,4 +777,8 @@ Media Reset CloudStream Wiki + Visit %s on your smartphone or computer and enter the above code + "Can't get the device PIN code, try local authentication" + "PIN code is now expired !" + "Code expires in %1$dm %2$ds" \ No newline at end of file