diff --git a/app/src/main/assets/devportal/dev_bridge.js b/app/src/main/assets/devportal/dev_bridge.js index 57f0f276..8186c8ae 100644 --- a/app/src/main/assets/devportal/dev_bridge.js +++ b/app/src/main/assets/devportal/dev_bridge.js @@ -217,6 +217,9 @@ function pluginUpdateTestPlugin(config) { } function pluginLoginTestPlugin() { return syncGET("/plugin/loginTestPlugin", {}); +}//captchaLoginTestPlugin +function pluginCaptchaTestPlugin(url, html) { + return syncPOST("/plugin/captchaTestPlugin?url=" + url, {}, html); } function pluginLogoutTestPlugin() { return syncGET("/plugin/logoutTestPlugin", {}); diff --git a/app/src/main/assets/devportal/index.html b/app/src/main/assets/devportal/index.html index 17bce7d5..7072e893 100644 --- a/app/src/main/assets/devportal/index.html +++ b/app/src/main/assets/devportal/index.html @@ -681,6 +681,9 @@ }); }, 1000); }, + captchaTestPlugin() { + captchaLoginTestPlugin(); + }, logoutTestPlugin() { pluginLogoutTestPlugin(); }, @@ -838,6 +841,12 @@ this.Testing.lastResultError = ""; } catch(ex) { + if(ex.plugin_type == "CaptchaRequiredException") { + let shouldCaptcha = confirm("Do you want to request captcha?"); + if(shouldCaptcha) { + pluginCaptchaTestPlugin(ex.url, ex.body); + } + } console.error("Failed to run test for " + req.title, ex); this.Testing.lastResult = "" if(ex.message) diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index e7337715..fcdb7804 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -72,6 +72,11 @@ class CaptchaRequiredException extends Error { this.body = body; } } +class CriticalException extends ScriptException { + constructor(msg) { + super("CriticalException", msg); + } +} class UnavailableException extends ScriptException { constructor(msg) { super("UnavailableException", msg); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt index 417f5b2e..9e5ffa06 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt @@ -7,6 +7,7 @@ import com.futo.platformplayer.api.media.models.comments.IPlatformComment import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.states.StateApp import java.util.* class DevJSClient : JSClient { @@ -24,6 +25,10 @@ class DevJSClient : JSClient { _auth = auth; _captcha = captcha; this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5); + + onCaptchaException.subscribe { client, captcha -> + StateApp.instance.handleCaptchaException(client, captcha); + } } //TODO: Misisng auth/captcha pass on purpose? constructor(context: Context, descriptor: SourcePluginDescriptor, script: String, auth: SourceAuth? = null, captcha: SourceCaptchaData? = null, savedState: String? = null, devID: String? = null): super(context, descriptor, savedState, script) { @@ -31,6 +36,10 @@ class DevJSClient : JSClient { _auth = auth; _captcha = captcha; this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5); + + onCaptchaException.subscribe { client, captcha -> + StateApp.instance.handleCaptchaException(client, captcha); + } } fun setCaptcha(captcha: SourceCaptchaData? = null) { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt index 7278c91b..de249649 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt @@ -68,7 +68,12 @@ class JSHttpClient : ManagedHttpClient { if(cookiesToApply.size > 0) { val cookieString = cookiesToApply.map { it.key + "=" + it.value }.joinToString("; "); - request.headers["Cookie"] = cookieString; + + val existingCookies = request.headers["Cookie"]; + if(!existingCookies.isNullOrEmpty()) + request.headers["Cookie"] = existingCookies.trim(';') + "; " + cookieString; + else + request.headers["Cookie"] = cookieString; } //printTestCode(request.url, request.body, auth.headers, cookieString, request.headers.filter { !auth.headers.containsKey(it.key) }); } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt index 833b26f2..307c41b8 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt @@ -69,9 +69,17 @@ abstract class MultiRefreshPager: IRefreshPager, IPager { if(pagerToAdd == null) { if(toReplacePager != null && toReplacePager is PlaceholderPager && error != null) { val pluginId = toReplacePager.placeholderFactory.invoke().id?.pluginId ?: ""; - _currentPager = PlaceholderPager(5) { + + _pagersReusable.add((PlaceholderPager(5) { return@PlaceholderPager PlatformContentPlaceholder(pluginId, error) - } as IPager; + } as IPager).asReusable()); + _currentPager = recreatePager(getCurrentSubPagers()); + + if(_currentPager is MultiParallelPager<*>) + runBlocking { (_currentPager as MultiParallelPager).initialize(); }; + else if(_currentPager is MultiPager<*>) + (_currentPager as MultiPager).initialize() + onPagerChanged.emit(_currentPager); } return; diff --git a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt index c8e1e471..bd8a3619 100644 --- a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt +++ b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt @@ -1,6 +1,7 @@ package com.futo.platformplayer.developer import android.content.Context +import com.futo.platformplayer.activities.CaptchaActivity import com.futo.platformplayer.activities.LoginActivity import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.server.HttpContext @@ -201,6 +202,28 @@ class DeveloperEndpoints(private val context: Context) { context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") } } + @HttpPOST("/plugin/captchaTestPlugin") + fun pluginCaptchaTestPlugin(context: HttpContext) { + val config = _testPlugin?.config as SourcePluginConfig; + val url = context.query.get("url") + val html = context.readContentString(); + try { + val captchaConfig = config.captcha; + if(captchaConfig == null) { + context.respondCode(403, "This plugin doesn't support captcha"); + return; + } + CaptchaActivity.showCaptcha(StateApp.instance.context, config, url, html) { + _testPluginVariables.clear(); + _testPlugin = V8Plugin(StateApp.instance.context, config, null, JSHttpClient(null, null, it), JSHttpClient(null, null, it)); + + }; + context.respondCode(200, "Captcha started"); + } + catch(ex: Throwable) { + context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + } + } @HttpGET("/plugin/loginTestPlugin") fun pluginLoginTestPlugin(context: HttpContext) { val config = _testPlugin?.config as SourcePluginConfig; diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 310f8ee8..a2caf747 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -298,6 +298,7 @@ class V8Plugin { private fun throwExceptionFromV8(config: IV8PluginConfig, pluginType: String, msg: String, innerEx: Exception? = null, stack: String? = null, code: String? = null) { when(pluginType) { "ScriptException" -> throw ScriptException(config, msg, innerEx, stack, code); + "CriticalException" -> throw ScriptCriticalException(config, msg, innerEx, stack, code); "AgeException" -> throw ScriptAgeException(config, msg, innerEx, stack, code); "UnavailableException" -> throw ScriptUnavailableException(config, msg, innerEx, stack, code); "ScriptExecutionException" -> throw ScriptExecutionException(config, msg, innerEx, stack, code); diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt new file mode 100644 index 00000000..6581ec25 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +open class ScriptCriticalException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { + + + + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + return ScriptCriticalException(config, obj.getOrThrow(config, "message", "ScriptCriticalException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt index 4c7f6deb..50fbe05d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -258,11 +258,17 @@ class SourceDetailFragment : MainFragment() { } } + val clientIfExists = StatePlugins.instance.getPlugin(config.id); groups.add( BigButtonGroup(c, "Management", BigButton(c, "Uninstall", "Removes the plugin from the app", R.drawable.ic_block) { uninstallSource(); - }.withBackground(R.drawable.background_big_button_red) + }.withBackground(R.drawable.background_big_button_red), + if(clientIfExists?.captchaEncrypted != null) + BigButton(c, "Delete Captcha", "Deletes captcha for this plugin", R.drawable.ic_block) { + clientIfExists?.updateCaptcha(null); + }.withBackground(R.drawable.background_big_button_red) + else null ) ) diff --git a/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt b/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt index 6f02c21d..b442508a 100644 --- a/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt +++ b/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.others import android.net.Uri import android.webkit.* +import com.futo.platformplayer.BuildConfig import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.constructs.Event1 @@ -43,6 +44,8 @@ class LoginWebViewClient : WebViewClient { private var urlFound = false; override fun onPageFinished(view: WebView?, url: String?) { + if(BuildConfig.DEBUG) + Logger.i(TAG, "Login Url Page: " + url); super.onPageFinished(view, url); onPageLoaded.emit(view, url); } @@ -56,11 +59,29 @@ class LoginWebViewClient : WebViewClient { return null; } + var completionUrlExcludeQuery = false + var completionUrlToCheck = if(urlFound) null else _authConfig.completionUrl; + if(completionUrlToCheck != null) { + if(completionUrlToCheck.endsWith("?*")) { + completionUrlToCheck = completionUrlToCheck.substring(0, completionUrlToCheck.length - 2); + completionUrlExcludeQuery = true; + } + } + + val domain = request.url.host; val domainLower = request.url.host?.lowercase(); + val urlString = request.url.toString(); if(_authConfig.completionUrl == null) urlFound = true; - else urlFound = urlFound || request.url == Uri.parse(_authConfig.completionUrl); + else urlFound = urlFound || ( + if(completionUrlExcludeQuery) + (if(urlString.contains("?")) + urlString.substring(0, urlString.indexOf("?")) == completionUrlToCheck + else urlString == completionUrlToCheck) + else + request.url == Uri.parse(_authConfig.completionUrl) + ); //HEADERS if(domainLower != null) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 6d6216a2..0545d2fa 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -29,6 +29,7 @@ import com.futo.platformplayer.activities.CaptchaActivity import com.futo.platformplayer.activities.IWithResultLauncher import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient import com.futo.platformplayer.background.BackgroundWorker @@ -672,13 +673,20 @@ class StateApp { UIDialogs.showConfirmationDialog(context, "Captcha required\nPlugin [${client.config.name}]", { CaptchaActivity.showCaptcha(context, client.config, exception.url, exception.body) { hasCaptchaDialog = false; - StatePlugins.instance.setPluginCaptcha(client.config.id, it); - scopeOrNull?.launch(Dispatchers.IO) { - try { - StatePlatform.instance.reloadClient(context, client.config.id); - } catch (e: Throwable) { - Logger.e(SourceDetailFragment.TAG, "Failed to reload client.", e) - return@launch; + + if(client is DevJSClient) { + client.setCaptcha(it); + client.recreate(context); + } + else { + StatePlugins.instance.setPluginCaptcha(client.config.id, it); + scopeOrNull?.launch(Dispatchers.IO) { + try { + StatePlatform.instance.reloadClient(context, client.config.id); + } catch (e: Throwable) { + Logger.e(SourceDetailFragment.TAG, "Failed to reload client.", e) + return@launch; + } } } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt index 7721cb60..ed57be6a 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt @@ -16,6 +16,7 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException +import com.futo.platformplayer.engine.exceptions.ScriptCriticalException import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.findNonRuntimeException import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile @@ -320,7 +321,16 @@ class StateSubscriptions { synchronized(failedPlugins) { //Fail all subscription calls to plugin if it has a captcha issue if(ex.config is SourcePluginConfig && !failedPlugins.contains(ex.config.id)) { - Logger.w(TAG, "Subscriptions fetch ignoring plugin [${ex.config.name}] due to Captcha"); + Logger.w(TAG, "Subscriptionsgnoring plugin [${ex.config.name}] due to Captcha"); + failedPlugins.add(ex.config.id); + } + } + } + else if(ex is ScriptCriticalException) { + synchronized(failedPlugins) { + //Fail all subscription calls to plugin if it has a critical issue + if(ex.config is SourcePluginConfig && !failedPlugins.contains(ex.config.id)) { + Logger.w(TAG, "Subscriptions ignoring plugin [${ex.config.name}] due to critical exception:\n" + ex.message); failedPlugins.add(ex.config.id); } } diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt index 66d3cd67..72d970e8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt @@ -14,13 +14,13 @@ class BigButtonGroup : LinearLayout { _header = findViewById(R.id.header_title); _buttons = findViewById(R.id.buttons); } - constructor(context: Context, header: String, vararg buttons: BigButton) : super(context) { + constructor(context: Context, header: String, vararg buttons: BigButton?) : super(context) { inflate(context, R.layout.big_button_group, this); _header = findViewById(R.id.header_title); _buttons = findViewById(R.id.buttons); _header.text = header; - for(button in buttons) + for(button in buttons.filterNotNull()) _buttons.addView(button); } diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index d05a9591..4ffd2a48 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit d05a959174bcceb616c9f42043466e9e1258f519 +Subproject commit 4ffd2a48c7ebed2c8512e092a6bae4b5447aff34