diff --git a/README.md b/README.md index 1ac2399..2014a3b 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,82 @@ -# Rivista XJP +# XMPP PubSub To Atom -Rivista XJP ("XMPP Journal Publisher"); previously XMPP PubSub To Atom ("XPTA"). +A little client 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. +## About -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)). +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 includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform -PubSub nodes into static XHTML journal sites. +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 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 +This software 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. - -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). - -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. ## 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 PubSubToAtom 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 saves system overhead, which includes CPU, I/O and RAM usage. ### Viewing PubSub -Suppose you have the following PubSub nodes and items. +Suppose you have the following 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 -``` +|PubSub |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| #### To view node items @@ -138,10 +84,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 ``` #### To view a node item @@ -152,15 +94,8 @@ 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 Schimon Jehudah Zackary @@ -175,30 +110,10 @@ 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. 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 -Rivista into production. +Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat ## 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/configuration.toml b/configuration.toml new file mode 100644 index 0000000..ed6eb52 --- /dev/null +++ b/configuration.toml @@ -0,0 +1,3 @@ +[account] +xmpp = "" +pass = "" diff --git a/css/stylesheet.css b/css/stylesheet.css new file mode 100644 index 0000000..9780fc2 --- /dev/null +++ b/css/stylesheet.css @@ -0,0 +1,117 @@ +* { + 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 > 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 h3 { + 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: 5%; +} + +#selection-page #return { + font-style: italic; + margin: auto; +} + +#selection-link, #selection-page #return { + cursor: pointer; + text-decoration: underline; +} 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..79f394e --- /dev/null +++ b/pubsub_to_atom.py @@ -0,0 +1,143 @@ +#!/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 +from slixmpp import ClientXMPP +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("/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: + with open('configuration.toml', mode="rb") as configuration: + credentials = tomllib.load(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") + +def pubsub_to_atom(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 + 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) + 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 + +"""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> - - - - - - - - -