diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt index 0ca2946b..fa49c67e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt @@ -29,6 +29,7 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.main.FeedView import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.states.StatePolycentric @@ -76,7 +77,9 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment { }).success { setLoading(false); setPager(it); - }.exception { + } + .exception { } + .exception { Logger.w(TAG, "Failed to load initial videos.", it); UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadNextPage() }); }; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt index e3cf3061..42e52e7f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt @@ -14,6 +14,7 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger @@ -52,7 +53,8 @@ class ChannelListFragment : Fragment, IChannelTabFragment { _authorLinks.add(PlatformAuthorLink(it.id, it.name, it.url, it.thumbnail)); adapter.notifyItemInserted(adapter.childToParentPosition(_authorLinks.size - 1)); loadNext(); - }.exceptionWithParameter { ex, para -> + }.exception { } + .exceptionWithParameter { ex, para -> Logger.w(ChannelFragment.TAG, "Failed to load results.", ex); UIDialogs.toast(requireContext(), "Failed to fetch\n${para}", false) loadNext(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt index 035ff8ed..8603d76f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -15,6 +15,7 @@ import com.futo.platformplayer.api.media.models.ResultCapabilities import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment import com.futo.platformplayer.views.FeedStyle import kotlinx.coroutines.Dispatchers @@ -86,7 +87,7 @@ class ContentSearchResultsFragment : MainFragment() { StatePlatform.instance.searchRefresh(fragment.lifecycleScope, query, null, _sortBy, _filterValues, _enabledClientIds) } }) - .success { loadedResult(it); } + .success { loadedResult(it); }.exception { } .exception { Logger.w(ChannelFragment.TAG, "Failed to load results.", it); UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt index ab58f63f..03398a57 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt @@ -13,6 +13,7 @@ import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment import com.futo.platformplayer.views.FeedStyle @@ -56,6 +57,7 @@ class CreatorSearchResultsFragment : MainFragment() { constructor(fragment: CreatorSearchResultsFragment, inflater: LayoutInflater): super(fragment, inflater) { _taskSearch = TaskHandler>({fragment.lifecycleScope}, { query -> StatePlatform.instance.searchChannels(query) }) .success { loadedResult(it); } + .exception { } .exception { Logger.w(ChannelFragment.TAG, "Failed to load results.", it); UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt index 21e7d20c..80d6c376 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt @@ -98,6 +98,7 @@ class HomeFragment : MainFragment() { StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope) }) .success { loadedResult(it); } + .exception { } .exception { Logger.w(ChannelFragment.TAG, "Plugin failure.", it); UIDialogs.showDialog(context, R.drawable.ic_error_pred, "Failed to get Home\nPlugin [${it.config.name}]", it.message, null, 0, diff --git a/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt b/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt index ec8be8a9..bf5abc50 100644 --- a/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt +++ b/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt @@ -18,6 +18,7 @@ class CaptchaWebViewClient : WebViewClient { private val _pluginConfig: SourcePluginConfig?; private val _captchaConfig: SourcePluginCaptchaConfig; + private var _didNotify = false; private val _extractor: WebViewRequirementExtractor; constructor(config: SourcePluginConfig) : super() { @@ -58,7 +59,8 @@ class CaptchaWebViewClient : WebViewClient { return super.shouldInterceptRequest(view, request as WebResourceRequest?); val extracted = _extractor.handleRequest(view, request); - if(extracted != null) { + if(extracted != null && !_didNotify) { + _didNotify = true; onCaptchaFinished.emit(SourceCaptchaData( extracted.cookies, extracted.headers 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 1711edbc..f7b3dbe5 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -643,12 +643,17 @@ class StateApp { } } + private var hasCaptchaDialog = false; fun handleCaptchaException(client: JSClient, exception: ScriptCaptchaRequiredException) { Logger.w(HomeFragment.TAG, "[${client.name}] Plugin captcha required.", exception); scopeOrNull?.launch(Dispatchers.Main) { + if(hasCaptchaDialog) + return@launch; + hasCaptchaDialog = true; 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 { @@ -659,6 +664,8 @@ class StateApp { } } } + }, { + hasCaptchaDialog = false; }) } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 549f3ef3..31ca3ff6 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -625,9 +625,13 @@ class StatePlatform { } fun hasEnabledChannelClient(url : String) : Boolean = getEnabledClients().any { it.isChannelUrl(url) }; - fun getChannelClient(url : String) : IPlatformClient = getChannelClientOrNull(url) + fun getChannelClient(url : String, exclude: List? = null) : IPlatformClient = getChannelClientOrNull(url, exclude) ?: throw NoPlatformClientException("No client enabled that supports this channel url (${url})"); - fun getChannelClientOrNull(url : String) : IPlatformClient? = getEnabledClients().find { it.isChannelUrl(url) }; + fun getChannelClientOrNull(url : String, exclude: List? = null) : IPlatformClient? = + if(exclude == null) + getEnabledClients().find { it.isChannelUrl(url) } + else + getEnabledClients().find { !exclude.contains(it.id) && it.isChannelUrl(url) }; fun getChannel(url: String, updateSubscriptions: Boolean = true): Deferred { Logger.i(TAG, "Platform - getChannel"); @@ -638,9 +642,9 @@ class StatePlatform { return _scope.async { getChannelLive(url, updateSubscriptions) }; } - fun getChannelContent(channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0): IPager { + fun getChannelContent(channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0, ignorePlugins: List? = null): IPager { Logger.i(TAG, "Platform - getChannelVideos"); - val baseClient = getChannelClient(channelUrl); + val baseClient = getChannelClient(channelUrl, ignorePlugins); val clientCapabilities = baseClient.getChannelCapabilities(); val client = if(usePooledClients > 1) @@ -666,11 +670,11 @@ class StatePlatform { if(sub != null) { val daysSinceLiveStream = sub.lastLiveStream.getNowDiffDays() if(daysSinceLiveStream > 7) { - Logger.i(TAG, "Subscription [${channelUrl}] Last livestream > 7 days, skipping live streams [${daysSinceLiveStream} days ago]"); + Logger.i(TAG, "Subscription [${sub.channel.name}:${channelUrl}] Last livestream > 7 days, skipping live streams [${daysSinceLiveStream} days ago]"); toQuery.remove(ResultCapabilities.TYPE_LIVE); } if(daysSinceLiveStream > 14) { - Logger.i(TAG, "Subscription [${channelUrl}] Last livestream > 15 days, skipping streams [${daysSinceLiveStream} days ago]"); + Logger.i(TAG, "Subscription [${sub.channel.name}:${channelUrl}] Last livestream > 15 days, skipping streams [${daysSinceLiveStream} days ago]"); toQuery.remove(ResultCapabilities.TYPE_STREAMS); } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt index dd6b70fa..18e262c9 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt @@ -126,7 +126,7 @@ class StatePolycentric { } } - fun getChannelContent(profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1): IPager { + fun getChannelContent(profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1, ignorePlugins: List? = null): IPager { //TODO: Currently abusing subscription concurrency for parallelism val concurrency = if (channelConcurrency == -1) Settings.instance.subscriptions.getSubscriptionsConcurrency() else channelConcurrency; val pagers = profile.ownedClaims.groupBy { it.claim.claimType }.mapNotNull { @@ -138,7 +138,7 @@ class StatePolycentric { return@mapNotNull null; } - return@mapNotNull StatePlatform.instance.getChannelContent(url, isSubscriptionOptimized, concurrency); + return@mapNotNull StatePlatform.instance.getChannelContent(url, isSubscriptionOptimized, concurrency, ignorePlugins); }.toTypedArray(); val pager = MultiChronoContentPager(pagers); 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 e5236e78..b11cf24e 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt @@ -5,6 +5,8 @@ import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.channels.SerializedChannel import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.structures.* import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable import com.futo.platformplayer.cache.ChannelContentCache @@ -12,6 +14,7 @@ import com.futo.platformplayer.constructs.Event0 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.exceptions.ChannelException import com.futo.platformplayer.findNonRuntimeException import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile @@ -230,8 +233,11 @@ class StateSubscriptions { var finished = 0; val exceptionMap: HashMap = hashMapOf(); val concurrency = Settings.instance.subscriptions.getSubscriptionsConcurrency(); + val failedPlugins = arrayListOf(); for (sub in getSubscriptions().filter { StatePlatform.instance.hasEnabledChannelClient(it.channel.url) }) { tasks.add(_subscriptionsPool.submit?>> { + val toIgnore = synchronized(failedPlugins){ failedPlugins.toList() }; + var polycentricProfile : PolycentricCache.CachedPolycentricProfile? = null; val getProfileTime = measureTimeMillis { try { @@ -258,9 +264,9 @@ class StateSubscriptions { val time = measureTimeMillis { val profile = polycentricProfile?.profile pager = if (profile != null) - StatePolycentric.instance.getChannelContent(profile, true, concurrency) + StatePolycentric.instance.getChannelContent(profile, true, concurrency, toIgnore) else - StatePlatform.instance.getChannelContent(sub.channel.url, true, concurrency); + StatePlatform.instance.getChannelContent(sub.channel.url, true, concurrency, toIgnore); if (cacheScope != null) pager = ChannelContentCache.cachePagerResults(cacheScope, pager) { @@ -276,12 +282,22 @@ class StateSubscriptions { ); } catch(ex: Throwable) { + Logger.e(TAG, "Subscription [${sub.channel.name}] failed", ex); finished++; onProgress?.invoke(finished, tasks.size); val channelEx = ChannelException(sub.channel, ex); synchronized(exceptionMap) { exceptionMap.put(sub, channelEx); } + if(ex is ScriptCaptchaRequiredException) { + 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"); + failedPlugins.add(ex.config.id); + } + } + } if(!withCacheFallback) throw channelEx; else {