Prevent subsequent subscription requests if captcha, Prevent retry dialog in some captcha situations, prevent dup captchas

This commit is contained in:
Kelvin 2023-10-17 20:47:23 +02:00
parent 9d39d74be5
commit 182c88fc9e
10 changed files with 52 additions and 14 deletions

View File

@ -29,6 +29,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.engine.exceptions.PluginException 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.FeedView
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StatePolycentric
@ -76,7 +77,9 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
}).success { }).success {
setLoading(false); setLoading(false);
setPager(it); setPager(it);
}.exception<Throwable> { }
.exception<ScriptCaptchaRequiredException> { }
.exception<Throwable> {
Logger.w(TAG, "Failed to load initial videos.", it); Logger.w(TAG, "Failed to load initial videos.", it);
UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadNextPage() }); UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadNextPage() });
}; };

View File

@ -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.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler 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.ChannelFragment
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.logging.Logger 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)); _authorLinks.add(PlatformAuthorLink(it.id, it.name, it.url, it.thumbnail));
adapter.notifyItemInserted(adapter.childToParentPosition(_authorLinks.size - 1)); adapter.notifyItemInserted(adapter.childToParentPosition(_authorLinks.size - 1));
loadNext(); loadNext();
}.exceptionWithParameter<Throwable> { ex, para -> }.exception<ScriptCaptchaRequiredException> { }
.exceptionWithParameter<Throwable> { ex, para ->
Logger.w(ChannelFragment.TAG, "Failed to load results.", ex); Logger.w(ChannelFragment.TAG, "Failed to load results.", ex);
UIDialogs.toast(requireContext(), "Failed to fetch\n${para}", false) UIDialogs.toast(requireContext(), "Failed to fetch\n${para}", false)
loadNext(); loadNext();

View File

@ -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.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.TaskHandler 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.fragment.mainactivity.topbar.SearchTopBarFragment
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -86,7 +87,7 @@ class ContentSearchResultsFragment : MainFragment() {
StatePlatform.instance.searchRefresh(fragment.lifecycleScope, query, null, _sortBy, _filterValues, _enabledClientIds) StatePlatform.instance.searchRefresh(fragment.lifecycleScope, query, null, _sortBy, _filterValues, _enabledClientIds)
} }
}) })
.success { loadedResult(it); } .success { loadedResult(it); }.exception<ScriptCaptchaRequiredException> { }
.exception<Throwable> { .exception<Throwable> {
Logger.w(ChannelFragment.TAG, "Failed to load results.", it); Logger.w(ChannelFragment.TAG, "Failed to load results.", it);
UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() });

View File

