summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/readability/EvalDecision.kt
blob: d0bd1cd7cc3cfc4ea3d343f7c5d24c6cfaf2ae54 (plain)
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 = ArticleLitter.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))
    }
}