From 0785b167adb72b610f0337bd156b224aaefe7bce Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Tue, 9 Jul 2024 19:17:40 +0300 Subject: [PATCH 01/36] Improve the handling of subscription hyperlinks. --- script/postprocess.js | 11 +++++++++-- xsl/atom_as_xhtml.xsl | 12 ++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/script/postprocess.js b/script/postprocess.js index dc37784..67e8f45 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -1,10 +1,17 @@ window.onload = function(){ - // Convert ISO8601 To UTC/ + // Fix button follow + let follow = document.querySelector('#follow'); + feedUrl = location.href.replace(/^https?:/, 'feed:'); + follow.href = feedUrl; + follow.onclick = window.open(feedUrl, "_self"); + // Fix button subtome + document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds='+location.href; + // Convert ISO8601 To UTC for (let element of document.querySelectorAll('#articles > ul > li > div > h4, #feed > #header > h2#subtitle.date')) { let timeStamp = new Date(element.textContent); element.textContent = timeStamp.toUTCString(); } - // Parse Markdown/ + // Parse Markdown for (let element of document.querySelectorAll('#articles > ul > li > div > p')) { let markDown = element.textContent element.innerHTML = marked.parse(markDown); diff --git a/xsl/atom_as_xhtml.xsl b/xsl/atom_as_xhtml.xsl index c59b7b7..02ee0d5 100644 --- a/xsl/atom_as_xhtml.xsl +++ b/xsl/atom_as_xhtml.xsl @@ -64,18 +64,18 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
- + Follow - - - https://www.subtome.com/#/subscribe?feeds= + + + javascript:location.href='https://www.subtome.com/#/subscribe?feeds='+location.href; - + ( function(btn){ var z=document.createElement('script'); -- 2.47.2 From 7712b7d9b82481f0d2b5327b52a8c8a7dd34c4fe Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Tue, 9 Jul 2024 19:24:23 +0300 Subject: [PATCH 02/36] JS: Fix an unintended usage of event click. --- script/postprocess.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/postprocess.js b/script/postprocess.js index 67e8f45..7b6fed0 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -3,7 +3,9 @@ window.onload = function(){ let follow = document.querySelector('#follow'); feedUrl = location.href.replace(/^https?:/, 'feed:'); follow.href = feedUrl; - follow.onclick = window.open(feedUrl, "_self"); + follow.addEventListener ('click', function() { + window.open(feedUrl, "_self"); + }); // Fix button subtome document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds='+location.href; // Convert ISO8601 To UTC -- 2.47.2 From 11ee189748d3069ce83a1bc0adfea80faf4e6fa5 Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Tue, 9 Jul 2024 19:37:33 +0300 Subject: [PATCH 03/36] Improve software reference page. --- css/stylesheet.css | 4 ++-- script/postprocess.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/css/stylesheet.css b/css/stylesheet.css index 9780fc2..76256b8 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -85,7 +85,7 @@ h1#title, h2#subtitle, #actions, #references { top: 0; } -#selection-page h3 { +#selection-page p { margin-left: 10%; margin-right: 10%; } @@ -103,7 +103,7 @@ h1#title, h2#subtitle, #actions, #references { } #selection-page #selection { - margin-bottom: 5%; + margin-bottom: 2%; } #selection-page #return { diff --git a/script/postprocess.js b/script/postprocess.js index 7b6fed0..ed316a8 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -66,13 +66,6 @@ window.onload = function(){ let elementH2 = document.createElement('h2'); elementH2.textContent = 'Install Feed Reader Apps For Desktop And Mobile'; elementDiv.appendChild(elementH2); - let elementH3 = document.createElement('h3'); - elementH3.textContent = '' + - 'This is a selection of desktop applications, mobile apps and online ' + - 'services for you to choose from. This selection includes news ' + - 'readers, podcast managers, torrent clients, chat bots, HTML browsers ' + - 'and plugins which support syndication feeds.'; - elementDiv.appendChild(elementH3); const brands = Object.keys(selection); let elementDivSel = document.createElement('div'); elementDivSel.id = 'selection'; @@ -89,6 +82,13 @@ window.onload = function(){ elementDivSel.appendChild(elementSpan); elementDiv.appendChild(elementDivSel); } + let elementP = document.createElement('p'); + elementP.textContent = '' + + 'This is a selection of desktop applications, mobile apps and online ' + + 'services for you to choose from. This selection includes news ' + + 'readers, podcast managers, torrent clients, chat bots, HTML browsers ' + + 'and plugins which support syndication feeds.'; + elementDiv.appendChild(elementP); let elementDivReturn = document.createElement('div'); elementDivReturn.id = 'return'; elementDivReturn.textContent = 'Return To PubSub...'; -- 2.47.2 From ea51e8198e018338eecbab17cc5f24dc06f2dd36 Mon Sep 17 00:00:00 2001 From: sch Date: Tue, 9 Jul 2024 19:55:57 +0200 Subject: [PATCH 04/36] Thank you roughnecks Your instructions, corrections and advises have accelerated the development of PubSubToAtom, and consequently saved me many hours. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2014a3b..aac5a37 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,9 @@ Python code is licensed under the license AGPL-3.0 only. ## Acknowledgement -Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat +Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI. + +And an important thank you to Simone "roughnecks" Canaletti for testing and deploying PubSubToAtom into production. ## Similar Projects -- 2.47.2 From 7f26996860c034146211328b736b57e4c2501327 Mon Sep 17 00:00:00 2001 From: sch Date: Wed, 10 Jul 2024 04:11:18 +0200 Subject: [PATCH 05/36] Add a new section: Motivation An explanation of this project. --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aac5a37..38d53ab 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # XMPP PubSub To Atom -A little client that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP. - -## About - XMPP PubSub To Atom ("XPTA") is a simple Python script that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP. XPTA generates Atom syndication feeds ([RFC 4287](https://www.rfc-editor.org/rfc/rfc4287)) from XMPP PubSub nodes ([XEP-0060](http://xmpp.org/extensions/xep-0060.html)). @@ -17,6 +13,14 @@ This software was inspired from Tigase and was motivated by Movim. [59d860ab-d7c8-477c-bb4b-86924485cbbb](screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png) [selection](screenshot/selection.png) +## Motivation + +PubSubToAtom is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available +from HTTP to both, XML news readers and even HTML browsers. + +This means that instead of hosting a journal or publication HTTP site in the old fashion, one only has to have an HTTP server to host PubSubToAtom and the rest of the content is hosted on the XMPP server. + +The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by XSLT stylesheets from client-side. ## Requirements -- 2.47.2 From 45e484a409c9c701a8339428df537cd3ed7e9830 Mon Sep 17 00:00:00 2001 From: sch Date: Wed, 10 Jul 2024 04:14:20 +0200 Subject: [PATCH 06/36] Improve a paragraph of section "Motivation" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38d53ab..518fff8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This software was inspired from Tigase and was motivated by Movim. PubSubToAtom is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available from HTTP to both, XML news readers and even HTML browsers. -This means that instead of hosting a journal or publication HTTP site in the old fashion, one only has to have an HTTP server to host PubSubToAtom and the rest of the content is hosted on the XMPP server. +This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate PubSubToAtom, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by XSLT stylesheets from client-side. -- 2.47.2 From 7f6be68df1dc0b99527589d5c2cfd068d29193a1 Mon Sep 17 00:00:00 2001 From: sch Date: Wed, 10 Jul 2024 04:29:56 +0200 Subject: [PATCH 07/36] Add a paragraph to section "Motivation" --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 518fff8..c4b6afc 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ This means that instead of hosting a journal or publication site in the old fash The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by XSLT stylesheets from client-side. +Because PubSubToAtom reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that that a considered and carefully earnest use of this PubSubToAtom would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. + ## Requirements * Python >= 3.5 @@ -67,7 +69,7 @@ $ fastapi dev pubsub_to_atom.py ## Usage -It is possible to view a complete node and even a single item, which means, that it is possible to save bandwidth and it further means that a considered and carefully earnest use of this software would saves system overhead, which includes CPU, I/O and RAM usage. +It is possible to view a complete node and even a single item, which means, that it is possible to save bandwidth and it further means that a considered and carefully earnest use of this software would save system overhead, which includes CPU, I/O and RAM usage. ### Viewing PubSub -- 2.47.2 From d0ded117e26fd34f8a364275260bf3cca8e0cfc8 Mon Sep 17 00:00:00 2001 From: sch Date: Wed, 10 Jul 2024 04:51:09 +0200 Subject: [PATCH 08/36] Thank you to stpeter, Wojtek and edhelas Thank you for Atom Over XMPP, Sure.IM and Movim. --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c4b6afc..e41d48d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This means that instead of hosting a journal or publication site in the old fash The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by XSLT stylesheets from client-side. -Because PubSubToAtom reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that that a considered and carefully earnest use of this PubSubToAtom would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. +Because PubSubToAtom reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of PubSubToAtom would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. ## Requirements @@ -116,9 +116,15 @@ Python code is licensed under the license AGPL-3.0 only. ## Acknowledgement -Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI. +Thank you to Mr. Peter Saint-Andre ([stpeter](https://stpeter.im/journal/731.html)) for manifesting [Atom Over XMPP](https://www.ietf.org/archive/id/draft-saintandre-atompub-notify-07.html). -And an important thank you to Simone "roughnecks" Canaletti for testing and deploying PubSubToAtom into production. +Thank you to Mr. Wojtek and [Tigase](https://tigase.org/) for publicly exposing an implementation of PubSub as Syndication at [Sure.IM](https://sure.im/) as [feeds.tigase.im](http://feeds.tigase.im/atom?server=pubsub@sure.im&node=news). + +Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) who consistently and earnestly showcases the potential of PubSub as a publication platform with project [Movim](https://movim.eu/). + +A special thank you to the gentlemen "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI. + +And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying PubSubToAtom into production. ## Similar Projects -- 2.47.2 From af7f31e19b6c5157719fdc446754c0a637e700e3 Mon Sep 17 00:00:00 2001 From: sch Date: Wed, 10 Jul 2024 05:10:55 +0200 Subject: [PATCH 09/36] Improve wording --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e41d48d..0e4b270 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ This software was inspired from Tigase and was motivated by Movim. ## Motivation -PubSubToAtom is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available +PubSub To Atom is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available from HTTP to both, XML news readers and even HTML browsers. -This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate PubSubToAtom, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). +This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate PubSub To Atom, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). -The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by XSLT stylesheets from client-side. +The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by HTML browsers (client-side) from XSLT stylesheets. -Because PubSubToAtom reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of PubSubToAtom would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. +Because PubSub To Atom reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of PubSub To Atom would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. ## Requirements @@ -44,8 +44,7 @@ Because PubSubToAtom reads XMPP PubSub nodes, it is possible to view a complete ### Download -Extract the source package to a directory that you have permission to run -software. +Extract the source package to a directory that you have permission to run software. ```shell $ git clone https://git.xmpp-it.net/sch/PubSubToAtom @@ -58,7 +57,7 @@ Add account credentials to file `configuration.toml`. ### Start -Execute PubSubToAtom with one of the following commands: +Execute PubSub To Atom with one of the following commands: ```shell $ python -m uvicorn pubsub_to_atom:app --reload @@ -73,9 +72,9 @@ It is possible to view a complete node and even a single item, which means, that ### Viewing PubSub -Suppose you have the following nodes and items. +Suppose you have the following PubSub nodes and items. -|PubSub |Node |Item | +|Jabber ID |Node |Item | |--- |--- |--- | |blog.jmp.chat |urn:xmpp:microblog:0|launch-2023 | |edhelas%40movim.eu|urn:xmpp:microblog:0|working-on-launching-the-movim-network-qPBzwc | @@ -83,6 +82,7 @@ Suppose you have the following nodes and items. |news.movim.eu |Phoronix | | |pubsub.movim.eu |berlin-xmpp-meetup |7363a41d-1146-40b3-ac0f-8ee2559591a3 | |pubsub.movim.eu |berlin-xmpp-meetup |let-s-talk-about-the-xsf-and-possibly-other-things-6A8eV4| +|pubsub%40sure.im |news | | #### To view node items @@ -90,6 +90,7 @@ Suppose you have the following nodes and items. http://127.0.0.1:8000/atom?pubsub=edhelas%40movim.eu&node=urn%3Axmpp%3Amicroblog%3A0 http://127.0.0.1:8000/atom?pubsub=news.movim.eu&node=Phoronix http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup +http://127.0.0.1:8000/atom?pubsub=pubsub%40sure.im&node=news ``` #### To view a node item @@ -124,7 +125,7 @@ Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) wh A special thank you to the gentlemen "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI. -And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying PubSubToAtom into production. +And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying PubSub To Atom into production. ## Similar Projects -- 2.47.2 From a4c7ada5402b62870345cc8cc8d99329e6d932f4 Mon Sep 17 00:00:00 2001 From: sch Date: Wed, 10 Jul 2024 05:27:05 +0200 Subject: [PATCH 10/36] Add a list of supported XEPs --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0e4b270..0f88b1b 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,11 @@ http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup&item=7 http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup&item=let-s-talk-about-the-xsf-and-possibly-other-things-6A8eV4 ``` +## Supported XEPs + +- [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html) +- [XEP-0472: Pubsub Social Feed](https://xmpp.org/extensions/xep-0472.html) + ## Author Schimon Jehudah Zackary -- 2.47.2 From a1e4cf0f71daca1de36d73bac5883e14e0ebe1dc Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Thu, 11 Jul 2024 17:43:28 +0300 Subject: [PATCH 11/36] Add a journal list to pages with a single item; Add an option to enable PubSubToAtom as a service; Add an option to confine queries to a specified hostname. --- README.md | 4 +- configuration.toml | 16 +++- css/stylesheet.css | 35 +++++++++ pubsub_to_atom.py | 172 +++++++++++++++++++++++++++++++++++------- script/postprocess.js | 90 +++++++++++++++++++++- xsl/atom_as_xhtml.xsl | 19 ++--- 6 files changed, 295 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 0f88b1b..e42c59b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ XMPP PubSub To Atom ("XPTA") is a simple Python script that parses XMPP Pubsub N XPTA generates Atom syndication feeds ([RFC 4287](https://www.rfc-editor.org/rfc/rfc4287)) from XMPP PubSub nodes ([XEP-0060](http://xmpp.org/extensions/xep-0060.html)). -This software was inspired from Tigase and was motivated by Movim. +XPTA includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transforms PubSub nodes into static XHTML journal sites. + +XPTA was inspired from Tigase and was motivated by Movim. ## Preview diff --git a/configuration.toml b/configuration.toml index ed6eb52..df55891 100644 --- a/configuration.toml +++ b/configuration.toml @@ -1,3 +1,15 @@ +# An account to connect PubSubToAtom to the XMPP network. [account] -xmpp = "" -pass = "" +xmpp = "" # Jabber ID. +pass = "" # Password. + +# A default node, when no arguments are set. +[default] +pubsub = "blog.jmp.chat" # Jabber ID. +nodeid = "urn:xmpp:microblog:0" # Node ID. + +# Settings +[settings] +service = 1 # Enable server as a service. +include = "" # Limit service to a given domain. +operator = "" # A Jabber ID to contact with, in case of an error. diff --git a/css/stylesheet.css b/css/stylesheet.css index 76256b8..91a2cad 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -51,6 +51,27 @@ h1#title, h2#subtitle, #actions, #references { border-top: 1px solid #eee; } +#articles { + display: flex; +} + +#articles #journal { + margin-left: 2%; + margin-right: 2%; + min-width: 350px; + padding-bottom: 50px; + width: 20%; +} + +#articles #journal ul { + /* height: 500px; */ + line-height: 160%; + overflow: auto; +} + +#articles #journal > a { + font-weight: bold; +} #articles > ul > li > div > p.content { font-size: 120%; @@ -115,3 +136,17 @@ h1#title, h2#subtitle, #actions, #references { cursor: pointer; text-decoration: underline; } + +@media (max-width: 950px) { + #articles { + display: unset; + } + + #articles #journal { + margin-left: unset; + margin-right: unset; + padding-bottom: 50px; + min-width: unset; + width: unset; + } +} diff --git a/pubsub_to_atom.py b/pubsub_to_atom.py index 79f394e..c15d958 100644 --- a/pubsub_to_atom.py +++ b/pubsub_to_atom.py @@ -6,7 +6,9 @@ from fastapi import FastAPI, Request, Response from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles import feedgenerator +import json from slixmpp import ClientXMPP +from slixmpp.exceptions import IqError, IqTimeout import xml.etree.ElementTree as ET #import importlib.resources @@ -28,6 +30,7 @@ xmpp = None # Mount static graphic, script and stylesheet directories app.mount("/css", StaticFiles(directory="css"), name="css") +app.mount("/data", StaticFiles(directory="data"), name="data") app.mount("/graphic", StaticFiles(directory="graphic"), name="graphic") app.mount("/script", StaticFiles(directory="script"), name="script") app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl") @@ -40,40 +43,136 @@ async def favicon(): async def view_pubsub(request: Request): global xmpp if not xmpp: - with open('configuration.toml', mode="rb") as configuration: - credentials = tomllib.load(configuration)['account'] + credentials = get_configuration('account') xmpp = XmppInstance(credentials['xmpp'], credentials['pass']) # xmpp.connect() pubsub = request.query_params.get('pubsub', '') node = request.query_params.get('node', '') item_id = request.query_params.get('item', '') - if pubsub and node and item_id: - iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id) - link = 'xmpp:{pubsub}?;node={node};item={item}'.format( - pubsub=pubsub, node=node, item=item_id) - xml_atom = pubsub_to_atom(iq, link) - result = append_stylesheet(xml_atom) - elif pubsub and node: - iq = await xmpp.plugin['xep_0060'].get_items(pubsub, node) - link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node) - xml_atom = pubsub_to_atom(iq, link) - result = append_stylesheet(xml_atom) - elif pubsub: - iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub) - link = 'xmpp:{pubsub}'.format(pubsub=pubsub) - result = pubsub_to_opml(iq) - elif node: - result = 'PubSub parameter is missing.' - else: - result = ('Mandatory parameter PubSub and ' - 'optional parameter Node are missing.') - return Response(content=result, media_type="application/xml") + settings = get_configuration('settings') + result = None + if settings['service']: + if settings['include'] in pubsub or not settings['include']: + if pubsub and node and item_id: + iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id) + link = 'xmpp:{pubsub}?;node={node};item={item}'.format( + pubsub=pubsub, node=node, item=item_id) + xml_atom = generate_rfc_4287(iq, link) + result = append_stylesheet(xml_atom) + iq = await get_node_items(pubsub, node) + if iq: + generate_json(iq, node) + else: + operator = get_configuration('settings')['operator'] + json_data = [{'title' : 'Timeout Error: Press here to contact the operator.', + 'link' : 'xmpp:{}?message'.format(operator)}] + filename = 'data/{}.json'.format(node) + with open(filename, 'w', encoding='utf-8') as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) + # try: + # iq = await get_node_items(pubsub, node) + # generate_json(iq, node) + # except: + # operator = get_configuration('settings')['operator'] + # json_data = [{'title' : 'Timeout retrieving node items from {}'.format(node), + # 'link' : 'xmpp:{}?message'.format(operator)}] + # filename = 'data/{}.json'.format(node) + # with open(filename, 'w', encoding='utf-8') as f: + # json.dump(json_data, f, ensure_ascii=False, indent=4) + elif pubsub and node: + iq = await get_node_items(pubsub, node) + link = form_a_link(pubsub, node) + xml_atom = generate_rfc_4287(iq, link) + result = append_stylesheet(xml_atom) + elif pubsub: + iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub) + link = 'xmpp:{pubsub}'.format(pubsub=pubsub) + result = pubsub_to_opml(iq) + elif node: + text = 'PubSub parameter is missing.' + xml_atom = error_message(text) + result = append_stylesheet(xml_atom) + # else: + # result = ('Mandatory parameter PubSub and ' + # 'optional parameter Node are missing.') + else: + text = 'The given domain {} is not allowed.'.format(pubsub) + xml_atom = error_message(text) + result = append_stylesheet(xml_atom) + default = get_configuration('default') + if not result: + if default['pubsub'] and default['nodeid']: + if not pubsub and not node: + pubsub = default['pubsub'] + node = default['nodeid'] + iq = await get_node_items(pubsub, node) + link = form_a_link(pubsub, node) + xml_atom = generate_rfc_4287(iq, link) + result = append_stylesheet(xml_atom) + elif not settings['service']: + pubsub = default['pubsub'] + node = default['nodeid'] + iq = await get_node_items(pubsub, node) + link = form_a_link(pubsub, node) + xml_atom = generate_rfc_4287(iq, link) + result = append_stylesheet(xml_atom) + else: + text = 'Please contact the administrator and ask him to set default PubSub and Node ID.' + xml_atom = error_message(text) + result = append_stylesheet(xml_atom) + response = Response(content=result, media_type="application/xml") + return response -def pubsub_to_atom(iq, link): +def get_configuration(section): + with open('configuration.toml', mode="rb") as configuration: + result = tomllib.load(configuration)[section] + return result + +#@timeout(5) +async def get_node_items(pubsub, node): + try: + iq = await xmpp.plugin['xep_0060'].get_items(pubsub, node, timeout=5) + return iq + except IqTimeout as e: + print(e) + +def form_a_link(pubsub, node): + link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node) + return link + +def error_message(text): + """Error message in RFC 4287: The Atom Syndication Format.""" + feed = feedgenerator.Atom1Feed( + description = ('This is a syndication feed generated with PubSub To ' + 'Atom, which conveys XEP-0060: Publish-Subscribe nodes ' + 'to standard RFC 4287: The Atom Syndication Format.'), + language = 'en', + link = '', + subtitle = 'XMPP PubSub To Atom', + title = 'StreamBurner') + namespace = '{http://www.w3.org/2005/Atom}' + feed_url = 'gemini://schimon.i2p/' + # create entry + feed.add_item( + description = text, + # enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, + link = '', + # pubdate = updated, + title = 'Error', + # unique_id = '' + ) + xml_atom = feed.writeString('utf-8') + xml_atom_extended = append_element( + xml_atom, + 'generator', + 'XPTA: XMPP PubSub To Atom') + return xml_atom_extended + +def generate_rfc_4287(iq, link): """Convert XEP-0060: Publish-Subscribe to RFC 4287: The Atom Syndication Format.""" feed = feedgenerator.Atom1Feed( - description = ('This is a syndication feed generated with PubSub to ' + description = ('This is a syndication feed generated with PubSub To ' 'Atom, which conveys XEP-0060: Publish-Subscribe nodes ' 'to standard RFC 4287: The Atom Syndication Format.'), language = iq['pubsub']['items']['lang'], @@ -87,7 +186,6 @@ def pubsub_to_atom(iq, link): namespace = '{http://www.w3.org/2005/Atom}' title = item.find(namespace + 'title') title = None if title == None else title.text - feed_url = 'gemini://schimon.i2p/' updated = item.find(namespace + 'updated') updated = None if updated == None else updated.text # if updated: updated = datetime.datetime.fromisoformat(updated) @@ -113,6 +211,28 @@ def pubsub_to_atom(iq, link): 'XPTA: XMPP PubSub To Atom') return xml_atom_extended +def generate_json(iq, node): + """Create a JSON file from node items.""" + json_data = [] + entries = iq['pubsub']['items'] + for entry in entries: + item = entry['payload'] + namespace = '{http://www.w3.org/2005/Atom}' + title = item.find(namespace + 'title') + title = None if title == None else title.text + # updated = item.find(namespace + 'updated') + # updated = None if updated == None else updated.text + # if updated: updated = datetime.datetime.fromisoformat(updated) + link = item.find(namespace + 'link') + link = '' if link == None else link.attrib['href'] + json_data_entry = {'title' : title, + 'link' : link} + json_data.append(json_data_entry) + if len(json_data) > 6: break + filename = 'data/{}.json'.format(node) + with open(filename, 'w', encoding='utf-8') as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) + """Patch function to append elements which are not provided by feedgenerator""" def append_element(xml_data, element, text): root = ET.fromstring(xml_data) diff --git a/script/postprocess.js b/script/postprocess.js index ed316a8..9ec4ba9 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -1,13 +1,17 @@ -window.onload = function(){ +window.onload = async function(){ // Fix button follow let follow = document.querySelector('#follow'); - feedUrl = location.href.replace(/^https?:/, 'feed:'); + //let feedUrl = location.href.replace(/^https?:/, 'feed:'); + let locationHref = new URL(location.href); + let node = locationHref.searchParams.get('node') + let pubsub = locationHref.searchParams.get('pubsub') + let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`; follow.href = feedUrl; follow.addEventListener ('click', function() { window.open(feedUrl, "_self"); }); // Fix button subtome - document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds='+location.href; + document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl; // Convert ISO8601 To UTC for (let element of document.querySelectorAll('#articles > ul > li > div > h4, #feed > #header > h2#subtitle.date')) { let timeStamp = new Date(element.textContent); @@ -18,6 +22,63 @@ window.onload = function(){ let markDown = element.textContent element.innerHTML = marked.parse(markDown); } + // Build a journal list + if (locationHref.searchParams.get('item')) { + node = locationHref.searchParams.get('node') + pubsub = locationHref.searchParams.get('pubsub') + itemsList = await openJson(node) + let elementDiv = document.createElement('div'); + elementDiv.id = 'journal'; + let elementH3 = document.createElement('h3'); + elementH3.textContent = 'Journal'; + elementDiv.appendChild(elementH3); + let elementH4 = document.createElement('h4'); + elementH4.textContent = node; + elementDiv.appendChild(elementH4); + let elementUl = document.createElement('ul'); + elementDiv.appendChild(elementUl); + for (let item of itemsList) { + let elementLi = document.createElement('li'); + let elementA = document.createElement('a'); + elementA.textContent = item.title; + elementA.href = item.link; + elementLi.appendChild(elementA); + elementUl.appendChild(elementLi); + } + let elementB = document.createElement('b'); + elementB.textContent = 'Actions'; + elementDiv.appendChild(elementB); + let elementUl2 = document.createElement('ul'); + elementDiv.appendChild(elementUl2); + links = [ + {'text' : 'Subscribe from an XMPP client...', + 'href' : `xmpp:${pubsub}?pubsub;action=subscribe;node=${node}`}, + {'text' : 'Subscribe with a News Reader...', + 'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`}, + {'text' : 'Browse the journal...', + 'href' : `atom?pubsub=${pubsub}&node=${node}`} + ] + for (let link of links) { + let elementLi = document.createElement('li'); + let elementA = document.createElement('a'); + elementA.textContent = link.text; + elementA.href = link.href; + elementLi.appendChild(elementA); + elementUl2.appendChild(elementLi); + } + elementDiv.appendChild(elementUl2); + // document.querySelector('#feed').appendChild(elementDiv); // This would result in a combination of Title, Article, and Journal + document.querySelector('#articles').appendChild(elementDiv); + } + // Convert URI xmpp: to URI http: links. + for (let xmppLink of document.querySelectorAll('a[href^="xmpp:"]')) { + xmppUri = new URL(xmppLink); + let parameters = xmppUri.search.split(';'); + let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; + let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1]; + let pubsub = xmppUri.pathname; + xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}` + } // Display a selection of suggested software. const selection = { 'akregator' : { @@ -99,3 +160,26 @@ window.onload = function(){ document.body.appendChild(elementDiv); }); } + +async function openJson(nodeId) { + return fetch(`/data/${nodeId}.json`) + .then(response => { + if (!response.ok) { + throw new Error('HTTP Error: ' + response.status); + } + return response.json(); + }) + .then(json => { + return json; + }) + .catch(err => { + throw new Error('Error: ' + err); + }) +} + +function parseXmppPubsubLink(link) { + const parts = link.split(';'); + const node = parts.find(part => part.startsWith('node=')).split('=')[1]; + const item = parts.find(part => part.startsWith('item=')).split('=')[1]; + return { node, item }; +} diff --git a/xsl/atom_as_xhtml.xsl b/xsl/atom_as_xhtml.xsl index 02ee0d5..725c3b7 100644 --- a/xsl/atom_as_xhtml.xsl +++ b/xsl/atom_as_xhtml.xsl @@ -391,7 +391,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
+ title='An Inclusive Space On The Jabber Network.'> JoinJabber Libervia + title='The Social Platform Shaped For Your Community.'> Movim + title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use Xmpp.'> Modern + title='The Universal Messaging Standard.'> XMPP

- This is an XMPP news feed which is conveyed as HTML, - and it can even be viewed by a syndication feed reader - which provides automated notifications on desktop and - mobile. Click here for - a selection of software that would fit you best! + This is an XMPP news feed which is conveyed as an HTML + document, and it can even be viewed by a syndication + feed reader which provides automated notifications on + desktop and mobile. Click + here for a selection of software and pick the + ones that would fit you best!

-- 2.47.2 From ed33aca5962e49b1f678afcf237b36bd6f69b535 Mon Sep 17 00:00:00 2001 From: sch Date: Thu, 11 Jul 2024 16:53:42 +0200 Subject: [PATCH 12/36] Add a dummy file Add a dummy file in order to create a directory. --- data/README | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/README diff --git a/data/README b/data/README new file mode 100644 index 0000000..67b9b1a --- /dev/null +++ b/data/README @@ -0,0 +1 @@ +This directory caches lists of PubSub nodes. \ No newline at end of file -- 2.47.2 From 16bd475be27062556faaf3a168c12f9417939e19 Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Thu, 11 Jul 2024 19:01:45 +0300 Subject: [PATCH 13/36] Improve error handling. --- pubsub_to_atom.py | 66 ++++++++++++++++++++++++++++++------------- script/postprocess.js | 25 ++++++++-------- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/pubsub_to_atom.py b/pubsub_to_atom.py index c15d958..a664497 100644 --- a/pubsub_to_atom.py +++ b/pubsub_to_atom.py @@ -55,21 +55,26 @@ async def view_pubsub(request: Request): if settings['service']: if settings['include'] in pubsub or not settings['include']: if pubsub and node and item_id: - iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id) - link = 'xmpp:{pubsub}?;node={node};item={item}'.format( - pubsub=pubsub, node=node, item=item_id) - xml_atom = generate_rfc_4287(iq, link) - result = append_stylesheet(xml_atom) - iq = await get_node_items(pubsub, node) + iq = await get_node_item(pubsub, node, item_id) if iq: - generate_json(iq, node) + link = 'xmpp:{pubsub}?;node={node};item={item}'.format( + pubsub=pubsub, node=node, item=item_id) + xml_atom = generate_rfc_4287(iq, link) + iq = await get_node_items(pubsub, node) + if iq: + generate_json(iq, node) + else: + operator = get_configuration('settings')['operator'] + json_data = [{'title' : 'Timeout Error: Press here to contact the operator.', + 'link' : 'xmpp:{}?message'.format(operator)}] + filename = 'data/{}.json'.format(node) + with open(filename, 'w', encoding='utf-8') as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) else: - operator = get_configuration('settings')['operator'] - json_data = [{'title' : 'Timeout Error: Press here to contact the operator.', - 'link' : 'xmpp:{}?message'.format(operator)}] - filename = 'data/{}.json'.format(node) - with open(filename, 'w', encoding='utf-8') as f: - json.dump(json_data, f, ensure_ascii=False, indent=4) + text = 'Please check that PubSub node and item are valid and accessible.' + xml_atom = error_message(text) + result = append_stylesheet(xml_atom) + # try: # iq = await get_node_items(pubsub, node) # generate_json(iq, node) @@ -82,13 +87,22 @@ async def view_pubsub(request: Request): # json.dump(json_data, f, ensure_ascii=False, indent=4) elif pubsub and node: iq = await get_node_items(pubsub, node) - link = form_a_link(pubsub, node) - xml_atom = generate_rfc_4287(iq, link) + if iq: + link = form_a_link(pubsub, node) + xml_atom = generate_rfc_4287(iq, link) + else: + text = 'Please check that PubSub node is valid and accessible.' + xml_atom = error_message(text) result = append_stylesheet(xml_atom) elif pubsub: - iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub) - link = 'xmpp:{pubsub}'.format(pubsub=pubsub) - result = pubsub_to_opml(iq) + iq = await get_nodes(pubsub) + if iq: + link = 'xmpp:{pubsub}'.format(pubsub=pubsub) + result = pubsub_to_opml(iq) + else: + text = 'Please check that PubSub Jabber ID is valid and accessible.' + xml_atom = error_message(text) + result = append_stylesheet(xml_atom) elif node: text = 'PubSub parameter is missing.' xml_atom = error_message(text) @@ -130,11 +144,25 @@ def get_configuration(section): return result #@timeout(5) +async def get_node_item(pubsub, node, item_id): + try: + iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id, timeout=5) + return iq + except (IqError, IqTimeout) as e: + print(e) + async def get_node_items(pubsub, node): try: iq = await xmpp.plugin['xep_0060'].get_items(pubsub, node, timeout=5) return iq - except IqTimeout as e: + except (IqError, IqTimeout) as e: + print(e) + +async def get_nodes(pubsub): + try: + await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5) + return iq + except (IqError, IqTimeout) as e: print(e) def form_a_link(pubsub, node): diff --git a/script/postprocess.js b/script/postprocess.js index 9ec4ba9..b7522bb 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -23,10 +23,10 @@ window.onload = async function(){ element.innerHTML = marked.parse(markDown); } // Build a journal list - if (locationHref.searchParams.get('item')) { + itemsList = await openJson(node) + if (itemsList && locationHref.searchParams.get('item')) { node = locationHref.searchParams.get('node') pubsub = locationHref.searchParams.get('pubsub') - itemsList = await openJson(node) let elementDiv = document.createElement('div'); elementDiv.id = 'journal'; let elementH3 = document.createElement('h3'); @@ -74,10 +74,14 @@ window.onload = async function(){ for (let xmppLink of document.querySelectorAll('a[href^="xmpp:"]')) { xmppUri = new URL(xmppLink); let parameters = xmppUri.search.split(';'); - let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; - let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1]; - let pubsub = xmppUri.pathname; - xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}` + try { + let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; + let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1]; + let pubsub = xmppUri.pathname; + xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}` + } catch (err) { + console.warn(err) + } } // Display a selection of suggested software. const selection = { @@ -173,13 +177,6 @@ async function openJson(nodeId) { return json; }) .catch(err => { - throw new Error('Error: ' + err); + console.warn(err); }) } - -function parseXmppPubsubLink(link) { - const parts = link.split(';'); - const node = parts.find(part => part.startsWith('node=')).split('=')[1]; - const item = parts.find(part => part.startsWith('item=')).split('=')[1]; - return { node, item }; -} -- 2.47.2 From e07ff6e838cb39a18570bf6d680e4461cfe5ca04 Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Thu, 11 Jul 2024 20:56:20 +0300 Subject: [PATCH 14/36] Rename project to XMPP Journal Publisher; Retrieve dates of PubSub node items; Improve CSS stylesheet; Fix JS error. --- README.md | 26 ++++++++++++++------------ configuration.toml | 2 +- css/stylesheet.css | 6 +++--- pubsub_to_atom.py | 31 ++++++++++++++++++++----------- script/postprocess.js | 6 +++--- 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index e42c59b..d4d59f2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -# XMPP PubSub To Atom +# XMPP Journal Publisher -XMPP PubSub To Atom ("XPTA") is a simple Python script that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP. +Previously, XMPP PubSub To Atom ("XPTA"). -XPTA generates Atom syndication feeds ([RFC 4287](https://www.rfc-editor.org/rfc/rfc4287)) from XMPP PubSub nodes ([XEP-0060](http://xmpp.org/extensions/xep-0060.html)). +XMPP Journal Publisher ("XJP") is a software that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP. -XPTA includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transforms PubSub nodes into static XHTML journal sites. +XJP generates Atom syndication feeds ([RFC 4287](https://www.rfc-editor.org/rfc/rfc4287)) from XMPP PubSub nodes ([XEP-0060](http://xmpp.org/extensions/xep-0060.html)). -XPTA was inspired from Tigase and was motivated by Movim. +XJP includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transforms PubSub nodes into static XHTML journal sites. + +XJP was inspired from Tigase and was motivated by Movim. ## Preview @@ -17,14 +19,14 @@ XPTA was inspired from Tigase and was motivated by Movim. ## Motivation -PubSub To Atom is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available +XMPP Journal Publisher is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available from HTTP to both, XML news readers and even HTML browsers. -This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate PubSub To Atom, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). +This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate XMPP Journal Publisher, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). The project also showcases the non-necessity of HTML, as it automatically generates valid XHTML pages by HTML browsers (client-side) from XSLT stylesheets. -Because PubSub To Atom reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of PubSub To Atom would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. +Because XMPP Journal Publisher reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of XMPP Journal Publisher would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. ## Requirements @@ -49,8 +51,8 @@ Because PubSub To Atom reads XMPP PubSub nodes, it is possible to view a complet Extract the source package to a directory that you have permission to run software. ```shell -$ git clone https://git.xmpp-it.net/sch/PubSubToAtom -$ cd PubSubToAtom/ +$ git clone https://git.xmpp-it.net/sch/XJP +$ cd XJP/ ``` ### Configure @@ -59,7 +61,7 @@ Add account credentials to file `configuration.toml`. ### Start -Execute PubSub To Atom with one of the following commands: +Execute XMPP Journal Publisher with one of the following commands: ```shell $ python -m uvicorn pubsub_to_atom:app --reload @@ -132,7 +134,7 @@ Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) wh A special thank you to the gentlemen "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI. -And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying PubSub To Atom into production. +And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying XMPP Journal Publisher into production. ## Similar Projects diff --git a/configuration.toml b/configuration.toml index df55891..06f20bb 100644 --- a/configuration.toml +++ b/configuration.toml @@ -1,4 +1,4 @@ -# An account to connect PubSubToAtom to the XMPP network. +# An account to connect XMPP Journal Publisher to the XMPP network. [account] xmpp = "" # Jabber ID. pass = "" # Password. diff --git a/css/stylesheet.css b/css/stylesheet.css index 91a2cad..a919cd0 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -42,6 +42,7 @@ h1#title, h2#subtitle, #actions, #references { #note { line-height: 30px; margin: auto; + margin-top: 0.67em; max-width: 70%; padding: 10px; text-align: center; @@ -59,7 +60,7 @@ h1#title, h2#subtitle, #actions, #references { margin-left: 2%; margin-right: 2%; min-width: 350px; - padding-bottom: 50px; + padding-bottom: 0.67em; width: 20%; } @@ -83,7 +84,7 @@ h1#title, h2#subtitle, #actions, #references { } #articles > ul > li > div.entry { - padding-bottom: 50px; + padding-bottom: 0.67em; } #articles > ul > li > div.entry h1 { @@ -145,7 +146,6 @@ h1#title, h2#subtitle, #actions, #references { #articles #journal { margin-left: unset; margin-right: unset; - padding-bottom: 50px; min-width: unset; width: unset; } diff --git a/pubsub_to_atom.py b/pubsub_to_atom.py index a664497..ccb4e3c 100644 --- a/pubsub_to_atom.py +++ b/pubsub_to_atom.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -#import datetime +import datetime from fastapi import FastAPI, Request, Response from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles @@ -65,8 +65,14 @@ async def view_pubsub(request: Request): generate_json(iq, node) else: operator = get_configuration('settings')['operator'] - json_data = [{'title' : 'Timeout Error: Press here to contact the operator.', - 'link' : 'xmpp:{}?message'.format(operator)}] + json_data = [{'title' : 'Error retrieving items list.', + 'link' : ('javascript:alert("XJP: XMPP Journal Publisher has experienced an error ' + 'while attempting to retrieve the list of items for Node {} of PubSub {}.")') + .format(node, pubsub)}, + {'title' : 'Contact the operator.', + 'link' : ('xmpp:{}?message;subject=XJP: XMPP Journal Publisher;body=Greetings! ' + 'I am contacting you to inform you that there is an error listing ' + 'node items for Node {} on PubSub {}.').format(operator, node, pubsub)}] filename = 'data/{}.json'.format(node) with open(filename, 'w', encoding='utf-8') as f: json.dump(json_data, f, ensure_ascii=False, indent=4) @@ -172,12 +178,13 @@ def form_a_link(pubsub, node): def error_message(text): """Error message in RFC 4287: The Atom Syndication Format.""" feed = feedgenerator.Atom1Feed( - description = ('This is a syndication feed generated with PubSub To ' - 'Atom, which conveys XEP-0060: Publish-Subscribe nodes ' - 'to standard RFC 4287: The Atom Syndication Format.'), + description = ('This is a syndication feed generated with XMPP Journal ' + 'Publisher (XJP), which conveys XEP-0060: Publish-' + 'Subscribe nodes to standard RFC 4287: The Atom ' + 'Syndication Format.'), language = 'en', link = '', - subtitle = 'XMPP PubSub To Atom', + subtitle = 'XMPP Journal Publisher', title = 'StreamBurner') namespace = '{http://www.w3.org/2005/Atom}' feed_url = 'gemini://schimon.i2p/' @@ -194,7 +201,7 @@ def error_message(text): xml_atom_extended = append_element( xml_atom, 'generator', - 'XPTA: XMPP PubSub To Atom') + 'XMPP Journal Publisher (XJP)') return xml_atom_extended def generate_rfc_4287(iq, link): @@ -216,7 +223,9 @@ def generate_rfc_4287(iq, link): title = None if title == None else title.text updated = item.find(namespace + 'updated') updated = None if updated == None else updated.text - # if updated: updated = datetime.datetime.fromisoformat(updated) + published = item.find(namespace + 'published') + published = None if published == None else published.text + if not updated and not published: updated = datetime.datetime.utcnow().isoformat() content = item.find(namespace + 'content') content = 'No content' if content == None else content.text link = item.find(namespace + 'link') @@ -229,14 +238,14 @@ def generate_rfc_4287(iq, link): description = content, # enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, link = link, - # pubdate = updated, + pubdate = published or updated, title = title, unique_id = link) xml_atom = feed.writeString('utf-8') xml_atom_extended = append_element( xml_atom, 'generator', - 'XPTA: XMPP PubSub To Atom') + 'XMPP Journal Publisher (XJP)') return xml_atom_extended def generate_json(iq, node): diff --git a/script/postprocess.js b/script/postprocess.js index b7522bb..342b44c 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -51,11 +51,11 @@ window.onload = async function(){ let elementUl2 = document.createElement('ul'); elementDiv.appendChild(elementUl2); links = [ - {'text' : 'Subscribe from an XMPP client...', + {'text' : 'Subscribe from an XMPP client.', 'href' : `xmpp:${pubsub}?pubsub;action=subscribe;node=${node}`}, - {'text' : 'Subscribe with a News Reader...', + {'text' : 'Subscribe with a News Reader.', 'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`}, - {'text' : 'Browse the journal...', + {'text' : 'Browse the journal.', 'href' : `atom?pubsub=${pubsub}&node=${node}`} ] for (let link of links) { -- 2.47.2 From d1f1edbaca09b80b18571c0acd67305ca87544e7 Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Fri, 12 Jul 2024 15:39:17 +0300 Subject: [PATCH 15/36] Add OPML support; Set a new default node (Thank you roughnecks); Improve CSS, JS, XSLT; Neglect external libraries to produce syndications. --- configuration.toml | 4 +- css/stylesheet.css | 25 ++- pubsub_to_atom.py | 256 +++++++++++++++++++------------ script/postprocess.js | 139 +++++++++-------- xsl/{stylesheet.xsl => atom.xsl} | 13 +- xsl/atom_as_xhtml.xsl | 23 ++- xsl/opml.xsl | 18 +++ xsl/opml_as_xhtml.xsl | 226 +++++++++++++++++++++++++++ 8 files changed, 519 insertions(+), 185 deletions(-) rename xsl/{stylesheet.xsl => atom.xsl} (86%) create mode 100644 xsl/opml.xsl create mode 100644 xsl/opml_as_xhtml.xsl diff --git a/configuration.toml b/configuration.toml index 06f20bb..e99e40a 100644 --- a/configuration.toml +++ b/configuration.toml @@ -5,8 +5,8 @@ pass = "" # Password. # A default node, when no arguments are set. [default] -pubsub = "blog.jmp.chat" # Jabber ID. -nodeid = "urn:xmpp:microblog:0" # Node ID. +pubsub = "pubsub.woodpeckersnest.space" # Jabber ID. +nodeid = "PlanetJabber" # Node ID. # Settings [settings] diff --git a/css/stylesheet.css b/css/stylesheet.css index a919cd0..e8a8ad2 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -7,6 +7,17 @@ body { background: #000; } +code, pre { + overflow: auto; + display: inline-block; + max-height: 500px; + max-width: 100%; } + +img, video { + width: auto; + height: auto; +} + h1#title, h2#subtitle, #actions, #references { text-align: center; text-transform: uppercase; @@ -64,6 +75,7 @@ h1#title, h2#subtitle, #actions, #references { width: 20%; } +#articles #journal ol, #articles #journal ul { /* height: 500px; */ line-height: 160%; @@ -74,24 +86,25 @@ h1#title, h2#subtitle, #actions, #references { font-weight: bold; } -#articles > ul > li > div > p.content { +#articles div.content { font-size: 120%; - line-height: 30px; + line-height: 200%; margin: auto; padding-left: 2%; padding-right: 10%; /* text-align: justify; */ + word-wrap: break-word; } -#articles > ul > li > div.entry { +#articles div.entry { padding-bottom: 0.67em; } -#articles > ul > li > div.entry h1 { +#articles div.entry h1 { font-size: 2vw; } -#articles > ul > li > div.entry h2 { +#articles div.entry h2 { font-size: 1.5vw; } @@ -138,7 +151,7 @@ h1#title, h2#subtitle, #actions, #references { text-decoration: underline; } -@media (max-width: 950px) { +@media (max-width: 1525px) { #articles { display: unset; } diff --git a/pubsub_to_atom.py b/pubsub_to_atom.py index ccb4e3c..475eea2 100644 --- a/pubsub_to_atom.py +++ b/pubsub_to_atom.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- import datetime +from dateutil import parser from fastapi import FastAPI, Request, Response from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles -import feedgenerator import json from slixmpp import ClientXMPP from slixmpp.exceptions import IqError, IqTimeout @@ -57,12 +57,11 @@ async def view_pubsub(request: Request): if pubsub and node and item_id: iq = await get_node_item(pubsub, node, item_id) if iq: - link = 'xmpp:{pubsub}?;node={node};item={item}'.format( - pubsub=pubsub, node=node, item=item_id) - xml_atom = generate_rfc_4287(iq, link) + link = form_an_item_link(pubsub, node, item_id) + xml_atom = generate_atom(iq, link) iq = await get_node_items(pubsub, node) if iq: - generate_json(iq, node) + generate_json(iq) else: operator = get_configuration('settings')['operator'] json_data = [{'title' : 'Error retrieving items list.', @@ -79,7 +78,8 @@ async def view_pubsub(request: Request): else: text = 'Please check that PubSub node and item are valid and accessible.' xml_atom = error_message(text) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') # try: # iq = await get_node_items(pubsub, node) @@ -94,32 +94,37 @@ async def view_pubsub(request: Request): elif pubsub and node: iq = await get_node_items(pubsub, node) if iq: - link = form_a_link(pubsub, node) - xml_atom = generate_rfc_4287(iq, link) + link = form_a_node_link(pubsub, node) + xml_atom = generate_atom(iq, link) else: text = 'Please check that PubSub node is valid and accessible.' xml_atom = error_message(text) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') elif pubsub: iq = await get_nodes(pubsub) if iq: link = 'xmpp:{pubsub}'.format(pubsub=pubsub) - result = pubsub_to_opml(iq) + xml_opml = generate_opml(iq) + result = append_stylesheet(xml_opml, 'opml.xsl') else: text = 'Please check that PubSub Jabber ID is valid and accessible.' xml_atom = error_message(text) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') elif node: text = 'PubSub parameter is missing.' xml_atom = error_message(text) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') # else: # result = ('Mandatory parameter PubSub and ' # 'optional parameter Node are missing.') else: text = 'The given domain {} is not allowed.'.format(pubsub) xml_atom = error_message(text) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') default = get_configuration('default') if not result: if default['pubsub'] and default['nodeid']: @@ -127,20 +132,23 @@ async def view_pubsub(request: Request): pubsub = default['pubsub'] node = default['nodeid'] iq = await get_node_items(pubsub, node) - link = form_a_link(pubsub, node) + link = form_a_node_link(pubsub, node) xml_atom = generate_rfc_4287(iq, link) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') elif not settings['service']: pubsub = default['pubsub'] node = default['nodeid'] iq = await get_node_items(pubsub, node) - link = form_a_link(pubsub, node) + link = form_a_node_link(pubsub, node) xml_atom = generate_rfc_4287(iq, link) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') else: text = 'Please contact the administrator and ask him to set default PubSub and Node ID.' xml_atom = error_message(text) - result = append_stylesheet(xml_atom) + result = append_stylesheet( + xml_atom, 'atom.xsl', namespace='http://www.w3.org/2005/Atom') response = Response(content=result, media_type="application/xml") return response @@ -166,104 +174,125 @@ async def get_node_items(pubsub, node): async def get_nodes(pubsub): try: - await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5) + iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5) return iq except (IqError, IqTimeout) as e: print(e) -def form_a_link(pubsub, node): +def form_a_node_link(pubsub, node): link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node) return link +def form_an_item_link(pubsub, node, item_id): + link = 'xmpp:{pubsub}?;node={node};item={item}'.format( + pubsub=pubsub, node=node, item=item_id) + return link + def error_message(text): """Error message in RFC 4287: The Atom Syndication Format.""" - feed = feedgenerator.Atom1Feed( - description = ('This is a syndication feed generated with XMPP Journal ' - 'Publisher (XJP), which conveys XEP-0060: Publish-' - 'Subscribe nodes to standard RFC 4287: The Atom ' - 'Syndication Format.'), - language = 'en', - link = '', - subtitle = 'XMPP Journal Publisher', - title = 'StreamBurner') - namespace = '{http://www.w3.org/2005/Atom}' - feed_url = 'gemini://schimon.i2p/' - # create entry - feed.add_item( - description = text, - # enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, - link = '', - # pubdate = updated, - title = 'Error', - # unique_id = '' - ) - xml_atom = feed.writeString('utf-8') - xml_atom_extended = append_element( - xml_atom, - 'generator', - 'XMPP Journal Publisher (XJP)') - return xml_atom_extended + title = 'StreamBurner' + subtitle = 'XMPP Journal Publisher' + description = ('This is a syndication feed generated with XMPP Journal ' + 'Publisher, which conveys XEP-0060: Publish-Subscribe ' + 'nodes to standard RFC 4287: The Atom Syndication Format.') + language = 'en' + feed = ET.Element("feed") + feed.set('xmlns', 'http://www.w3.org/2005/Atom') + ET.SubElement(feed, 'title', {'type': 'text'}).text = title + ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle + ET.SubElement(feed, 'author', {'name':'XMPP Journal Publisher','email':'xjp@schimon.i2p'}) + ET.SubElement(feed, 'generator', { + 'uri': 'https://git.xmpp-it.net/sch/XMPPJournalPublisher', + 'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)' + ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat() + entry = ET.SubElement(feed, 'entry') + ET.SubElement(entry, 'title').text = 'Error' + ET.SubElement(entry, 'id').text = 'xjp-error' + ET.SubElement(entry, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat() + ET.SubElement(entry, 'published').text = datetime.datetime.now(datetime.UTC).isoformat() + # ET.SubElement(entry, 'summary', {'type': summary_type_text}).text = summary_text + ET.SubElement(entry, 'content', {'type': 'text'}).text = text + return ET.tostring(feed, encoding='unicode') -def generate_rfc_4287(iq, link): - """Convert XEP-0060: Publish-Subscribe to RFC 4287: The Atom Syndication Format.""" - feed = feedgenerator.Atom1Feed( - description = ('This is a syndication feed generated with PubSub To ' - 'Atom, which conveys XEP-0060: Publish-Subscribe nodes ' - 'to standard RFC 4287: The Atom Syndication Format.'), - language = iq['pubsub']['items']['lang'], - link = link, - subtitle = 'XMPP PubSub Syndication Feed', - title = iq['pubsub']['items']['node']) - # See also iq['pubsub']['items']['substanzas'] - entries = iq['pubsub']['items'] - for entry in entries: - item = entry['payload'] +# generate_rfc_4287 +def generate_atom(iq, link): + """Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items.""" + pubsub = iq['from'].bare + node = iq['pubsub']['items']['node'] + title = node + link = link + # link = form_a_node_link(pubsub, node) + subtitle = 'XMPP PubSub Syndication Feed' + description = ('This is a syndication feed generated with XMPP Journal ' + 'Publisher, which conveys XEP-0060: Publish-Subscribe ' + 'nodes to standard RFC 4287: The Atom Syndication Format.') + language = iq['pubsub']['items']['lang'] + items = iq['pubsub']['items'] + feed = ET.Element("feed") + feed.set('xmlns', 'http://www.w3.org/2005/Atom') + ET.SubElement(feed, 'title', {'type': 'text'}).text = title + ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle + ET.SubElement(feed, 'link', {'rel': 'self', 'href': link}) + ET.SubElement(feed, 'generator', { + 'uri': 'https://git.xmpp-it.net/sch/XMPPJournalPublisher', + 'version': '0.1'}).text = 'XMPP Journal Publisher (XJP)' + ET.SubElement(feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat() + for item in items: + item_id = item['id'] + item_payload = item['payload'] namespace = '{http://www.w3.org/2005/Atom}' - title = item.find(namespace + 'title') - title = None if title == None else title.text - updated = item.find(namespace + 'updated') - updated = None if updated == None else updated.text - published = item.find(namespace + 'published') - published = None if published == None else published.text - if not updated and not published: updated = datetime.datetime.utcnow().isoformat() - content = item.find(namespace + 'content') - content = 'No content' if content == None else content.text - link = item.find(namespace + 'link') - link = '' if link == None else link.attrib['href'] - author = item.find(namespace + 'author') + title = item_payload.find(namespace + 'title') + title_text = None if title == None else title.text + # link = item_payload.find(namespace + 'link') + # link_href = '' if link == None else link.attrib['href'] + link_href = form_an_item_link(pubsub, node, item_id) + if not title_text or not link_href: continue + content = item_payload.find(namespace + 'content') + content_text = 'No content' if content == None else content.text + if content.attrib: + content_type = content.attrib['type'] if 'type' in content.attrib else 'text' + content_type_text = 'html' if 'html' in content_type else 'text' + published = item_payload.find(namespace + 'published') + published_text = None if published == None else published.text + if published: published_dt = parser.parse(published_text) + updated = item_payload.find(namespace + 'updated') + updated_text = None if updated == None else updated.text + author = item_payload.find(namespace + 'author') if author and author.attrib: print(author.attrib) - author = 'None' if author == None else author.text - # create entry - feed.add_item( - description = content, - # enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, - link = link, - pubdate = published or updated, - title = title, - unique_id = link) - xml_atom = feed.writeString('utf-8') - xml_atom_extended = append_element( - xml_atom, - 'generator', - 'XMPP Journal Publisher (XJP)') - return xml_atom_extended + author_text = 'None' if author == None else author.text + identifier = item_payload.find(namespace + 'id') + if identifier and identifier.attrib: print(identifier.attrib) + identifier_text = 'None' if identifier == None else identifier.text + entry = ET.SubElement(feed, 'entry') + ET.SubElement(entry, 'title').text = title_text + ET.SubElement(entry, 'link', {'href': link_href}) + ET.SubElement(entry, 'id').text = identifier_text + ET.SubElement(entry, 'updated').text = updated_text + ET.SubElement(entry, 'published').text = published_text + # ET.SubElement(entry, 'summary', {'type': summary_type_text}).text = summary_text + ET.SubElement(entry, 'content', {'type': content_type_text}).text = content_text + return ET.tostring(feed, encoding='unicode') -def generate_json(iq, node): +def generate_json(iq): """Create a JSON file from node items.""" json_data = [] + pubsub = iq['from'].bare + node = iq['pubsub']['items']['node'] entries = iq['pubsub']['items'] for entry in entries: - item = entry['payload'] + item_id = entry['id'] + item_payload = entry['payload'] namespace = '{http://www.w3.org/2005/Atom}' - title = item.find(namespace + 'title') - title = None if title == None else title.text + title = item_payload.find(namespace + 'title') + title_text = None if title == None else title.text # updated = item.find(namespace + 'updated') # updated = None if updated == None else updated.text # if updated: updated = datetime.datetime.fromisoformat(updated) - link = item.find(namespace + 'link') - link = '' if link == None else link.attrib['href'] - json_data_entry = {'title' : title, - 'link' : link} + link_href = form_an_item_link(pubsub, node, item_id) + # link = item.find(namespace + 'link') + # link_href = '' if link == None else link.attrib['href'] + json_data_entry = {'title' : title_text, + 'link' : link_href} json_data.append(json_data_entry) if len(json_data) > 6: break filename = 'data/{}.json'.format(node) @@ -286,15 +315,40 @@ def append_element(xml_data, element, text): """Patch function to append XSLT reference to XML""" """Why is not this a built-in function of ElementTree or LXML""" -def append_stylesheet(xml_data): +def append_stylesheet(xml_data, filename, namespace=None): # Register namespace in order to avoide ns0: - ET.register_namespace("", "http://www.w3.org/2005/Atom") + if namespace: ET.register_namespace("", namespace) # Load XML from string tree = ET.fromstring(xml_data) # The following direction removes the XML declaration - xml_data_no_declaration = ET.tostring(tree, encoding='unicode') + xml_data_without_a_declaration = ET.tostring(tree, encoding='unicode') # Add XML declaration and stylesheet - xml_data_declaration = ('' - '' + - xml_data_no_declaration) + xml_data_declaration = ( + '' + ''.format(filename) + + xml_data_without_a_declaration) return xml_data_declaration + +def generate_opml(iq): + pubsub = iq['from'].bare + items = iq['disco_items']['items'] + opml = ET.Element("opml") + opml.set("version", "1.0") + head = ET.SubElement(opml, "head") + ET.SubElement(head, "title").text = pubsub + ET.SubElement(head, "description").text = ( + "PubSub Nodes of {}").format(pubsub) + ET.SubElement(head, "generator").text = "XMPP Journal Publisher (XJP)" + ET.SubElement(head, "urlPublic").text = ( + "https://git.xmpp-it.net/sch/XMPPJournalPublisher") + time_stamp = datetime.datetime.now(datetime.UTC).isoformat() + ET.SubElement(head, "dateCreated").text = time_stamp + ET.SubElement(head, "dateModified").text = time_stamp + body = ET.SubElement(opml, "body") + for item in items: + pubsub, node, title = item + uri = form_a_node_link(pubsub, node) + outline = ET.SubElement(body, "outline") + outline.set("text", title or node) + outline.set("xmlUrl", uri) + return ET.tostring(opml, encoding='unicode') diff --git a/script/postprocess.js b/script/postprocess.js index 342b44c..c04ca0d 100644 --- a/script/postprocess.js +++ b/script/postprocess.js @@ -1,84 +1,99 @@ window.onload = async function(){ - // Fix button follow - let follow = document.querySelector('#follow'); - //let feedUrl = location.href.replace(/^https?:/, 'feed:'); let locationHref = new URL(location.href); let node = locationHref.searchParams.get('node') let pubsub = locationHref.searchParams.get('pubsub') - let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`; - follow.href = feedUrl; - follow.addEventListener ('click', function() { - window.open(feedUrl, "_self"); - }); - // Fix button subtome - document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl; + // Fix button follow + let follow = document.querySelector('#follow'); + if (follow) { + //let feedUrl = location.href.replace(/^https?:/, 'feed:'); + let feedUrl = `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`; + follow.href = feedUrl; + follow.addEventListener ('click', function() { + window.open(feedUrl, "_self"); + }); + // Fix button subtome + document.querySelector('#subtome').href='https://www.subtome.com/#/subscribe?feeds=' + feedUrl; + } // Convert ISO8601 To UTC - for (let element of document.querySelectorAll('#articles > ul > li > div > h4, #feed > #header > h2#subtitle.date')) { + for (let element of document.querySelectorAll( + '#articles > ul > li > div > h4.published,' + + '#articles > ul > li > div > h4.updated, ' + + '#feed > #header > h2#subtitle.date')) { let timeStamp = new Date(element.textContent); element.textContent = timeStamp.toUTCString(); } // Parse Markdown - for (let element of document.querySelectorAll('#articles > ul > li > div > p')) { + for (let element of document.querySelectorAll('#articles > ul > li > div > p[type="text"]')) { let markDown = element.textContent element.innerHTML = marked.parse(markDown); } // Build a journal list - itemsList = await openJson(node) - if (itemsList && locationHref.searchParams.get('item')) { - node = locationHref.searchParams.get('node') - pubsub = locationHref.searchParams.get('pubsub') - let elementDiv = document.createElement('div'); - elementDiv.id = 'journal'; - let elementH3 = document.createElement('h3'); - elementH3.textContent = 'Journal'; - elementDiv.appendChild(elementH3); - let elementH4 = document.createElement('h4'); - elementH4.textContent = node; - elementDiv.appendChild(elementH4); - let elementUl = document.createElement('ul'); - elementDiv.appendChild(elementUl); - for (let item of itemsList) { - let elementLi = document.createElement('li'); - let elementA = document.createElement('a'); - elementA.textContent = item.title; - elementA.href = item.link; - elementLi.appendChild(elementA); - elementUl.appendChild(elementLi); + if (node) { + itemsList = await openJson(node) + if (itemsList && locationHref.searchParams.get('item')) { + node = locationHref.searchParams.get('node') + pubsub = locationHref.searchParams.get('pubsub') + let elementDiv = document.createElement('div'); + elementDiv.id = 'journal'; + let elementH3 = document.createElement('h3'); + elementH3.textContent = 'Journal'; + elementDiv.appendChild(elementH3); + let elementH4 = document.createElement('h4'); + elementH4.textContent = node; + elementDiv.appendChild(elementH4); + let elementUl = document.createElement('ol'); + elementDiv.appendChild(elementUl); + for (let item of itemsList) { + let elementLi = document.createElement('li'); + let elementA = document.createElement('a'); + elementA.textContent = item.title; + elementA.href = item.link; + elementLi.appendChild(elementA); + elementUl.appendChild(elementLi); + } + let elementB = document.createElement('b'); + elementB.textContent = 'Actions'; + elementDiv.appendChild(elementB); + let elementUl2 = document.createElement('ul'); + elementDiv.appendChild(elementUl2); + links = [ + {'text' : 'Subscribe from an XMPP client.', + 'href' : `xmpp:${pubsub}?pubsub;action=subscribe;node=${node}`}, + {'text' : 'Subscribe with a News Reader.', + 'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`}, + {'text' : 'Browse the journal.', + 'href' : `atom?pubsub=${pubsub}&node=${node}`}, + {'text' : 'Browse the portal.', + 'href' : `atom?pubsub=${pubsub}`} + ] + for (let link of links) { + let elementLi = document.createElement('li'); + let elementA = document.createElement('a'); + elementA.textContent = link.text; + elementA.href = link.href; + elementLi.appendChild(elementA); + elementUl2.appendChild(elementLi); + } + elementDiv.appendChild(elementUl2); + // document.querySelector('#feed').appendChild(elementDiv); // This would result in a combination of Title, Article, and Journal + document.querySelector('#articles').appendChild(elementDiv); } - let elementB = document.createElement('b'); - elementB.textContent = 'Actions'; - elementDiv.appendChild(elementB); - let elementUl2 = document.createElement('ul'); - elementDiv.appendChild(elementUl2); - links = [ - {'text' : 'Subscribe from an XMPP client.', - 'href' : `xmpp:${pubsub}?pubsub;action=subscribe;node=${node}`}, - {'text' : 'Subscribe with a News Reader.', - 'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`}, - {'text' : 'Browse the journal.', - 'href' : `atom?pubsub=${pubsub}&node=${node}`} - ] - for (let link of links) { - let elementLi = document.createElement('li'); - let elementA = document.createElement('a'); - elementA.textContent = link.text; - elementA.href = link.href; - elementLi.appendChild(elementA); - elementUl2.appendChild(elementLi); - } - elementDiv.appendChild(elementUl2); - // document.querySelector('#feed').appendChild(elementDiv); // This would result in a combination of Title, Article, and Journal - document.querySelector('#articles').appendChild(elementDiv); } // Convert URI xmpp: to URI http: links. - for (let xmppLink of document.querySelectorAll('a[href^="xmpp:"]')) { + for (let xmppLink of document.querySelectorAll('#articles > ul a[href^="xmpp:"], ol a[href^="xmpp:"]')) { xmppUri = new URL(xmppLink); let parameters = xmppUri.search.split(';'); try { - let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; - let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1]; - let pubsub = xmppUri.pathname; - xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}` + try { + let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; + let item = parameters.find(parameter => parameter.startsWith('item=')).split('=')[1]; + let pubsub = xmppUri.pathname; + xmppLink.href = `atom?pubsub=${pubsub}&node=${node}&item=${item}` + } catch { + let node = parameters.find(parameter => parameter.startsWith('node=')).split('=')[1]; + let pubsub = xmppUri.pathname; + xmppLink.href = `atom?pubsub=${pubsub}&node=${node}` + } } catch (err) { console.warn(err) } diff --git a/xsl/stylesheet.xsl b/xsl/atom.xsl similarity index 86% rename from xsl/stylesheet.xsl rename to xsl/atom.xsl index 6b8c5b6..72c1dd2 100644 --- a/xsl/stylesheet.xsl +++ b/xsl/atom.xsl @@ -8,22 +8,15 @@ element inside of html element --> - - - + - - - - diff --git a/xsl/atom_as_xhtml.xsl b/xsl/atom_as_xhtml.xsl index 725c3b7..6d8ba89 100644 --- a/xsl/atom_as_xhtml.xsl +++ b/xsl/atom_as_xhtml.xsl @@ -1,7 +1,7 @@ -

+

@@ -290,6 +293,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'> + + + + + + @@ -299,6 +308,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'> + + + + + + @@ -313,7 +328,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'> -

+
diff --git a/xsl/opml.xsl b/xsl/opml.xsl new file mode 100644 index 0000000..9c57a4f --- /dev/null +++ b/xsl/opml.xsl @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/xsl/opml_as_xhtml.xsl b/xsl/opml_as_xhtml.xsl new file mode 100644 index 0000000..6304ef6 --- /dev/null +++ b/xsl/opml_as_xhtml.xsl @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:choose> + <xsl:when test='title and not(title="")'> + <xsl:value-of select='title'/> + </xsl:when> + <xsl:otherwise>StreamBurner</xsl:otherwise> + </xsl:choose> + + + + + + + + +