Newspaper (Syndication Feed Reader)

This software renders syndication feeds as XHTML; It supports Atom Activity Streams (Friendica, Nostr, OStatus), Atom Over XMPP (Blasta, Libervia, Movim, Rivista), BitTorrent RSS, JSON Feed, OPML, RDF (DOAP, FOAF, RSS, XMPP), RSS-in-JSON, Simple Machines Forum (SMF), Sitemap, The Atom Syndication Format, and Twtxt; and it also supports navigation (RFC 5005).

Old: v25.11.12 - 2025-10-30 - Correct a fault when value of "entry.text" is empty.
New: v25.11.13 - 2025-11-01 - Fix TypeError upon policy TrustedHTML (Thank you. hacker09.); Add a navigationn bar from RSS document to Atom document; Fix appearance of content and media with RSS; and Improve selection of summary over content.

  • --- /tmp/diffy20260119-74277-pjwmfi 2026-01-19 15:37:58.431825491 +0000
  • +++ /tmp/diffy20260119-74277-bdpyqp 2026-01-19 15:37:58.432825508 +0000
  • @@ -4,6 +4,7 @@
  • // @description Native Feed Viewer; Render syndication feeds as XHTML; Supported document formats are ActivityStreams, Atom, DOAP (XMPP), FOAF, JSON Feed, Nostr, OPML, OStatus, RDF, RSS, RSS-in-JSON, Sitemap, SMF, and Twtxt; Including navigation support (RFC 5005).
  • // @author Schimon Jehudah
  • // @collaborator CY Fung
  • +// @collaborator hacker09
  • // @collaborator NotYou
  • // @homepageURL https://schapps.woodpeckersnest.eu/newspaper/
  • // @supportURL https://greasyfork.org/scripts/465932-newspaper/feedback
  • @@ -17,7 +18,7 @@
  • // @noframes
  • // @exclude *?streamburner=0*
  • // @exclude *&streamburner=0*
  • -// @version 25.11.12
  • +// @version 25.11.13
  • // @run-at document-start
  • // @grant GM.setValue
  • // @grant GM.getValue
  • @@ -94,7 +95,8 @@
  • "rss10" : "https://web.resource.org/rss/1.0/",
  • "schema" : "https://schema.org/",
  • "sitemap" : "http://www.sitemaps.org/schemas/sitemap/0.9",
  • - "smf" : "http://www.simplemachines.org/xml/recent",
  • + "smf" : "http://www.simplemachines.org/",
  • + "smr" : "http://www.simplemachines.org/xml/recent",
  • "sy" : "http://purl.org/rss/1.0/modules/syndication/",
  • "tf" : "urn:schapps:params:xml:ns:thefocus",
  • "webfeeds" : "http://webfeeds.org/rss/1.0",
  • @@ -1961,7 +1963,7 @@
  • <li><a class="recom" href="https://github.com/hanejs/hane">Hane JS</a></li>
  • <li><a class="recom" href="https://dthompson.us/projects/haunt.html">Haunt</a></li>
  • <li><a class="recom" href="https://hexo.io">Hexo</a> (<a href="https://github.com/hexojs/hexo">code</a>)</li>
  • - <li><a href="https://gohugo.io">Hugo</a></li>
  • + <li><a class="recom" href="https://gohugo.io">Hugo</a></li>
  • <li><a href="http://hyde.github.io">hyde</a></li>
  • <li><a href="http://ikiwiki.info">ikiwiki</a></li>
  • <li><a class="recom" href="https://jekyllrb.com">Jekyll</a></li>
  • @@ -2125,7 +2127,7 @@
  • <li><a class="recom" href="https://github.com/hanejs/hane">Hane JS</a></li>
  • <li><a class="recom" href="https://dthompson.us/projects/haunt.html">Haunt</a></li>
  • <li><a class="recom" href="https://hexo.io">Hexo</a> (<a href="https://github.com/hexojs/hexo">code</a>)</li>
  • - <li><a href="https://gohugo.io">Hugo</a></li>
  • + <li><a class="recom" href="https://gohugo.io">Hugo</a></li>
  • <li><a href="http://hyde.github.io">hyde</a></li>
  • <li><a href="http://ikiwiki.info">ikiwiki</a></li>
  • <li><a class="recom" href="https://jekyllrb.com">Jekyll</a></li>
  • @@ -2562,7 +2564,7 @@
  • <p>
  • <h3>#1 Mozilla</h3>
  • <h4>Playing the role of the selected (i.e. controlled) opposition of the internet since 1998.</h4>
  • - <p>Mozilla is a brand unofficially owned and controlled by Google Inc. In other words, Mozilla is the plaything and marketing toy of Google. It always has been and it always will be, as long as it pays the rent.</p>
  • + <p>Mozilla is a brand unofficially owned and controlled by Google Inc. In other words, Mozilla is the plaything and marketing toy of Gooble. It always has been and it always will be, as long as it pays the rent.</p>
  • <ul>
  • <li><a href="https://openrss.org/blog/browsers-should-bring-back-the-rss-button">Browsers removed the RSS Button and they should bring it back</a> (May 30, 2023)</li>
  • <li><a href="https://techdows.com/2018/10/mozilla-removes-rss-live-bookmarks-support-from-firefox-64.html">Mozilla removes RSS feed and Live Bookmarks support from Firefox 64</a> (October 12, 2018)</li>
  • @@ -2650,12 +2652,12 @@
  • <span class="decor"></span>
  • <h3>Syndication feeds</h3>
  • <h4>Upcoming changes as a public affair.</h4>
  • - <p>During 2010, the company refers itself by the brand "Mozilla Foundation" (the company) has made a public display (i.e. phony public suggestion and mindstorm, so called) that proposed an idiotic and impossible notion that any individual is eligible to participate in proposing changes to its products, such as "Firefox" version 3.</p>
  • - <p>To make that public display convincing, the company has made a "heatmap" page that displayed a new UI of "Firefox" against colorful numbers and figures, in order to make people to believe that the company is honest and "transparent" so called.</p>
  • + <p>During 2010, the organization refers itself by the brand "Mozilla Foundation" (the organization) has made a public display (i.e. phony public suggestion and mindstorm, so called) that proposed an idiotic and impossible notion that any individual is eligible to participate in proposing changes to its products, such as "Firefox" version 3.</p>
  • + <p>To make that public display convincing, the organization has made a "heatmap" page that displayed a new UI of "Firefox" against colorful numbers and figures, in order to make people to believe that the organization is honest and "transparent" so called.</p>
  • <p>That public heatmap show and display took place for less than just a month. That's it!?</p>
  • - <p>It is important to note that the heatmap had first accounted for 7% - 15% of activity for the feed button and a week afterwords, it accounted for only 3% - 5% and then the heatmap results have been suddenly froze.</p>
  • - <p>It is very likely that the heatmap was frozen because the statistics, albeit probably forged, were convenient to the company.</p>
  • - <p>Finally, the company has replaced the feed button by a feed menu item, and whilst:</p>
  • + <p>It is important to note that the heatmap had first accounted for 7% - 15% of activity for the feed button and a week afterwords, it accounted for only 3% - 5% and then the heatmap results have been suddenly stopped.</p>
  • + <p>It is mostly probably that the heatmap stoppage has occured because the statistics, albeit probably forged, were convenient to that organization.</p>
  • + <p>Finally, the organization has replaced the feed button by a feed menu item, and whilst:</p>
  • <ul>
  • <li>The button included a visual and active indicator and took 2 - 3 clicks to get into syndication feeds;</li>
  • <li>The new menu item had no automated indication, which requires to manually open the Bookmarks menu to check whether or not a feed is available;</li>
  • @@ -2667,9 +2669,9 @@
  • <p>Whether you are a software engineer or not, this is simple to understand that this is not an improvement in UI. It is the complete opposite of improvement.</p>
  • <span class="decor"></span>
  • <h4>Complete removal of syndication feeds.</h4>
  • - <p>On December 2018, the company has stated that the built-in feed reader was removed from a software browser branded as "Firefox" due to security concerns which were never proven. The statement is as follows:</p>
  • + <p>On December 2018, the organization has stated that the built-in feed reader was removed from a software browser branded as "Firefox" due to security concerns which were never proven. The statement is as follows:</p>
  • <p class="quote">"After reviewing the usage data and technical maintenance requirements for these features and taking into account alternative Atom/RSS feed readers already available to you, we have realized that these features have an outsized maintenance and security impact relative to their usage. Removing the feed reader and Live Bookmarks allows us to focus on features that make a greater impact." (<a href="https://support.mozilla.org/en-US/kb/feed-reader-replacements-firefox" rel="noreferrer">source</a>)</p>
  • - <p>At the same moment, the company introduced a new built-in element called "Pocket" which connects to a centralized data mining platform referred to by the same name and is publicly presented as a news and content aggregating service; in reality, it is a centralized closed-source platform, not supporting interoperability, privacy unfriendly, and is subjected to potential massive data leaks which are both a privacy and a security concern.</p>
  • + <p>At the same moment, the organization introduced a new built-in element called "Pocket" which connects to a centralized data mining platform referred to by the same name and is publicly presented as a news and content aggregating service; in reality, it is a centralized closed-source platform, not supporting interoperability, privacy unfriendly, and is subjected to potential massive data leaks which are both a privacy and a security concern.</p>
  • <span class="decor"></span>
  • <h4>Heatmap and karma: Hitting at 3% and getting 3% in return.</h4>
  • <!-- p class="cyan">This is a story about a man who was deliberately hitting at 3% and the lord has returned 3%.</p -->
  • @@ -2900,6 +2902,7 @@
  • <a href="https://joinpeertube.org/rss-en.xml">PeerTube</a>
  • <a href="https://procolix.com/feed/atom/">ProcoliX</a>
  • <a href="https://sk.ru/rss/">Новости Сколково</a>
  • + <a href="https://slackbuilds.org/rss/ChangeLog.rss">SlackBuilds.org</a>
  • <a href="https://sourceforge.net/blog/feed/atom/">SourceForge</a>
  • <a href="https://sourcehut.org/blog/index.xml">sourcehut</a>
  • <a href="https://stackexchange.com/feeds/questions">Stack Exchange</a>
  • @@ -2907,6 +2910,7 @@
  • <a href="https://supremecourt.gov">Supreme Court for the United States</a>
  • <a href="https://tailscale.com/blog/index.xml">Tailscale</a>
  • <a href="https://tpb.party/rss">The Pirate Bay</a>
  • + <a href="https://packages.slackware.com">The Slackware Linux Project</a>
  • <a href="https://timetecinc.com/blogs/blogs.atom">Timetecinc</a>
  • <a href="https://tutanota.com/blog/feed.xml">Tutanota</a>
  • <a href="https://marines.mil/RSS/">United States Marine Corps</a>
  • @@ -5070,10 +5074,16 @@
  • }
  • #not-well-formed {
  • - background: dimgrey; /* #449 */ }
  • + background: dimgrey; /* #449 */
  • +}
  • +
  • +#atom-message {
  • + background: royalblue;
  • + text-decoration: none;
  • +}
  • #xslt-message {
  • - background: darkgoldenrod; /* royalblue #2c3e50 coral */
  • + background: darkgoldenrod; /* coral royalblue #2c3e50 */
  • cursor: pointer;
  • }
  • @@ -5376,6 +5386,7 @@
  • feed.update = {};
  • let isHtml = false, isJson = false; //isXml = false isTxt = false
  • let requestResponse = request.response;
  • + //console.log(requestResponse);
  • console.log(request.getResponseHeader("Content-Type"));
  • // NOTE Binary check.
  • //if (requestResponse instanceof Blob) {
  • @@ -5392,11 +5403,14 @@
  • // Therefore, it is essential to conduct arbitrary checks.
  • // TODO Set the "graceful" system as a routine and offer the "arbitrary"
  • // system as an option.
  • - if (requestResponse. includes("{") && requestResponse. includes("}")) {
  • + if (!requestResponse.startsWith("<") &&
  • + requestResponse.includes("{") &&
  • + requestResponse.includes("}")) {
  • try { // Check whether JSON.
  • jsonFile = JSON.parse(requestResponse);
  • isJson = true;
  • - } catch {
  • -
  • + } catch (e) {
  • + console.error(e);
  • +
  • isJson = false;
  • }
  • } else {
  • @@ -5404,7 +5418,23 @@
  • }
  • if (!isJson) {
  • let domParser = new DOMParser();
  • - let htmlFile = domParser.parseFromString(requestResponse, "text/html");
  • + let htmlFile;
  • + try {
  • + htmlFile = domParser.parseFromString(requestResponse, "text/html");
  • + } catch (e) {
  • + console.warn(e);
  • + // TODO Recognize TrustedHTML, as or similarly to "securitypolicyviolation".
  • + if (e.toString().includes("TypeError") && e.toString().includes("TrustedHTML")) {
  • + // Thank you. hacker09.
  • + // https://greasyfork.org/en/discussions/development/220765#comment-469969
  • + if (window.trustedTypes && window.trustedTypes.createPolicy) {
  • + window.trustedTypes.createPolicy("default", {
  • + createHTML: (string, sink) => string
  • + });
  • + }
  • + htmlFile = domParser.parseFromString(requestResponse, "text/html");
  • + }
  • + }
  • +
  • if (htmlFile.documentElement.tagName == "HTML" &&
  • htmlFile.doctype && htmlFile.doctype.name == "html") {
  • //if (htmlFile.body && htmlFile.body.childNodes.length > 0) {
  • @@ -5693,7 +5723,7 @@
  • newDocument = renderDocument(feed);
  • urgentMessage(newDocument);
  • feedInfo(newDocument, feed);
  • - await preProcess(newDocument);
  • + await preProcess(newDocument, feed);
  • placeNewDocument(newDocument);
  • await postProcess("", "");
  • } else
  • @@ -5712,7 +5742,7 @@
  • newDocument = renderDocument(feed);
  • urgentMessage(newDocument);
  • feedInfo(newDocument, feed);
  • - await preProcess(newDocument);
  • + await preProcess(newDocument, feed);
  • placeNewDocument(newDocument);
  • await postProcess("", "");
  • }
  • @@ -5822,6 +5852,10 @@
  • feed.type = "RSS"; // RSS Syndication Feed 2.0
  • }
  • } else
  • + if (nodeSmf) {
  • + extractSmf(xmlFile, feed);
  • + feed.type = "SMF - Simple Machines Forum";
  • + } else
  • if (nodeProject) {
  • extractDoapXmpp(xmlFile, feed);
  • feed.type = "DOAP - Description of a Project (XMPP)";
  • @@ -5860,10 +5894,6 @@
  • if (nodeSitemapUrlset) {
  • extractSitemapUrlset(xmlFile, feed);
  • feed.type = "Sitemap (urlset)";
  • - } else
  • - if (nodeSmf) {
  • - extractSmf(xmlFile, feed);
  • - feed.type = "SMF - Simple Machines Forum";
  • } else {
  • // Exit.
  • return;
  • @@ -5880,7 +5910,7 @@
  • //aboutInfo(xmlFile, rdfRules);
  • urgentMessage(newDocument);
  • feedInfo(newDocument, feed);
  • - await preProcess(newDocument);
  • + await preProcess(newDocument, feed);
  • placeNewDocument(newDocument);
  • await postProcess(feed.next, feed.prev);
  • }
  • @@ -6462,7 +6492,7 @@
  • let nodeContent = nodeEntry.queryPath(xmlns.atom, "atom:content");
  • let nodeSummary = nodeEntry.queryPath(xmlns.atom, "atom:summary");
  • // if (nodeContent) {
  • - if (contentMode == "content-complete" && nodeContent) {
  • + if (contentMode == "content-complete" && nodeContent && nodeContent.textContent) {
  • entry.text = nodeContent.textContent;
  • } else // contentMode == "content-summary"
  • if (nodeSummary) {
  • @@ -6531,29 +6561,17 @@
  • }
  • entry.authors.push(entryAuthor);
  • }
  • - let entry_content;
  • - entry_content = entry.text + " " + entry.title + " " + entry.subtitle;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.text + " " + entry.title + " " + entry.subtitle;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -6619,29 +6637,17 @@
  • }
  • // TODO Entry attachments
  • // TODO Entry author
  • - let entry_content;
  • - entry_content = entry.text + " " + entry.title + " " + entry.link;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.text + " " + entry.title + " " + entry.link;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -6737,29 +6743,17 @@
  • entryAuthor.name = nodePublisher.textContent;
  • entry.authors.push(entryAuthor);
  • }
  • - let entry_content;
  • - entry_content = entry.text + " " + entry.title + " " + entry.subtitle;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.text + " " + entry.title + " " + entry.subtitle;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -6866,7 +6860,9 @@
  • let nodeDescription = nodeItem.queryPath(null, "description");
  • let nodeContentEncoded = nodeItem.queryPath(xmlns.content, "content:encoded");
  • // if (nodeContentEncoded) {
  • - if (contentMode == "content-complete" && nodeContentEncoded) {
  • + if (contentMode == "content-complete" &&
  • + nodeContentEncoded &&
  • + nodeContentEncoded.textContent) {
  • entry.text = nodeContentEncoded.textContent;
  • } else // contentMode == "content-summary"
  • if (nodeDescription && nodeDescription.textContent) {
  • @@ -6897,9 +6893,9 @@
  • }
  • entry.enclosures.push(entryEnclosure);
  • }
  • - // Media RSS
  • - let nodesMedia = nodeItem.queryPathAll(xmlns.media, "content");
  • - for (const media of nodesMedia) {
  • + // Entry enclosures (media)
  • + let nodesMediaContent = nodeItem.queryPathAll(xmlns.media, "media:content");
  • + for (const media of nodesMediaContent) {
  • let entryMedia = {};
  • if (media.getAttribute("type")) {
  • entryMedia.type = media.getAttribute("type");
  • @@ -6926,29 +6922,17 @@
  • entryAuthor.name = author.textContent;
  • entry.authors.push(entryAuthor);
  • }
  • - let entry_content;
  • - entry_content = entry.text + " " + entry.title + " " + entry.subtitle;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.text + " " + entry.title + " " + entry.subtitle;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -6986,29 +6970,17 @@
  • if (nodeLastmod) {
  • entry.date = nodeLastmod.textContent;
  • }
  • - let entry_content;
  • - entry_content = entry.title;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.title;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -7046,29 +7018,17 @@
  • if (nodeLastmod) {
  • entry.date = nodeLastmod.textContent;
  • }
  • - let entry_content;
  • - entry_content = entry.title;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.title;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -7168,29 +7128,17 @@
  • }
  • // Entry link
  • entry.link = uri;
  • - let entry_content;
  • - entry_content = entry.text + " " + entry.title;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.text + " " + entry.title;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -7220,7 +7168,7 @@
  • feed.language = nodeFeed.getAttribute("xml:lang");
  • //feed.link = nodeFeed.getAttribute("forum-url");
  • feed.version = nodeFeed.getAttribute("version");
  • - let nodesRecentPost = nodeFeed.queryPathAll(null, "recent-post");
  • + let nodesRecentPost = nodeFeed.queryPathAll(xmlns.smr, "smf:recent-post");
  • feed.count = nodesRecentPost.length;
  • if (feed.count) {
  • feed.entries = [];
  • @@ -7228,7 +7176,7 @@
  • for (const nodeRecentPost of nodesRecentPost) {
  • let entry = {};
  • // Entry date
  • - let nodeTime = nodeRecentPost.queryPath(null, "time");
  • + let nodeTime = nodeRecentPost.queryPath(xmlns.smr, "smf:time");
  • if (nodeTime) {
  • //entry.date = nodeTime.textContent;
  • entry.date = nodeTime.getAttribute("UTC");
  • @@ -7236,13 +7184,13 @@
  • entry.date = nodeTime.textContent;
  • }
  • // Entry title
  • - let nodeSubject = nodeRecentPost.queryPath(null, "subject");
  • + let nodeSubject = nodeRecentPost.queryPath(xmlns.smr, "smf:subject");
  • if (nodeSubject && nodeSubject.textContent.trim().length) {
  • // NOTE Perhaps HTML should be parsed.
  • entry.title = nodeSubject.textContent;
  • }
  • // Entry link
  • - let nodeLink = nodeRecentPost.queryPath(null, "link");
  • + let nodeLink = nodeRecentPost.queryPath(xmlns.smr, "smf:link");
  • if (nodeLink && nodeLink.textContent.length) {
  • // FIXME Ignore whitespace
  • // https://handheldgameconsoles.com/f.atom
  • @@ -7254,7 +7202,7 @@
  • // Entry content
  • // if (contentMode == "content-complete" || contentMode == "content-summary") {
  • if (contentMode != "content-title") {
  • - let nodeBody = nodeRecentPost.queryPath(null, "body");
  • + let nodeBody = nodeRecentPost.queryPath(xmlns.smr, "smf:body");
  • if (nodeBody) {
  • entry.text = nodeBody.textContent;
  • }
  • @@ -7262,49 +7210,37 @@
  • // TODO Entry attachments
  • entry.enclosures = [];
  • // Entry author
  • - let nodePoster = nodeRecentPost.queryPath(null, "poster");
  • - let nodeStarter = nodeRecentPost.queryPath(null, "starter");
  • + let nodePoster = nodeRecentPost.queryPath(xmlns.smr, "smf:poster");
  • + let nodeStarter = nodeRecentPost.queryPath(xmlns.smr, "smf:starter");
  • if (nodePoster) {
  • - if (nodePoster.queryPath(null, "name")) {
  • + if (nodePoster.queryPath(xmlns.smr, "smf:name")) {
  • entry.authors = [];
  • let entryAuthor = {};
  • - entryAuthor.name = nodePoster.queryPath(null, "name").textContent;
  • - entryAuthor.uri = nodePoster.queryPath(null, "link").textContent;
  • + entryAuthor.name = nodePoster.queryPath(xmlns.smr, "smf:name").textContent;
  • + entryAuthor.uri = nodePoster.queryPath(xmlns.smr, "smf:link").textContent;
  • entry.authors.push(entryAuthor);
  • }
  • }
  • if (nodeStarter) {
  • - if (nodeStarter.queryPath(null, "name")) {
  • + if (nodeStarter.queryPath(xmlns.smr, "smf:name")) {
  • entry.authors = [];
  • let entryAuthor = {};
  • - entryAuthor.name = nodeStarter.queryPath(null, "name").textContent;
  • - entryAuthor.uri = nodeStarter.queryPath(null, "link").textContent;
  • + entryAuthor.name = nodeStarter.queryPath(xmlns.smr, "smf:name").textContent;
  • + entryAuthor.uri = nodeStarter.queryPath(xmlns.smr, "smf:link").textContent;
  • entry.authors.push(entryAuthor);
  • }
  • }
  • - let entry_content;
  • - entry_content = entry.text + " " + entry.title + " " + entry.subtitle;
  • - entry_content = entry_content.toLowerCase();
  • + let entryContent;
  • + entryContent = entry.text + " " + entry.title + " " + entry.subtitle;
  • + entryContent = entryContent.toLowerCase();
  • entry.blacklisted = false;
  • entry.whitelisted = false;
  • if (filterBlacklist && keywordsBlacklist) {
  • if (filterWhitelist) {
  • - for (let keyword of keywordsWhitelist.split(",")) {
  • - if (keyword.length && entry_content.includes(keyword)) {
  • - entry.whitelisted = true;
  • - break;
  • - }
  • - }
  • -
  • + entry.whitelisted = isListed(entryContent, keywordsWhitelist);
  • +
  • }
  • if (!entry.whitelisted) {
  • - for (let keyword of keywordsBlacklist.split(",")) {
  • - for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • - if (keyword.length && entry_content.includes(" " + keyword + character)) {
  • - entry.blacklisted = true;
  • - break;
  • - }
  • - }
  • - }
  • -
  • + entry.blacklisted = isListed(entryContent, keywordsBlacklist);
  • +
  • }
  • }
  • feed.entries.push(entry);
  • @@ -7933,7 +7869,7 @@
  • );
  • }
  • -async function preProcess(newDocument) {
  • +async function preProcess(newDocument, feed) {
  • let infobarMessages = newDocument.createElement("div");
  • infobarMessages.id = "infobar-messages";
  • @@ -7942,11 +7878,24 @@
  • if (articlesFiltered.length) {
  • let infobarMessage = newDocument.createElement("div");
  • infobarMessages.prepend(infobarMessage);
  • - infobarMessage.textContent = `${articlesFiltered.length} articles have been filtered.`;
  • + infobarMessage.textContent = `${articlesFiltered.length} articles were filtered.`;
  • infobarMessage.id = "articles-filtered";
  • infobarMessage.className = "infobar-message";
  • }
  • + if (feed.type == "RSS 2.0" &&
  • + location.pathname == "/feed/" &&
  • + feed.generator &&
  • + feed.generator.name.length &&
  • + feed.generator.name.includes(".org/?v=")) {
  • + let infobarMessage = newDocument.createElement("a");
  • + infobarMessages.prepend(infobarMessage);
  • + infobarMessage.innerHTML = "Navigate to The Atom Syndication Format version of this document.";
  • + infobarMessage.id = "atom-message";
  • + infobarMessage.className = "infobar-message";
  • + infobarMessage.href = location.href + "atom";
  • + }
  • +
  • if (xmlStylesheet) {
  • // function stylesheetMessage(newDocument) {
  • let infobarMessage = newDocument.createElement("div");
  • @@ -8578,7 +8527,7 @@
  • switch (subscriptionHandler) {
  • case "desktop":
  • text = "Subscribe";
  • - link = `feed:`;
  • + link = "feed:";
  • break;
  • case "commafeed":
  • text = "CommaFeed";
  • @@ -8588,7 +8537,7 @@
  • break;
  • case "feedly":
  • text = "Feedly";
  • - link = `https://feedly.com/i/discover/sources/search/feed/`;
  • + link = "https://feedly.com/i/discover/sources/search/feed/";
  • break;
  • case "subtome":
  • text = "SubToMe";
  • @@ -8606,7 +8555,7 @@
  • } else {
  • // TODO Change to SubToMe
  • text = "Subscribe";
  • - link = `feed:`;
  • + link = "feed:";
  • }
  • try {
  • await GM.setValue("handler-url", link);
  • @@ -8720,7 +8669,7 @@
  • // Consider https://openuserjs.org/libs/BigTSDMB/setStyle
  • //function setNonceUponCSP() {
  • window.addEventListener("securitypolicyviolation", (e) => {
  • - // TODO Extract hash if possible
  • + // TODO Extract or forge hash if possible
  • let nonceValue = e.originalPolicy.match(/"nonce-(.*?)"/);
  • console.log(nonceValue);
  • if (nonceValue && nonceValue.length > 0) {
  • @@ -10194,6 +10143,16 @@
  • }
  • })();
  • +function isListed(content, keywords) {
  • + for (let keyword of keywords.split(",")) {
  • + for (let character of [" ", "’", "'", "-", '"', ":", ";", ",", "."]) {
  • + if (keyword.length && content.includes(" " + keyword + character)) {
  • + return true;
  • + }
  • + }
  • + }
  • +}
  • +
  • // Remove "style" attribute from all elements recursively
  • function removeStyleAttribute(element) {
  • // Remove style attribute from the current element