1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
package org.terst.feedreader.readability
/** What to do with one `evaluateJavascript` result. */
sealed interface EvalDecision {
/** Nothing more to try: this is the extraction's outcome. */
data class Done(val result: ExtractionResult) : EvalDecision
/** The page probably was not ready; inject the script again. */
data object Retry : EvalDecision
}
/**
* Decides, for one script result, whether extraction succeeded, should be
* retried, or has failed and why.
*
* Lifted out of the WebView callback it used to live inside so it can be
* exercised without a device. The bug that prompted that: a result consisting
* only of the page's cookie-consent banner was non-blank and error-free, so it
* counted as a success, was cached to `articles.extractedContent`, and was never
* reconsidered. Reported live as "reader mode just extracts cookie disclaimer".
*/
internal fun decideEvalOutcome(
article: ExtractedArticle?,
rawResult: String,
attempt: Int,
maxAttempts: Int
): EvalDecision {
val readabilityError = article?.error?.takeIf { it.isNotBlank() }
// A banner is exactly what a half-loaded page produces -- the consent script
// runs early, the article body may not be in the DOM yet -- so this is not
// usable, which routes it into the same retry the extractor already does for
// an empty result rather than being accepted.
val isConsentNotice = article != null &&
readabilityError == null &&
ConsentNotice.looksLikeConsentNotice(article.content)
val usable = article != null &&
readabilityError == null &&
!article.content.isNullOrBlank() &&
!isConsentNotice
return when {
// Cleaned on the way out rather than at the point of display: this is the one place
// every fresh extraction passes through, so the widgets never reach the screen and
// never reach `articles.extractedContent` either.
usable -> EvalDecision.Done(
ExtractionResult.Success(
article!!.copy(content = RecirculationWidgets.strip(article.content))
)
)
// Readability ran and objected: that verdict will not change.
readabilityError != null ->
EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.ReadabilityError(readabilityError)))
attempt < maxAttempts -> EvalDecision.Retry
// Every attempt came back with nothing but the consent notice, so this is
// what the page actually serves -- report it as its own failure rather
// than passing a cookie disclaimer off as the article.
isConsentNotice ->
EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.ConsentInterstitial))
article == null ->
EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.BadResult(rawResult)))
else -> EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.NoContent))
}
}
|