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.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<Throwable> {
}
.exception<ScriptCaptchaRequiredException> { }
.exception<Throwable> {
Logger.w(TAG, "Failed to load initial videos.", it);
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.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<Throwable> { ex, para ->
}.exception<ScriptCaptchaRequiredException> { }
.exceptionWithParameter<Throwable> { ex, para ->
Logger.w(ChannelFragment.TAG, "Failed to load results.", ex);
UIDialogs.toast(requireContext(), "Failed to fetch\n${para}", false)
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.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<ScriptCaptchaRequiredException> { }
.exception<Throwable> {
Logger.w(ChannelFragment.TAG, "Failed to load results.", it);
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.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<String, IPager<PlatformAuthorLink>>({fragment.lifecycleScope}, { query -> StatePlatform.instance.searchChannels(query) })
.success { loadedResult(it); }
.exception<ScriptCaptchaRequiredException> { }
.exception<Throwable> {
Logger.w(ChannelFragment.TAG, "Failed to load results.", it);
UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() });

View file

@ -98,6 +98,7 @@ class HomeFragment : MainFragment() {
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
})
.success { loadedResult(it); }
.exception<ScriptCaptchaRequiredException> { }
.exception<ScriptExecutionException> {
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,

View file

@ -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

View file

@ -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;
})
}
}

View file

@ -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<String>? = 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<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> {
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<IPlatformContent> {
fun getChannelContent(channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0, ignorePlugins: List<String>? = null): IPager<IPlatformContent> {
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);
}
}

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
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);

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.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<Subscription, Throwable> = hashMapOf();
val concurrency = Settings.instance.subscriptions.getSubscriptionsConcurrency();
val failedPlugins = arrayListOf<String>();
for (sub in getSubscriptions().filter { StatePlatform.instance.hasEnabledChannelClient(it.channel.url) }) {
tasks.add(_subscriptionsPool.submit<Pair<Subscription, IPager<IPlatformContent>?>> {
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 {