@ -13,6 +13,7 @@ import com.futo.platformplayer.Settings
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.TaskHandler 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.fragment.mainactivity.topbar.SearchTopBarFragment
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
@ -56,6 +57,7 @@ class CreatorSearchResultsFragment : MainFragment() {
constructor(fragment: CreatorSearchResultsFragment, inflater: LayoutInflater): super(fragment, inflater) { constructor(fragment: CreatorSearchResultsFragment, inflater: LayoutInflater): super(fragment, inflater) {
_taskSearch = TaskHandler<String, IPager<PlatformAuthorLink>>({fragment.lifecycleScope}, { query -> StatePlatform.instance.searchChannels(query) }) _taskSearch = TaskHandler<String, IPager<PlatformAuthorLink>>({fragment.lifecycleScope}, { query -> StatePlatform.instance.searchChannels(query) })
.success { loadedResult(it); } .success { loadedResult(it); }
.exception<ScriptCaptchaRequiredException> { }
.exception<Throwable> { .exception<Throwable> {
Logger.w(ChannelFragment.TAG, "Failed to load results.", it); Logger.w(ChannelFragment.TAG, "Failed to load results.", it);
UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() });

View File

@ -98,6 +98,7 @@ class HomeFragment : MainFragment() {
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope) StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
}) })
.success { loadedResult(it); } .success { loadedResult(it); }
.exception<ScriptCaptchaRequiredException> { }
.exception<ScriptExecutionException> { .exception<ScriptExecutionException> {
Logger.w(ChannelFragment.TAG, "Plugin failure.", it); 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, UIDialogs.showDialog(context, R.drawable.ic_error_pred, "Failed to get Home\nPlugin [${it.config.name}]", it.message, null, 0,

View File

@ -18,6 +18,7 @@ class CaptchaWebViewClient : WebViewClient {
private val _pluginConfig: SourcePluginConfig?; private val _pluginConfig: SourcePluginConfig?;
private val _captchaConfig: SourcePluginCaptchaConfig; private val _captchaConfig: SourcePluginCaptchaConfig;
private var _didNotify = false;
private val _extractor: WebViewRequirementExtractor; private val _extractor: WebViewRequirementExtractor;
constructor(config: SourcePluginConfig) : super() { constructor(config: SourcePluginConfig) : super() {
@ -58,7 +59,8 @@ class CaptchaWebViewClient : WebViewClient {
return super.shouldInterceptRequest(view, request as WebResourceRequest?); return super.shouldInterceptRequest(view, request as WebResourceRequest?);
val extracted = _extractor.handleRequest(view, request); val extracted = _extractor.handleRequest(view, request);
if(extracted != null) { if(extracted != null && !_didNotify) {
_didNotify = true;
onCaptchaFinished.emit(SourceCaptchaData( onCaptchaFinished.emit(SourceCaptchaData(
extracted.cookies, extracted.cookies,
extracted.headers extracted.headers

View File

@ -643,12 +643,17 @@ class StateApp {
} }
} }
private var hasCaptchaDialog = false;
fun handleCaptchaException(client: JSClient, exception: ScriptCaptchaRequiredException) { fun handleCaptchaException(client: JSClient, exception: ScriptCaptchaRequiredException) {
Logger.w(HomeFragment.TAG, "[${client.name}] Plugin captcha required.", exception); Logger.w(HomeFragment.TAG, "[${client.name}] Plugin captcha required.", exception);
scopeOrNull?.launch(Dispatchers.Main) { scopeOrNull?.launch(Dispatchers.Main) {
if(hasCaptchaDialog)
return@launch;
hasCaptchaDialog = true;
UIDialogs.showConfirmationDialog(context, "Captcha required\nPlugin [${client.config.name}]", { UIDialogs.showConfirmationDialog(context, "Captcha required\nPlugin [${client.config.name}]", {
CaptchaActivity.showCaptcha(context, client.config, exception.url, exception.body) { CaptchaActivity.showCaptcha(context, client.config, exception.url, exception.body) {
hasCaptchaDialog = false;
StatePlugins.instance.setPluginCaptcha(client.config.id, it); StatePlugins.instance.setPluginCaptcha(client.config.id, it);
scopeOrNull?.launch(Dispatchers.IO) { scopeOrNull?.launch(Dispatchers.IO) {
try { try {
@ -659,6 +664,8 @@ class StateApp {
} }
} }
} }
}, {
hasCaptchaDialog = false;
}) })
} }
} }

View File

@ -625,9 +625,13 @@ class StatePlatform {
} }
fun hasEnabledChannelClient(url : String) : Boolean = getEnabledClients().any { it.isChannelUrl(url) }; fun hasEnabledChannelClient(url : String) : Boolean = getEnabledClients().any { it.isChannelUrl(url) };
fun getChannelClient(url : String) : IPlatformClient = getChannelClientOrNull(url) fun getChannelClient(url : String, exclude: List<String>? = null) : IPlatformClient = getChannelClientOrNull(url, exclude)
?: throw NoPlatformClientException("No client enabled that supports this channel url (${url})"); ?: 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<String>? = 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<IPlatformChannel> { fun getChannel(url: String, updateSubscriptions: Boolean = true): Deferred<IPlatformChannel> {
Logger.i(TAG, "Platform - getChannel"); Logger.i(TAG, "Platform - getChannel");
@ -638,9 +642,9 @@ class StatePlatform {
return _scope.async { getChannelLive(url, updateSubscriptions) }; return _scope.async { getChannelLive(url, updateSubscriptions) };
} }
fun getChannelContent(channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0): IPager<IPlatformContent> { fun getChannelContent(channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0, ignorePlugins: List<String>? = null): IPager<IPlatformContent> {
Logger.i(TAG, "Platform - getChannelVideos"); Logger.i(TAG, "Platform - getChannelVideos");
val baseClient = getChannelClient(channelUrl); val baseClient = getChannelClient(channelUrl, ignorePlugins);
val clientCapabilities = baseClient.getChannelCapabilities(); val clientCapabilities = baseClient.getChannelCapabilities();
val client = if(usePooledClients > 1) val client = if(usePooledClients > 1)
@ -666,11 +670,11 @@ class StatePlatform {
if(sub != null) { if(sub != null) {
val daysSinceLiveStream = sub.lastLiveStream.getNowDiffDays() val daysSinceLiveStream = sub.lastLiveStream.getNowDiffDays()
if(daysSinceLiveStream > 7) { 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); toQuery.remove(ResultCapabilities.TYPE_LIVE);
} }
if(daysSinceLiveStream > 14) { 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); toQuery.remove(ResultCapabilities.TYPE_STREAMS);
} }
} }

View File

@ -126,7 +126,7 @@ class StatePolycentric {
} }
} }
fun getChannelContent(profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1): IPager<IPlatformContent> { fun getChannelContent(profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1, ignorePlugins: List<String>? = null): IPager<IPlatformContent> {
//TODO: Currently abusing subscription concurrency for parallelism //TODO: Currently abusing subscription concurrency for parallelism
val concurrency = if (channelConcurrency == -1) Settings.instance.subscriptions.getSubscriptionsConcurrency() else channelConcurrency; val concurrency = if (channelConcurrency == -1) Settings.instance.subscriptions.getSubscriptionsConcurrency() else channelConcurrency;
val pagers = profile.ownedClaims.groupBy { it.claim.claimType }.mapNotNull { val pagers = profile.ownedClaims.groupBy { it.claim.claimType }.mapNotNull {
@ -138,7 +138,7 @@ class StatePolycentric {
return@mapNotNull null; return@mapNotNull null;
} }
return@mapNotNull StatePlatform.instance.getChannelContent(url, isSubscriptionOptimized, concurrency); return@mapNotNull StatePlatform.instance.getChannelContent(url, isSubscriptionOptimized, concurrency, ignorePlugins);
}.toTypedArray(); }.toTypedArray();
val pager = MultiChronoContentPager(pagers); val pager = MultiChronoContentPager(pagers);

View File

@ -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.IPlatformChannel
import com.futo.platformplayer.api.media.models.channels.SerializedChannel import com.futo.platformplayer.api.media.models.channels.SerializedChannel
import com.futo.platformplayer.api.media.models.contents.IPlatformContent 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.*
import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable
import com.futo.platformplayer.cache.ChannelContentCache 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.Event1
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.PluginException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.exceptions.ChannelException
import com.futo.platformplayer.findNonRuntimeException import com.futo.platformplayer.findNonRuntimeException
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
@ -230,8 +233,11 @@ class StateSubscriptions {
var finished = 0; var finished = 0;
val exceptionMap: HashMap<Subscription, Throwable> = hashMapOf(); val exceptionMap: HashMap<Subscription, Throwable> = hashMapOf();
val concurrency = Settings.instance.subscriptions.getSubscriptionsConcurrency(); val concurrency = Settings.instance.subscriptions.getSubscriptionsConcurrency();
val failedPlugins = arrayListOf<String>();
for (sub in getSubscriptions().filter { StatePlatform.instance.hasEnabledChannelClient(it.channel.url) }) { for (sub in getSubscriptions().filter { StatePlatform.instance.hasEnabledChannelClient(it.channel.url) }) {
tasks.add(_subscriptionsPool.submit<Pair<Subscription, IPager<IPlatformContent>?>> { tasks.add(_subscriptionsPool.submit<Pair<Subscription, IPager<IPlatformContent>?>> {
val toIgnore = synchronized(failedPlugins){ failedPlugins.toList() };
var polycentricProfile : PolycentricCache.CachedPolycentricProfile? = null; var polycentricProfile : PolycentricCache.CachedPolycentricProfile? = null;
val getProfileTime = measureTimeMillis { val getProfileTime = measureTimeMillis {
try { try {
@ -258,9 +264,9 @@ class StateSubscriptions {
val time = measureTimeMillis { val time = measureTimeMillis {
val profile = polycentricProfile?.profile val profile = polycentricProfile?.profile
pager = if (profile != null) pager = if (profile != null)
StatePolycentric.instance.getChannelContent(profile, true, concurrency) StatePolycentric.instance.getChannelContent(profile, true, concurrency, toIgnore)
else else
StatePlatform.instance.getChannelContent(sub.channel.url, true, concurrency); StatePlatform.instance.getChannelContent(sub.channel.url, true, concurrency, toIgnore);
if (cacheScope != null) if (cacheScope != null)
pager = ChannelContentCache.cachePagerResults(cacheScope, pager) { pager = ChannelContentCache.cachePagerResults(cacheScope, pager) {
@ -276,12 +282,22 @@ class StateSubscriptions {
); );
} }
catch(ex: Throwable) { catch(ex: Throwable) {
Logger.e(TAG, "Subscription [${sub.channel.name}] failed", ex);
finished++; finished++;
onProgress?.invoke(finished, tasks.size); onProgress?.invoke(finished, tasks.size);
val channelEx = ChannelException(sub.channel, ex); val channelEx = ChannelException(sub.channel, ex);
synchronized(exceptionMap) { synchronized(exceptionMap) {
exceptionMap.put(sub, channelEx); 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) if(!withCacheFallback)
throw channelEx; throw channelEx;
else { else {