summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/readability/PreferredSourceBadge.kt
blob: 4ea1370986b157a73fdefe6dd77382d19e1a4a15 (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
69
package org.terst.feedreader.readability

/**
 * Removes the "Add as a preferred source on Google" badge publishers put beside the byline.
 *
 * Reported live on a West Hawaii Today article: "remove giant Google icon". Giant because the
 * page caps it with its own stylesheet -- `.preferred_source_badge { max-width: 120px }` -- and
 * the reader drops the page's CSS, so what is a small badge on the site becomes a full-width
 * image in the reader, above the first paragraph.
 *
 * The signal is the link's address, not the class or the image filename, and that is forced
 * rather than chosen. Confirmed by running the vendored Readability.js over the real page: the
 * wrapper's `preferred_source_badge` class does not survive extraction at all -- the only class
 * left in the output is Readability's own -- so a class rule would match nothing. What does
 * survive is the anchor, intact:
 *
 * ```
 * <p><a href="https://www.google.com/preferences/source?q=westhawaiitoday.com" target="_blank"
 *      rel="noopener"> <img src=".../google_preferred_source_badge_light_en.png"
 *      alt="Add as a preferred source on Google"> </a> </p>
 * ```
 *
 * The address is Google's own and identical for every publisher in the programme; the image
 * filename sits under the site's theme directory and is not. So matching the address is both the
 * only thing available and the one that generalises past this one newspaper.
 *
 * A link with real words in it is never touched, which is the same safety valve [ConsentNotice]
 * and [RecirculationWidgets] use: an article may legitimately link to that page and say so in
 * writing, and that sentence is the author's, not litter. Only a link whose entire content is an
 * image goes.
 */
object PreferredSourceBadge {

    /** Google's address for the programme. Not the badge image, which is per-publisher. */
    private const val BADGE_LINK = "google.com/preferences/source"

    private val ANCHOR = Regex("""<a\b[^>]*>.*?</a\s*>""", setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL))
    private val TAG = Regex("<[^>]*>")
    private val EMPTY_PARAGRAPH = Regex("""<p>\s*</p>""", RegexOption.IGNORE_CASE)

    /** [html] with any preferred-source badge removed. */
    fun strip(html: String?): String? =
        if (html.isNullOrBlank() || !html.contains(BADGE_LINK, ignoreCase = true)) {
            html
        } else {
            removeBadges(html)
        }

    private fun removeBadges(html: String): String {
        val withoutBadges = ANCHOR.replace(html) { match ->
            if (isBadge(match.value)) "" else match.value
        }
        // The badge is normally the only thing in its paragraph, and a <p> holding nothing but
        // the whitespace around the image it used to wrap still takes up a line on the screen.
        return EMPTY_PARAGRAPH.replace(withoutBadges, "")
    }

    /**
     * A badge is a link to the programme whose whole content is an image.
     *
     * Both halves are required. The address alone would take a sentence linking to the page with
     * it; "contains an image" alone would take every photo credit on the site.
     */
    private fun isBadge(anchor: String): Boolean {
        val opening = anchor.substringBefore('>')
        if (!opening.contains(BADGE_LINK, ignoreCase = true)) return false
        return TAG.replace(anchor, "").isBlank()
    }
}