diff --git a/README.md b/README.md index 1ac2399..e42c59b 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,90 @@ -# Rivista XJP +# XMPP PubSub To Atom -Rivista XJP ("XMPP Journal Publisher"); previously XMPP PubSub To Atom ("XPTA"). +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. -Rivista is a software 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)). -Rivista 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 includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transforms PubSub nodes into static XHTML journal sites. -Rivista includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform -PubSub nodes into static XHTML journal sites. - -Rivista was inspired from Tigase and was motivated by Movim. - -## Home site - -- gemini://woodpeckersnest.space/~schapps/rivista.gmi -- https://schapps.woodpeckersnest.eu/rivista/ - -## Instances - -* https://rivista.woodpeckersnest.eu +XPTA was inspired from Tigase and was motivated by Movim. ## Preview -[berlin-xmpp-meetup](rivista/screenshot/berlin-xmpp-meetup.png) -[let-s-talk-about-xmpp-tricks-with-conversations-author-daniel-gultsch-OqjaQh](rivista/screenshot/let-s-talk-about-xmpp-tricks-with-conversations-author-daniel-gultsch-OqjaQh.png) -[59d860ab-d7c8-477c-bb4b-86924485cbbb](rivista/screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png) -[selection](rivista/screenshot/selection.png) +[berlin-xmpp-meetup](screenshot/berlin-xmpp-meetup.png) +[let-s-talk-about-xmpp-tricks-with-conversations-author-daniel-gultsch-OqjaQh](screenshot/let-s-talk-about-xmpp-tricks-with-conversations-author-daniel-gultsch-OqjaQh.png) +[59d860ab-d7c8-477c-bb4b-86924485cbbb](screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png) +[selection](screenshot/selection.png) ## Motivation -Rivista 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. +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 Rivista, 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 HTML browsers (client-side) from XSLT -stylesheets. +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 Rivista 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 Rivista 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 * Python >= 3.5 -* beautifulsoup4 * fastapi +* feedgenerator * lxml -* markdown * slixmpp -* tomllib (Python <= 3.10) +* tomllib * uvicorn ## Installation -It is possible to install Rivista using pip and pipx. +### Debian-based Distro -#### pip inside venv - -``` -$ python3 -m venv .venv -$ source .venv/bin/activate +```shell +# apt install python3 python3-fastapi python3-feedgenerator python3-lxml python3-slixmpp python3-tomli uvicorn ``` -##### Install +### Download -``` -$ pip install git+https://git.xmpp-it.net/sch/Rivista +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/ ``` -#### pipx +### Configure -##### Install - -``` -$ pipx install git+https://git.xmpp-it.net/sch/Rivista -``` - -##### Update - -``` -$ pipx uninstall rivista -$ pipx install git+https://git.xmpp-it.net/sch/Rivista -``` +Add account credentials to file `configuration.toml`. ### Start -``` -$ rivista -``` +Execute PubSub To Atom with one of the following commands: -Open URL http://localhost:8000 +```shell +$ python -m uvicorn pubsub_to_atom:app --reload +$ python -m uvicorn pubsub_to_atom:app --reload --host 127.0.0.1 --port 8000 +$ uvicorn pubsub_to_atom:app --host 127.0.0.1 --port 8000 +$ 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 save 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 Suppose you have the following PubSub nodes and items. -|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 | -|goffi%40goffi.org |urn:xmpp:microblog:0 |libervia-v0-8-la-cecilia-BdQ4 | -|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.movim.eu |jesus-christ-son-of-god|the-passion-of-christ-redemption-and-salvation-for-all-who-believe-moSqXO| -|pubsub.woodpeckersnest.space|PlanetJabber | | -|pubsub.woodpeckersnest.space|xmpp-it | | -|pubsub%40sure.im |news | | - -#### To view pubsub nodes - -``` -http://127.0.0.1:8000/opml?pubsub=news.movim.eu -http://127.0.0.1:8000/opml?pubsub=pubsub.woodpeckersnest.space -``` +|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 | +|goffi%40goffi.org |urn:xmpp:microblog:0|libervia-v0-8-la-cecilia-BdQ4 | +|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 @@ -138,9 +92,6 @@ http://127.0.0.1:8000/opml?pubsub=pubsub.woodpeckersnest.space 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.movim.eu&node=jesus-christ-son-of-god -http://127.0.0.1:8000/atom?pubsub=pubsub.woodpeckersnest.space&node=PlanetJabber -http://127.0.0.1:8000/atom?pubsub=pubsub.woodpeckersnest.space&node=xmpp-it http://127.0.0.1:8000/atom?pubsub=pubsub%40sure.im&node=news ``` @@ -152,13 +103,11 @@ http://127.0.0.1:8000/atom?pubsub=edhelas%40movim.eu&node=urn%3Axmpp%3Amicroblog http://127.0.0.1:8000/atom?pubsub=goffi%40goffi.org&node=urn%3Axmpp%3Amicroblog%3A0&item=libervia-v0-8-la-cecilia-BdQ4 http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=berlin-xmpp-meetup&item=7363a41d-1146-40b3-ac0f-8ee2559591a3 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 -http://127.0.0.1:8000/atom?pubsub=pubsub.movim.eu&node=jesus-christ-son-of-god&item=the-passion-of-christ-redemption-and-salvation-for-all-who-believe-moSqXO ``` ## Supported XEPs - [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html) -- [XEP-0277: Microblogging over XMPP](https://xmpp.org/extensions/xep-0277.html) - [XEP-0472: Pubsub Social Feed](https://xmpp.org/extensions/xep-0472.html) ## Author @@ -175,30 +124,18 @@ Python code is licensed under the license AGPL-3.0 only. ## Acknowledgement -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). +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). -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 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/). +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. +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 -Rivista 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 -* [AtomEntry](https://github.com/tigase/sureim/blob/master/site/src/main/java/tigase/sure/web/site/client/pubsub/AtomEntry.java) - and [PubSubPublishViewImpl](https://github.com/tigase/sureim/blob/master/site/src/main/java/tigase/sure/web/site/client/pubsub/PubSubPublishViewImpl.java) - - Convert XMPP Pubsub Nodes to Atom Syndication Format and convey them over HTTP. +* [AtomEntry](https://github.com/tigase/sureim/blob/master/site/src/main/java/tigase/sure/web/site/client/pubsub/AtomEntry.java) and [PubSubPublishViewImpl](https://github.com/tigase/sureim/blob/master/site/src/main/java/tigase/sure/web/site/client/pubsub/PubSubPublishViewImpl.java) - Convert XMPP Pubsub Nodes to Atom Syndication Format and convey them over HTTP. -* [AtomToPubsub](https://github.com/edhelas/atomtopubsub) - A little client that - parses Atom + RSS feeds and send them on XMPP Pubsub Nodes. +* [AtomToPubsub](https://github.com/edhelas/atomtopubsub) - A little client that parses Atom + RSS feeds and send them on XMPP Pubsub Nodes. diff --git a/rivista/configs/settings.toml b/configuration.toml similarity index 57% rename from rivista/configs/settings.toml rename to configuration.toml index ce01971..df55891 100644 --- a/rivista/configs/settings.toml +++ b/configuration.toml @@ -1,15 +1,15 @@ -# An account to connect XMPP Journal Publisher to the XMPP network. +# An account to connect PubSubToAtom to the XMPP network. [account] xmpp = "" # Jabber ID. pass = "" # Password. # A default node, when no arguments are set. [default] -pubsub = "" # Jabber ID. -nodeid = "" # Node ID. +pubsub = "blog.jmp.chat" # Jabber ID. +nodeid = "urn:xmpp:microblog:0" # Node ID. # Settings [settings] -service = "" # Enable server as a service. +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 new file mode 100644 index 0000000..91a2cad --- /dev/null +++ b/css/stylesheet.css @@ -0,0 +1,152 @@ +* { + color: #eee; + max-width: 100%; +} + +body { + background: #000; +} + +h1#title, h2#subtitle, #actions, #references { + text-align: center; + text-transform: uppercase; + line-height: 140%; +} + +#actions, #references { + border-bottom: 1px solid #eee; + line-height: 150%; + padding: 10px; + user-select: none; +} + +#actions > *, #references * { + letter-spacing: 5px; + margin: 5px; + text-decoration: none; +} + +#header, #menu { + line-height: 120%; + padding-bottom: 20px; +} + +#menu > h3 { + padding-left: 2%; +} + +#menu > ul > li { + padding: 5px; +} + +#note { + line-height: 30px; + margin: auto; + max-width: 70%; + padding: 10px; + text-align: center; +} + +#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%; + line-height: 30px; + margin: auto; + padding-left: 2%; + padding-right: 10%; + /* text-align: justify; */ +} + +#articles > ul > li > div.entry { + padding-bottom: 50px; +} + +#articles > ul > li > div.entry h1 { + font-size: 2vw; +} + +#articles > ul > li > div.entry h2 { + font-size: 1.5vw; +} + +#selection-page { + background: #000; + bottom: 0; + left: 0; + line-height: 200%; + overflow: overlay; + position: fixed; + right: 0; + text-align: center; + top: 0; +} + +#selection-page p { + margin-left: 10%; + margin-right: 10%; +} + +#selection-page span { + display: inline-grid; + margin: 2% +} + +#selection-page img { + height: 128px; + margin-bottom: 20%; + margin-top: 20%; + width: 128px; +} + +#selection-page #selection { + margin-bottom: 2%; +} + +#selection-page #return { + font-style: italic; + margin: auto; +} + +#selection-link, #selection-page #return { + 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/rivista/assets/img/favicon.ico b/favicon.ico similarity index 100% rename from rivista/assets/img/favicon.ico rename to favicon.ico diff --git a/rivista/assets/graphic/akregator.svg b/graphic/akregator.svg similarity index 100% rename from rivista/assets/graphic/akregator.svg rename to graphic/akregator.svg diff --git a/rivista/assets/graphic/leechcraft.png b/graphic/leechcraft.png similarity index 100% rename from rivista/assets/graphic/leechcraft.png rename to graphic/leechcraft.png diff --git a/rivista/assets/graphic/liferea.svg b/graphic/liferea.svg similarity index 100% rename from rivista/assets/graphic/liferea.svg rename to graphic/liferea.svg diff --git a/rivista/assets/graphic/raven.svg b/graphic/raven.svg similarity index 100% rename from rivista/assets/graphic/raven.svg rename to graphic/raven.svg diff --git a/rivista/assets/graphic/rssguard.png b/graphic/rssguard.png similarity index 100% rename from rivista/assets/graphic/rssguard.png rename to graphic/rssguard.png diff --git a/rivista/assets/graphic/rssowl.svg b/graphic/rssowl.svg similarity index 100% rename from rivista/assets/graphic/rssowl.svg rename to graphic/rssowl.svg diff --git a/rivista/assets/graphic/tickr.png b/graphic/tickr.png similarity index 100% rename from rivista/assets/graphic/tickr.png rename to graphic/tickr.png diff --git a/rivista/assets/graphic/xmpp.svg b/graphic/xmpp.svg similarity index 100% rename from rivista/assets/graphic/xmpp.svg rename to graphic/xmpp.svg diff --git a/pubsub_to_atom.py b/pubsub_to_atom.py new file mode 100644 index 0000000..c15d958 --- /dev/null +++ b/pubsub_to_atom.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#import datetime +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 + +try: + import tomllib +except: + import tomli as tomllib + +app = FastAPI() + +class XmppInstance(ClientXMPP): + def __init__(self, jid, password): + super().__init__(jid, password) + self.register_plugin('xep_0060') + self.connect() + # self.process(forever=False) + +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") + +@app.get('/favicon.ico', include_in_schema=False) +async def favicon(): + return FileResponse('favicon.ico') + +@app.get('/atom') +async def view_pubsub(request: Request): + global xmpp + if not xmpp: + 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', '') + 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 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 ' + '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'] + 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) + 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') + 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 = 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') + 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) + + # Create the generator element + generator_element = ET.Element(element) + generator_element.text = text + + # Append the generator element to the root + root.append(generator_element) + + # Return the modified XML as a string + return ET.tostring(root, encoding='unicode') + +"""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): + # Register namespace in order to avoide ns0: + ET.register_namespace("", "http://www.w3.org/2005/Atom") + # 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') + # Add XML declaration and stylesheet + xml_data_declaration = ('' + '' + + xml_data_no_declaration) + return xml_data_declaration diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 33fc721..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,63 +0,0 @@ -[build-system] -requires = ["setuptools>=61.2"] -build-backend = "setuptools.build_meta" - -[project] -name = "Rivista" -version = "1.0" -description = "A private journal publication and a content management system for XMPP" -authors = [{name = "Schimon Zachary", email = "sch@fedora.email"}] -license = {text = "AGPL-3.0"} -classifiers = [ - "Framework :: slixmpp", - "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: AGPL-3.0 License", - "Natural Language :: English", - "Programming Language :: Python", - "Programming Language :: Python :: 3.10", - "Topic :: Internet :: Extensible Messaging and Presence Protocol (XMPP)", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary", - "Topic :: Internet :: XMPP", - "Topic :: Office/Business :: News/Diary", -] -keywords = [ - "atom", - "blog", - "cms", - "gemini", - "jabber", - "journal", - "news", - "ssg", - "syndication", - "xml", - "xmpp", -] - -dependencies = [ - "beautifulsoup4", - "fastapi", - "gmcapsule", - "lxml", - "markdown", -# "markdown-text-clean", -# "md2gemini", - "python-dateutil", - "slixmpp", - "tomli", # Python 3.10 - "uvicorn", -] - -[project.urls] -Homepage = "https://schapps.woodpeckersnest.eu/rivista/" -Repository = "https://git.xmpp-it.net/sch/Rivista" -Issues = "https://git.xmpp-it.net/sch/Rivista/issues" - -[project.scripts] -rivista = "rivista.__main__:main" - -[tool.setuptools] -platforms = ["any"] - -[tool.setuptools.package-data] -"*" = ["*.css", "*.js", "*.ico", "*.png", "*.svg", "*.toml", "*.xsl"] diff --git a/rivista/__init__.py b/rivista/__init__.py deleted file mode 100644 index 55e4fdb..0000000 --- a/rivista/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from rivista.version import __version__, __version_info__ - -print('Rivista XJP', __version__) diff --git a/rivista/__main__.py b/rivista/__main__.py deleted file mode 100644 index b0e5459..0000000 --- a/rivista/__main__.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -#import importlib.resources - -import os -from rivista.config import Cache, Data, Settings -from rivista.utilities import Toml -from rivista.http.instance import HttpInstance -import shutil -import uvicorn - -def main(): - http_instance = HttpInstance() - return http_instance.app - -if __name__ == 'rivista.__main__': - - directory = os.path.dirname(__file__) - - # Copy data files - # FIXME File settins.toml is copied too. - directory_data = Data.get_directory() - if not os.path.exists(directory_data): - directory_assets = os.path.join(directory, 'assets') - directory_assets_new = shutil.copytree(directory_assets, directory_data) - print(f'Data directory {directory_assets_new} has been created and populated.') - - # Copy settings files - directory_settings = Settings.get_directory() - if not os.path.exists(directory_settings): - directory_configs = os.path.join(directory, 'configs') - directory_settings_new = shutil.copytree(directory_configs, directory_settings) - print(f'Settings directory {directory_settings_new} has been created and populated.') - - # Create cache directories - directory_cache = Cache.get_directory() - if not os.path.exists(directory_cache): - print(f'Creating a cache directory at {directory_cache}.') - os.mkdir(directory_cache) - # TODO Add more cache directories (see JabberCard). - # TODO Remove json (indices is the new name) - for subdirectory in ('json', 'indices', 'pubsub'): - subdirectory_cache = os.path.join(directory_cache, subdirectory) - if not os.path.exists(subdirectory_cache): - print(f'Creating a cache subdirectory at {subdirectory_cache}.') - os.mkdir(subdirectory_cache) - - # Configure settings file - file_settings = os.path.join(directory_settings, 'settings.toml') - if not os.path.exists(file_settings): - directory_configs = os.path.join(directory, 'configs') - file_settings_empty = os.path.join(directory_configs, 'settings.toml') - shutil.copyfile(file_settings_empty, file_settings) - data_settings = Toml.open_file_toml(file_settings) - - # Configure account - data_settings_account = data_settings['account'] - - settings_account = { - 'xmpp': 'Set a Jabber ID', - 'pass': 'Input Password' - } - - for key in settings_account: - data_settings_account_value = data_settings_account[key] - if not data_settings_account_value: - settings_account_message = settings_account[key] - while not data_settings_account_value: - data_settings_account_value = input(f'{settings_account_message}: ') - data_settings_account[key] = data_settings_account_value - - # Configure default PubSub Service and Node Name - data_settings_default = data_settings['default'] - - settings_default_default = { - 'pubsub': 'pubsub.woodpeckersnest.space', - 'nodeid': 'PlanetJabber' - } - - for key in settings_default_default: - data_settings_default_value = data_settings_default[key] - if not data_settings_default_value: - settings_default_default_value = settings_default_default[key] - data_settings_default_value = input(f'Set brand {key} (default: "{settings_default_default_value}"): ') - if not data_settings_default_value: data_settings_default_value = settings_default_default_value - data_settings_default[key] = data_settings_default_value - - # Configure Settings - data_settings_settings = data_settings['settings'] - - data_settings_settings_service = data_settings_settings['service'] - if data_settings_settings_service is '': - while data_settings_settings_service not in ('n', 'N', 'no', 'No', 'y', 'Y', 'yes', 'Yes'): - data_settings_settings_service = input(f'Run as service? [Y/n] ') - if data_settings_settings_service in ('y', 'Y', 'yes', 'Yes'): - data_settings_settings['service'] = 1 - else: - data_settings_settings['service'] = 0 - - data_settings_settings_include = data_settings_settings['include'] - if data_settings_settings_include is '': - data_settings_settings_include = input(f'Limit service to included domains? (comma separated. remain empty for no limit) ') - data_settings_settings['include'] = data_settings_settings_include if data_settings_settings_include else 0 - - data_settings_settings_operator = data_settings_settings['operator'] - if data_settings_settings_operator is '': - data_settings_settings['operator'] = input(f'Set a Jabber ID of an operator to contact with: ') - - Toml.save_to_toml(file_settings, data_settings) - - # Start JabberCard - app = main() - uvicorn.run(app, host='localhost', port=8000, reload=False) diff --git a/rivista/assets/css/stylesheet.css b/rivista/assets/css/stylesheet.css deleted file mode 100644 index 4f0db3f..0000000 --- a/rivista/assets/css/stylesheet.css +++ /dev/null @@ -1,303 +0,0 @@ -/* - -TODO - -pubsub: news.movim.eu - node: fake-news - item: fdef84f5-e3e1-41ea-9b68-af4bb9130f77 - title: #14October2023EpochEclipse - date: Fri, 15 Jan 2021 20:24:46 - -*/ - -* { - color: #eee; - max-width: 100%; -} - -/* -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} -*/ - -body { - background: #000; -} - -code, pre { - overflow: auto; - max-height: 100%; - max-width: 100%; } - -img, svg, video { - display: block; - max-height: 500px; - width: auto; - height: auto; -} - -h1#title, h2#subtitle, #actions, #references { - text-align: center; - text-transform: uppercase; - line-height: 140%; -} - -h5.related > a { - margin-right: 5px; - text-decoration: none; - user-select: none; - -} - -h3.title > a { - display: block; - padding-top: 50px; -} - -#actions, #references { - border-bottom: 1px solid #eee; - line-height: 150%; - padding: 10px; - user-select: none; -} - -#actions > *, #references * { - letter-spacing: 5px; - margin: 5px; - text-decoration: none; -} - -#header, #menu { - line-height: 120%; - padding-bottom: 20px; -} - -#menu > h3 { - padding-left: 2%; -} - -#menu > ol > li { - padding: 5px; -} - -#references { - margin-top: 5em; - border-top: 1px solid #eee; -} - -#articles { - min-height: 80vh; - display: flex; -} - -#articles #journal { - margin-left: 2%; - margin-right: 2%; - margin-top: 2%; - min-width: 350px; - padding-bottom: 0.67em; - width: 20%; -} - -#journal { - margin: auto; - padding-top: 50px; -} - -#articles > ul { - margin: auto; - margin-left: 2%; - margin-top: 2%; -} - -#articles #journal ol, -#articles #journal ul { - /* height: 500px; */ - line-height: 160%; - overflow: auto; - word-wrap: break-word; -} - -#articles div.content { - font-size: 120%; - line-height: 200%; - margin: auto; - padding-left: 2%; - padding-right: 10%; - /* text-align: justify; */ - word-wrap: break-word; -} - -#articles div.entry { - padding-bottom: 0.67em; -} - -#articles div.entry h1 { - font-size: 115%; /* 2vw */ -} - -#articles div.entry h2 { - font-size: 1.5vw; -} - -#selection-page { - background: #000; - bottom: 0; - left: 0; - line-height: 200%; - overflow: overlay; - position: fixed; - right: 0; - text-align: center; - top: 0; -} - -#selection-page p { - margin-left: 10%; - margin-right: 10%; -} - -#selection-page span { - display: inline-grid; - margin: 2% -} - -#selection-page img { - height: 128px; - margin-bottom: 20%; - margin-top: 20%; - width: 128px; -} - -#selection-page #selection { - margin-bottom: 2%; -} - -#selection-page #return { - /* font-style: italic; */ - margin: auto; -} - -#selection-link, #selection-page #return { - cursor: pointer; - text-decoration: underline; -} - -.content[type='text'] { - white-space: pre-wrap; -} - -#articles div.entry span.tags { - display: inline-flex; - /* display: ruby; */ - flex-wrap: wrap; -} - -#articles div.entry span.tags > div { - margin: 5px; -} - -.enclosures { - cursor: help; - direction: ltr; - margin: 5px auto 15px 1%; - padding: 15px; - padding: 1em; - /* background: #222; - border: 1px solid GrayText; - border-radius: 4px; - border-radius: .5em; - color: #525c66; - border-left: double; - max-width: 40%; */ -} - -.enclosure a { - margin: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.enclosure > span[icon='atom']:after, -.enclosure > span[icon='html5']:after, -.enclosure > span[icon='rss']:after { - content: '📰'; - margin:3px; -} - -.enclosure > span.audio:after { - content: ' (Audio file) '; -} - -.enclosure > span[icon='audio']:after{ - content: '🎼️'; - margin: 3px; -} - -.enclosure > span:after { - content: ' (Document file) '; -} - -.enclosure > span[icon]:after { - content: '📄️'; - margin: 3px; -} - -.enclosure > span.executable:after{ - content: ' (Executable file) '; -} - -.enclosure > span[icon='executable']:after { - content: '📦️'; - margin: 3px; -} - -.enclosure > span.image:after { - content: ' (Image file) '; -} - -.enclosure > span[icon='image']:after { - content: '🖼️'; - margin: 3px; -} - -.enclosure > span.video:after { - content: ' (Video file) '; -} - -.enclosure > span[icon='video']:after { - content: '📽️'; - margin:3px; -} - -#note, #small { - line-height: 30px; - margin: auto; - margin-top: 0.67em; - max-width: 80%; - padding: 10px; - text-align: center; - user-select: none; -} - -#small { - font-size: 80%; -} - -@media (max-width: 1550px) { - #articles { - display: unset; - } - - #articles #journal { - margin-right: unset; - min-width: unset; - width: unset; - } -} diff --git a/rivista/assets/graphic/leechcraft.svg b/rivista/assets/graphic/leechcraft.svg deleted file mode 100644 index cd103bd..0000000 --- a/rivista/assets/graphic/leechcraft.svg +++ /dev/null @@ -1,724 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rivista/assets/script/iso8601_to_utc.js b/rivista/assets/script/iso8601_to_utc.js deleted file mode 100644 index 8f369b3..0000000 --- a/rivista/assets/script/iso8601_to_utc.js +++ /dev/null @@ -1,8 +0,0 @@ -// Convert ISO8601 To UTC - -window.onload = function(){ - for (element of document.querySelectorAll('#articles > ul > li > div > h4')) { - timestamp = new Date(element.textContent); - element.textContent = timestamp.toUTCString(); - } -} diff --git a/rivista/assets/script/parse_markdown.js b/rivista/assets/script/parse_markdown.js deleted file mode 100644 index b38c994..0000000 --- a/rivista/assets/script/parse_markdown.js +++ /dev/null @@ -1,7 +0,0 @@ -// Parse Markdown - -window.onload = function(){ - for (element of document.querySelectorAll('#articles > ul > li > div > p')) { - element.innerHTML = marked.parse(element.textContent); - } -} diff --git a/rivista/assets/script/postprocess.js b/rivista/assets/script/postprocess.js deleted file mode 100644 index c428ec5..0000000 --- a/rivista/assets/script/postprocess.js +++ /dev/null @@ -1,265 +0,0 @@ -window.onload = async function(){ - let locationHref = new URL(location.href); - let node = locationHref.searchParams.get('node') - let pubsub = locationHref.searchParams.get('pubsub') - - // Set 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.type = `application/atom+xml`; - 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.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 div[type="text"]')) { - element.innerHTML = marked.parse(element.textContent); - } - - // NOTE Report this issue to Movim. See node "deltachat" of pubsub "news.movim.eu". - for (let element of document.querySelectorAll('#articles div[type="html"]')) { - if (!element.children.length) { - element.innerHTML = marked.parse(element.textContent); - } - } - - /* - - NOTE - - The reason for the following code to parse HTML inside an software which - already parses HTML, is that some people who influence the so called Gecko - HTML Engine product (mostly people from the advertisement industry with whom - I shamefully used to work with) have intentions to eliminate standards and - technologies such as Syndication (Atom/RDF/RSS), XSLT and many other - technologies that actually empower people and their privacy. - - Recently, some changes were made to the XSLT parser of Gecko which result in - noncompliance with the XSLT standard. - - The XSLT feature that was removed is "disable-output-escaping" which upon the - value "yes" the XSLT engine should transform and treat a subject string into - HTML, yet the Gecko HTML Engine ignores this XSLT direction. - - This change was probably made in order to: - * Frustrate new XSLT developers; or - * Influence new XSLT developers to think that XSLT has a limited set of - features and believing of some sort of inability to parse HTML from a - retrieved HTML string; and - * Consequently cause developers to abstain from using the XSLT technology. - - Do not use HTML browsers or use Ladybird, Pale Moon or browsers that are - powered by KHTML (WebKit) instead of anti-privacy software such as Chromium - and Gecko. - - */ - - // Parse HTML - //if (navigator.userAgent.includes(') Gecko/')) { - // for (let element of document.querySelectorAll('#articles div[type="html"]')) { - // element.innerHTML = element.textContent; - // } - //} - for (let element of document.querySelectorAll('#articles div[type="html"]')) { - if (!element.children.length) { - element.innerHTML = element.textContent; - } - } - - // Build a journal list - if (locationHref.pathname.startsWith('/atom') && pubsub && node && !node.includes('/')) { - itemsList = await openJson(pubsub, node) - if (itemsList && locationHref.searchParams.get('item')) { - 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.reverse()) { - let elementLi = document.createElement('li'); - let elementA = document.createElement('a'); - elementA.textContent = item.title; - elementA.href = item.link; - elementLi.appendChild(elementA); - elementUl.appendChild(elementLi); - console.log(elementLi.length) - if (elementUl.children.length > 9) {break}; - } - 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}`, - 'type' : 'x-scheme-handler/xmpp'}, - {'text' : 'Subscribe with a News Reader.', - 'href' : `feed://${location.host}/atom?pubsub=${pubsub}&node=${node}`, - 'type' : 'application/atom+xml'}, - {'text' : 'Browse the journal.', - 'href' : `atom?pubsub=${pubsub}&node=${node}`, - 'type' : 'application/atom+xml'}, - {'text' : 'Browse the portal.', - 'href' : `opml?pubsub=${pubsub}`, - 'type' : 'text/x-opml'} - ] - for (let link of links) { - let elementLi = document.createElement('li'); - let elementA = document.createElement('a'); - elementA.textContent = link.text; - elementA.href = link.href; - elementA.type = link.type; - 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( - '#articles h3 > a[href^="xmpp:"][id^="rivista-"],' + - '#articles h5.related > a[class^="rivista-"],' + - '#journal > ol > li > a[href^="xmpp:"]')) { - xmppUri = new URL(xmppLink); - let parameters = xmppUri.search.split(';'); - try { - 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) - } - } - - // Display a selection of suggested software. - const selection = { - 'akregator' : { - 'name' : 'Akregator', - 'image' : 'akregator.svg', - 'url' : 'https://apps.kde.org/akregator/' - }, - 'leechcraft' : { - 'name' : 'LeechCraft', - 'image' : 'leechcraft.png', - 'url' : 'https://leechcraft.org/' - }, - 'liferea' : { - 'name' : 'Liferea', - 'image' : 'liferea.svg', - 'url' : 'https://lzone.de/liferea/' - }, - 'raven' : { - 'name' : 'Raven Reader', - 'image' : 'raven.svg', - 'url' : 'https://ravenreader.app/' - }, - 'rssguard' : { - 'name' : 'RSS Guard', - 'image' : 'rssguard.png', - 'url' : 'https://github.com/martinrotter/rssguard' - }, - 'rssowl' : { - 'name' : 'RSSOwl', - 'image' : 'rssowl.svg', - 'url' : 'http://www.rssowl.org/' - }, - 'tickr' : { - 'name' : 'TICKR', - 'image' : 'tickr.png', - 'url' : 'https://www.open-tickr.net/' - } - } - let selectionLink = document.querySelector('#selection-link'); - selectionLink.addEventListener ('click', function() { - let elementDiv = document.createElement('div'); - elementDiv.id = 'selection-page'; - let elementH1 = document.createElement('h1'); - elementH1.textContent = 'Select A News Reader'; - elementDiv.appendChild(elementH1); - let elementH2 = document.createElement('h2'); - elementH2.textContent = 'Install A Feed Reader For Desktop And Mobile'; - elementDiv.appendChild(elementH2); - const brands = Object.keys(selection); - let elementDivSel = document.createElement('div'); - elementDivSel.id = 'selection'; - for (let i = 0; i < brands.length; i++) { - let brand = brands[i]; - let elementSpan = document.createElement('span'); - let elementA = document.createElement('a'); - elementA.href = selection[brand].url; - elementA.textContent = selection[brand].name; - let elementImg = document.createElement('img'); - elementImg.src = 'graphic/' + selection[brand].image; - elementSpan.appendChild(elementImg); - elementSpan.appendChild(elementA); - elementDivSel.appendChild(elementSpan); - elementDiv.appendChild(elementDivSel); - } - let elementP1 = document.createElement('p'); - elementP1.textContent = '' + - 'This is a selection of desktop, mobile and HTML (sometimes referred to ' + - 'as "online") News Readers for you to choose from.'; - elementDiv.appendChild(elementP1); - let elementP2 = document.createElement('p'); - elementP2.textContent = '' + - 'This selection includes: Podcast Managers, Torrent ' + - 'Clients, Chat Bots, HTML Browsers and Plugins which support ' + - 'syndication feeds.'; - elementDiv.appendChild(elementP2); - let elementSpan = document.createElement('span'); - elementSpan.id = 'return'; - elementSpan.textContent = 'Continue Reading'; - elementSpan.addEventListener ('click', function() { - document.querySelector('#selection-page').remove(); - }); - elementDiv.appendChild(elementSpan); - document.body.appendChild(elementDiv); - }); -} - -async function openJson(pubsubJid, nodeId) { - return fetch(`/data/${pubsubJid}/${nodeId}.json`) - .then(response => { - if (!response.ok) { - throw new Error('HTTP Error: ' + response.status); - } - return response.json(); - }) - .then(json => { - return json; - }) - .catch(err => { - console.warn(err); - }) -} diff --git a/rivista/assets/xsl/atom.xsl b/rivista/assets/xsl/atom.xsl deleted file mode 100644 index 31c6436..0000000 --- a/rivista/assets/xsl/atom.xsl +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/rivista/assets/xsl/atom_as_xhtml.xsl b/rivista/assets/xsl/atom_as_xhtml.xsl deleted file mode 100644 index 03047f5..0000000 --- a/rivista/assets/xsl/atom_as_xhtml.xsl +++ /dev/null @@ -1,531 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - <xsl:choose> - <xsl:when test='atom:title and not(atom:title="") and count(atom:entry) > 1'> - <xsl:value-of select='atom:title'/> - </xsl:when> - <xsl:when test='atom:entry'> - <xsl:value-of select='atom:entry/atom:title'/> - </xsl:when> - <xsl:otherwise>Rivista</xsl:otherwise> - </xsl:choose> - - - - - - - - -