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: v26.01.02 - 2025-12-01 - Disable notification of "rendering an XHTML document".
New: v26.01.03 - 2025-12-01 - Enhance preference menu.
- @@ -19,7 +19,7 @@
- // @noframes
- // @exclude *?streamburner=0*
- // @exclude *&streamburner=0*
-// @version 26.01.02
- +// @version 26.01.03
- // @run-at document-end
- // @grant GM_addStyle
- // @grant GM.setValue
- @@ -5566,73 +5566,7 @@
-
- // Scan for possible syndication feeds.
- if (isHtml && detectionScan) {
- console.info("📰 Greasemonkey Newspaper: Scanning for syndicated documents…");
- if (gmNotification && detectionNotification) {
- await GM.notification("Scanning for syndicated documents…", "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- }
- let subscriptions = 0,
- mimeTypes = [
- "application/activity+xml",
- "application/activitystream+xml",
- "application/atom+xml",
- "application/feed+json",
- "application/gemini+text",
- "application/pubsub+xml",
- "application/rdf+xml",
- "application/rss+json",
- "application/rss+xml",
- "application/smf+xml",
- "application/stream+xml",
- "application/twtxt+text",
- "text/twtxt+plain",
- "text/gemini"
- ];
- for (mimeType of mimeTypes) {
- results = document.head.querySelectorAll(`link[type="${mimeType}"`);
- /*
- results = document.head.queryPathAll(
- null,
- //`link[@rel="alternate" and contains(@type, "${mimeType}")]`);
- `link[@rel="alternate" and @type="${mimeType}"]`);
- */
- for (result of results) {
- let a = document.createElement("a");
- if (result.href.startsWith("feed:")) {
- result.href = result.href.replace("feed:", "http:");
- } else
- if (result.href.startsWith("itpc:")) {
- result.href = result.href.replace("itpc:", "http:");
- } else {
- }
- a.href = result.href;
- a.title = mimeType;
- a.textContent = result.title || result.href;
- a.style.color = "#eee";
- for (let i = 0; i < a.style.length; i++) {
- a.style.setProperty(
- a.style[i],
- a.style.getPropertyValue(a.style[i]),
- "important"
- );
- }
- subscriptions += 1;
- console.info(`📰 Greasemonkey Newspaper: Subscription "${result.title}" ${result.href}`);
- if (gmRegisterMenuCommand) {
- GM.registerMenuCommand(
- `📰 ${result.title}`,
- () => navigateToUri(result.href));
- }
- }
- }
- if (subscriptions) {
- console.info(`📰 Greasemonkey Newspaper: This site has ${subscriptions} subscriptions.`);
- if (detectionNotification) {
- if (gmNotification && detectionNotification) {
- await GM.notification(`This site offers ${subscriptions} subscriptions.`, "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- //GM_notification("This site has syndicated feeds.", "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- }
- }
- }
- + scanDocument();
- } else
- if (isJson) { // Attempt to parse JSON.
- console.info("📰 Greasemonkey Newspaper: Collecting JSON data…");
- @@ -8490,12 +8424,13 @@
- return await GM.getValue("font-size", 20);
- }
-
-async function setSettingValue(key, message) {
- +async function setSettingValue(title, message, key) {
- let value;
- value = prompt(message);
- value = parseInt(value);
- if (typeof value == "number") {
- await GM.setValue(key, value);
- + GM.notification(`${title} (${value})`, "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- } else {
- alert("Value must be numeric.");
- }
- @@ -10832,6 +10767,7 @@
- "a", "body", "code", ".enclosures", ".resources", "#control-bar-container",
- "#empty-feed", "#info-square"];
- if (gmGetValue && gmSetValue) {
- + //GM.unregisterMenuCommand(menuView);
- if (await GM.getValue("view-mode") == "dark") {
- await GM.setValue("view-mode", "bright");
- //mode.textContent = "Light View";
- @@ -10845,6 +10781,9 @@
- element.classList.remove("dark");
- }
- }
- + //menuView = await GM.registerMenuCommand(
- + // "🌙 Dark mode, () => toggleMode(),
- + // "T");
- } else {
- await GM.setValue("view-mode", "dark");
- //mode.textContent = "Dark View";
- @@ -10858,6 +10797,9 @@
- element.classList.add("dark");
- }
- }
- + //menuView = await GM.registerMenuCommand(
- + // "☀ Bright mode, () => toggleMode(),
- + // "T");
- }
- } else {
- if (document.querySelector("#feed .dark")) {
- @@ -11156,91 +11098,73 @@
- // playEnclosure = await getAudioEnclosureMode();
- // subscriptionHandler = await getSubscriptionHandler();
- await GM.registerMenuCommand(
- "Start setup wizard",
- () => callibratePreferences(),
- + "📡 Detect subscriptions",
- + () => scanDocument(),
- "S");
- await GM.registerMenuCommand(
- "Set font size", // 𝙵
- () => setSettingValue("font-size", "Set font size"),
- "F");
-// await GM.registerMenuCommand(
-// "Set font type", // 𐍆
-// () => setSettingValue("font-type", "Set font type (e.g. arial, sans, serif)"),
-// "T");
- await GM.registerMenuCommand(
- "Set number of items",
- () => setSettingValue("item-number", "Set a routine maximal number of items to display"),
- "E");
- await GM.registerMenuCommand(
- "Toggle view mode", () => toggleMode(),
- "T");
- + "🎛 Display preferences",
- + () => displayPreferences(),
- + "P");
- }
- })();
-
-async function callibratePreferences () {
- let preferencesBoolean = [
- {"key" : "detection-scan",
- "query" : "Do you want to enable automatic detection of feeds?"},
- {"key" : "detection-notification",
- "query" : "Do you want to enable notifications of detected feeds?"},
- {"key" : "filter-blacklist",
- "query" : "Do you want to enable filter 'blacklist'?"},
- {"key" : "filter-whitelist",
- "query" : "Do you want to enable filter 'whitelist'?"},
- {"key" : "motd",
- "query" : "Do you want to enable our special announcement?"},
- {"key" : "play-enclosure",
- "query" : "Do you want to enable playback of audible attachments?"},
- {"key" : "show-icon",
- "query" : "Do you want to enable the display of graphical icons and logos?"}
- ];
- for (let preference of preferencesBoolean) {
- let value;
- if (confirm(preference.query)) {
- value = true;
- } else {
- value = false;
- }
- await GM.setValue(preference.key, value);
- }
- let preferencesNumeric = [
- {"key" : "font-size",
- "query" : "Please. Input value (pixel) of font size."},
- {"key" : "item-number",
- "query" : "Please. Input value of routine maximal number of items to display."}
- ];
- for (let preference of preferencesNumeric) {
- let value = prompt(preference.query);
- value = parseInt(value);
- if (typeof value == "number") {
- await GM.setValue(preference.key, value);
- } else {
- alert("Preference was not changed. Value must be numeric.");
- }
- }
- let preferencesFixed = [
- {"key" : "content-mode",
- "query" : "Do you prefer to display the whole content?",
- "value" : "content-complete",
- "routine" : "content-summary"},
- {"key" : "enclosure-view",
- "query" : "Do you want to display attachments in a table?",
- "value" : "table",
- "routine" : "list"},
- {"key" : "view-mode",
- "query" : "Do you want to enable dark-mode?",
- "value" : "dark",
- "routine" : "bright"}
- ];
- for (let preference of preferencesFixed) {
- let value;
- if (confirm(preference.query)) {
- value = preference.value;
- } else {
- value = preference.routine;
- }
- await GM.setValue(preference.key, value);
- +async function displayPreferences() {
- + await GM.registerMenuCommand(
- + "📶 Automatic detection",
- + () => togglePreference("Automatic detection", "detection-scan"),
- + "A");
- + await GM.registerMenuCommand(
- + "🎫 Detection notification",
- + () => togglePreference("Detection notification", "detection-notification"),
- + "N");
- + await GM.registerMenuCommand(
- + "📈 Filter (Allow)",
- + () => togglePreference("Filter (Allow)", "filter-whitelist"),
- + "W");
- + await GM.registerMenuCommand(
- + "📉 Filter (Deny)",
- + () => togglePreference("Filter (Deny)", "filter-blacklist"),
- + "D");
- + await GM.registerMenuCommand(
- + "🖊 Font size", // 𝙵
- + () => setSettingValue("Font size", "Set font size (pixel).", "font-size"),
- + "F");
- +//await GM.registerMenuCommand(
- +// "Font type", // 𐍆
- +// () => setSettingValue("Font type", "Set font type (e.g. arial, sans, serif).", "font-type"),
- +// "T");
- + await GM.registerMenuCommand(
- + "🖼 Graphics",
- + () => togglePreference("Graphic", "show-icon"),
- + "G");
- + await GM.registerMenuCommand(
- + "📝 Items",
- + () => setSettingValue("Routine number of items", "Set a routine number of items to display.", "item-number"),
- + "N");
- +// await GM.registerMenuCommand(
- +// "💡 Mode",
- +// () => togglePreference("view-mode", "dark", "bright"),
- +// "M");
- + await GM.registerMenuCommand(
- + "📢 MOTD",
- + () => togglePreference("MOTD", "motd"),
- + "T");
- + await GM.registerMenuCommand(
- + "🎹 Playback",
- + () => togglePreference("Playback", "play-enclosure"),
- + "B");
- +}
- +
- +async function togglePreference(title, key) {
- + if (await GM.getValue(key)) {
- + message = `${title} is disabled.`;
- + value = false;
- + } else {
- + message = `${title} is enabled.`;
- + value = true;
- }
- + await GM.setValue(key, value);
- + GM.notification(message, "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- }
-
- function isListed(content, keywords) {
- @@ -11324,12 +11248,78 @@
- } while (currentDate - anchorDate < milliseconds);
- }
-
-function navigateToUri(url) {
- //window.open(url, "_blank");
- window.open(url, "_self");
-
- +function scanDocument() {
- + console.info("📰 Greasemonkey Newspaper: Scanning for syndicated documents…");
- + if (gmNotification && detectionNotification) {
- + GM.notification("Scanning for syndicated documents…", "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- + }
- + let subscriptions = 0,
- + mimeTypes = [
- + "application/activity+xml",
- + "application/activitystream+xml",
- + "application/atom+xml",
- + "application/feed+json",
- + "application/gemini+text",
- + "application/pubsub+xml",
- + "application/rdf+xml",
- + "application/rss+json",
- + "application/rss+xml",
- + "application/smf+xml",
- + "application/stream+xml",
- + "application/twtxt+text",
- + "text/twtxt+plain",
- + "text/gemini"
- + ];
- + for (let mimeType of mimeTypes) {
- + let results = document.head.querySelectorAll(`link[type="${mimeType}"`);
- + /*
- + results = document.head.queryPathAll(
- + null,
- + //`link[@rel="alternate" and contains(@type, "${mimeType}")]`);
- + `link[@rel="alternate" and @type="${mimeType}"]`);
- + */
- + for (let result of results) {
- + let a = document.createElement("a");
- + if (result.href.startsWith("feed:")) {
- + result.href = result.href.replace("feed:", "http:");
- + } else
- + if (result.href.startsWith("itpc:")) {
- + result.href = result.href.replace("itpc:", "http:");
- + } else {
- + }
- + a.href = result.href;
- + a.title = mimeType;
- + a.textContent = result.title || result.href;
- + a.style.color = "#eee";
- + for (let i = 0; i < a.style.length; i++) {
- + a.style.setProperty(
- + a.style[i],
- + a.style.getPropertyValue(a.style[i]),
- + "important"
- + );
- + }
- + subscriptions += 1;
- + console.info(`📰 Greasemonkey Newspaper: Subscription "${result.title}" ${result.href}`);
- + if (gmRegisterMenuCommand) {
- + GM.registerMenuCommand(
- + `📰 ${result.title}`,
- + //() => window.open(result.href, "_blank"));
- + () => window.open(result.href, "_self")
- + );
- + }
- + }
- + }
- + if (subscriptions) {
- + console.info(`📰 Greasemonkey Newspaper: This site has ${subscriptions} subscriptions.`);
- + if (detectionNotification) {
- + if (gmNotification && detectionNotification) {
- + GM.notification(`This site offers ${subscriptions} subscriptions.`, "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- + //GM_notification("This site has syndicated feeds.", "Greasemonkey Newspaper", "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5OwPC90ZXh0Pjwvc3ZnPgo=")
- + }
- + }
- + }
- +
- }
-
-
- function getNodeByXPath(node, query) { // FIXME
- res = document.evaluate(
- query, node, null,