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: * * ``` *
* ``` * * 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("""]*>.*?""", setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL)) private val TAG = Regex("<[^>]*>") private val EMPTY_PARAGRAPH = Regex("""\s*
""", 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 aholding 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() } }