Compare commits

...

30 commits

Author SHA1 Message Date
Schimon Jehudah, Adv.
74b0f50c61 Fix errors of type UnboundLocalError (Thank you. roughnecks). 2024-09-16 16:10:46 +03:00
Schimon Jehudah, Adv.
028fdacc39 Improve display of comments of discussion nodes;
Add a list of instances (Thank you roughnecks).
2024-09-05 11:08:26 +03:00
Schimon Jehudah, Adv.
b26958acae Update ECMAScript accordingly. 2024-09-04 15:50:11 +03:00
Schimon Jehudah, Adv.
b5762e90ef Improve cache handling;
Order items descendingly.
2024-09-04 15:11:45 +03:00
Schimon Jehudah, Adv.
ad34af72ff Add support for contents of Libervia (XEP-0277);
Various of visual improvements.
2024-07-31 19:21:33 +03:00
Schimon Jehudah, Adv.
44e7778578 Improve the presentation of related links. 2024-07-14 14:14:34 +03:00
Schimon Jehudah, Adv.
a9c7832da1 Python: Fix an error upon iterating for elements of type category (Thank you roughnecks). 2024-07-14 12:49:56 +03:00
Schimon Jehudah, Adv.
8ab7396a36 CSS: Ad support for element category;
XSLT: Add a reference to script postprocess.js to file opml_as_xhtml.xsl;
XSLT: Add support for element category;
Python: Add support for element category.
2024-07-14 12:35:59 +03:00
Schimon Jehudah, Adv.
cb4507bb78 Rename project to Rivista;
CSS: Improve position of elements;
JS: Improve handling of content in erroneous cases;
JS: Render post as HTML when XSLT does not render correctly;
JS: Handle posts which were tagged as Text instead of HTML and vice versa;
XSLT: Adapt to be viewed on browsers with a lack of support;
XSLT: Add Support for link elements of attribute "rel";
Python: Add elements author and link of attribute "rel".
2024-07-13 22:16:25 +03:00
Schimon Jehudah, Adv.
040d532fb9 CSS: Add padding to title links. 2024-07-12 18:54:50 +03:00
Schimon Jehudah, Adv.
a3c989cec9 Correct pathname from /atom to /opml 2024-07-12 18:33:51 +03:00
Schimon Jehudah, Adv.
1138faceda Add more examples to README and update information. 2024-07-12 18:27:21 +03:00
Schimon Jehudah, Adv.
eb280b5aab Default to content type HTML;
Correct name to XHTML;
Improve CSS.
2024-07-12 18:05:33 +03:00
Schimon Jehudah, Adv.
694c8bb489 Set pathname /opml as a pathname for OPML;
Add a message to be displayed upon empty content;
Add more error handlings;
Fix error related to Markdown parsing;
Fix error related to Atom Syndication Format;
Thank you roughnecks.
2024-07-12 16:50:59 +03:00
Schimon Jehudah, Adv.
d1f1edbaca Add OPML support;
Set a new default node (Thank you roughnecks);
Improve CSS, JS, XSLT;
Neglect external libraries to produce syndications.
2024-07-12 15:39:17 +03:00
Schimon Jehudah, Adv.
e07ff6e838 Rename project to XMPP Journal Publisher;
Retrieve dates of PubSub node items;
Improve CSS stylesheet;
Fix JS error.
2024-07-11 20:56:20 +03:00
Schimon Jehudah, Adv.
16bd475be2 Improve error handling. 2024-07-11 19:01:45 +03:00
sch
ed33aca596 Add a dummy file
Add a dummy file in order to create a directory.
2024-07-11 16:53:42 +02:00
Schimon Jehudah, Adv.
a1e4cf0f71 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.
2024-07-11 17:43:28 +03:00
sch
a4c7ada540 Add a list of supported XEPs 2024-07-10 05:27:05 +02:00
sch
af7f31e19b Improve wording 2024-07-10 05:10:55 +02:00
sch
d0ded117e2 Thank you to stpeter, Wojtek and edhelas
Thank you for Atom Over XMPP, Sure.IM and Movim.
2024-07-10 04:51:09 +02:00
sch
7f6be68df1 Add a paragraph to section "Motivation" 2024-07-10 04:29:56 +02:00
sch
45e484a409 Improve a paragraph of section "Motivation" 2024-07-10 04:14:20 +02:00
sch
7f26996860 Add a new section: Motivation
An explanation of this project.
2024-07-10 04:11:18 +02:00
sch
ea51e8198e Thank you roughnecks
Your instructions, corrections and advises have accelerated the development of PubSubToAtom, and consequently saved me many hours.
2024-07-09 19:55:57 +02:00
sch
2a42e7b202 Merge pull request 'Update README.md with Debian based distro instructions' (#1) from roughnecks/PubSubToAtom:apt-instructions-for-readme into main
Reviewed-on: sch/PubSubToAtom#1
2024-07-09 19:32:55 +02:00
Schimon Jehudah, Adv.
11ee189748 Improve software reference page. 2024-07-09 19:37:33 +03:00
Schimon Jehudah, Adv.
7712b7d9b8 JS: Fix an unintended usage of event click. 2024-07-09 19:24:23 +03:00
Schimon Jehudah, Adv.
0785b167ad Improve the handling of subscription hyperlinks. 2024-07-09 19:17:40 +03:00
10 changed files with 1512 additions and 312 deletions

View file

@ -1,14 +1,18 @@
# XMPP PubSub To Atom # Rivista
A little client that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP. Previously, XMPP Journal Publisher ("XJP") and XMPP PubSub To Atom ("XPTA").
## About Rivista is a software that parses XMPP Pubsub Nodes and sends them as Atom Syndication Format or OPML over HTTP.
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 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 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 includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform PubSub nodes into static XHTML journal sites.
This software was inspired from Tigase and was motivated by Movim. Rivista was inspired from Tigase and was motivated by Movim.
## Instances
* https://rivista.woodpeckersnest.eu/
## Preview ## Preview
@ -17,15 +21,24 @@ This software was inspired from Tigase and was motivated by Movim.
[<img alt="59d860ab-d7c8-477c-bb4b-86924485cbbb" src="screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png" width="200px"/>](screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png) [<img alt="59d860ab-d7c8-477c-bb4b-86924485cbbb" src="screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png" width="200px"/>](screenshot/59d860ab-d7c8-477c-bb4b-86924485cbbb.png)
[<img alt="selection" src="screenshot/selection.png" width="200px"/>](screenshot/selection.png) [<img alt="selection" src="screenshot/selection.png" width="200px"/>](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 ## Requirements
* Python >= 3.5 * Python >= 3.5
* fastapi * fastapi
* feedgenerator
* lxml * lxml
* slixmpp * slixmpp
* tomllib * tomllib (Python <= 3.10)
* uvicorn * uvicorn
## Installation ## Installation
@ -38,12 +51,11 @@ This software was inspired from Tigase and was motivated by Movim.
### Download ### Download
Extract the source package to a directory that you have permission to run Extract the source package to a directory that you have permission to run software.
software.
```shell ```shell
$ git clone https://git.xmpp-it.net/sch/PubSubToAtom $ git clone https://git.xmpp-it.net/sch/Rivista
$ cd PubSubToAtom/ $ cd Rivista/
``` ```
### Configure ### Configure
@ -52,9 +64,10 @@ Add account credentials to file `configuration.toml`.
### Start ### Start
Execute PubSubToAtom with one of the following commands: Execute Rivista with one of the following commands:
```shell ```shell
$ python -m uvicorn main:app --reload
$ python -m uvicorn pubsub_to_atom:app --reload $ python -m uvicorn pubsub_to_atom:app --reload
$ python -m uvicorn pubsub_to_atom:app --reload --host 127.0.0.1 --port 8000 $ 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 $ uvicorn pubsub_to_atom:app --host 127.0.0.1 --port 8000
@ -63,13 +76,13 @@ $ fastapi dev pubsub_to_atom.py
## Usage ## 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 ### 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 | |blog.jmp.chat |urn:xmpp:microblog:0 |launch-2023 |
|edhelas%40movim.eu |urn:xmpp:microblog:0 |working-on-launching-the-movim-network-qPBzwc | |edhelas%40movim.eu |urn:xmpp:microblog:0 |working-on-launching-the-movim-network-qPBzwc |
@ -77,6 +90,17 @@ Suppose you have the following nodes and items.
|news.movim.eu |Phoronix | | |news.movim.eu |Phoronix | |
|pubsub.movim.eu |berlin-xmpp-meetup |7363a41d-1146-40b3-ac0f-8ee2559591a3 | |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 |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
```
#### To view node items #### To view node items
@ -84,6 +108,10 @@ 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=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=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=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 #### To view a node item
@ -94,8 +122,15 @@ 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=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=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=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 ## Author
Schimon Jehudah Zackary Schimon Jehudah Zackary
@ -110,7 +145,15 @@ Python code is licensed under the license AGPL-3.0 only.
## Acknowledgement ## Acknowledgement
Special thanks to "d3x" and "cchianel" from IRC channel #python on irc.libera.chat 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.
## Similar Projects ## Similar Projects

View file

@ -1,3 +1,15 @@
# An account to connect XMPP Journal Publisher to the XMPP network.
[account] [account]
xmpp = "" xmpp = "" # Jabber ID.
pass = "" pass = "" # Password.
# A default node, when no arguments are set.
[default]
pubsub = "pubsub.woodpeckersnest.space" # Jabber ID.
nodeid = "PlanetJabber" # 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.

View file

@ -1,18 +1,64 @@
/*
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; color: #eee;
max-width: 100%; max-width: 100%;
} }
/*
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
*/
body { body {
background: #000; 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 { h1#title, h2#subtitle, #actions, #references {
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
line-height: 140%; 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 { #actions, #references {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
line-height: 150%; line-height: 150%;
@ -35,41 +81,67 @@ h1#title, h2#subtitle, #actions, #references {
padding-left: 2%; padding-left: 2%;
} }
#menu > ul > li { #menu > ol > li {
padding: 5px; padding: 5px;
} }
#note {
line-height: 30px;
margin: auto;
max-width: 70%;
padding: 10px;
text-align: center;
}
#references { #references {
margin-top: 5em;
border-top: 1px solid #eee; border-top: 1px solid #eee;
} }
#articles {
min-height: 80vh;
display: flex;
}
#articles > ul > li > div > p.content { #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%; font-size: 120%;
line-height: 30px; line-height: 200%;
margin: auto; margin: auto;
padding-left: 2%; padding-left: 2%;
padding-right: 10%; padding-right: 10%;
/* text-align: justify; */ /* text-align: justify; */
word-wrap: break-word;
} }
#articles > ul > li > div.entry { #articles div.entry {
padding-bottom: 50px; padding-bottom: 0.67em;
} }
#articles > ul > li > div.entry h1 { #articles div.entry h1 {
font-size: 2vw; font-size: 115%; /* 2vw */
} }
#articles > ul > li > div.entry h2 { #articles div.entry h2 {
font-size: 1.5vw; font-size: 1.5vw;
} }
@ -85,7 +157,7 @@ h1#title, h2#subtitle, #actions, #references {
top: 0; top: 0;
} }
#selection-page h3 { #selection-page p {
margin-left: 10%; margin-left: 10%;
margin-right: 10%; margin-right: 10%;
} }
@ -103,11 +175,11 @@ h1#title, h2#subtitle, #actions, #references {
} }
#selection-page #selection { #selection-page #selection {
margin-bottom: 5%; margin-bottom: 2%;
} }
#selection-page #return { #selection-page #return {
font-style: italic; /* font-style: italic; */
margin: auto; margin: auto;
} }
@ -115,3 +187,117 @@ h1#title, h2#subtitle, #actions, #references {
cursor: pointer; cursor: pointer;
text-decoration: underline; 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;
}
}

1
data/README Normal file
View file

@ -0,0 +1 @@
This directory caches lists of PubSub nodes.

View file

@ -1,12 +1,15 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#import datetime import datetime
from fastapi import FastAPI, Request, Response from fastapi import FastAPI, Request, Response
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import feedgenerator import json
from os import mkdir
from os.path import exists
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
#import importlib.resources #import importlib.resources
@ -15,8 +18,6 @@ try:
except: except:
import tomli as tomllib import tomli as tomllib
app = FastAPI()
class XmppInstance(ClientXMPP): class XmppInstance(ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super().__init__(jid, password)
@ -26,8 +27,11 @@ class XmppInstance(ClientXMPP):
xmpp = None xmpp = None
app = FastAPI()
# Mount static graphic, script and stylesheet directories # Mount static graphic, script and stylesheet directories
app.mount("/css", StaticFiles(directory="css"), name="css") 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("/graphic", StaticFiles(directory="graphic"), name="graphic")
app.mount("/script", StaticFiles(directory="script"), name="script") app.mount("/script", StaticFiles(directory="script"), name="script")
app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl") app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl")
@ -36,108 +40,540 @@ app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl")
async def favicon(): async def favicon():
return FileResponse('favicon.ico') return FileResponse('favicon.ico')
@app.get('/atom') @app.route('/')
async def view_pubsub(request: Request): @app.get('/opml')
async def view_pubsub_nodes(request: Request):
global xmpp global xmpp
if not xmpp: if not xmpp:
with open('configuration.toml', mode="rb") as configuration: credentials = get_configuration('account')
credentials = tomllib.load(configuration)['account'] xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect()
pubsub = request.query_params.get('pubsub', '')
settings = get_configuration('settings')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub:
iq = await get_nodes(pubsub)
if iq:
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml')
else:
text = 'Please ensure that PubSub "{}" (Jabber ID) is valid and accessible.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
default = get_configuration('default')
if not result:
if default['pubsub']:
if not pubsub:
pubsub = default['pubsub']
iq = await get_nodes(pubsub)
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml')
elif not settings['service']:
pubsub = default['pubsub']
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = generate_opml(iq)
result = append_stylesheet(xml_opml, 'opml')
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, 'atom')
response = Response(content=result, media_type="application/xml")
return response
@app.get('/atom')
async def view_node_items(request: Request):
global xmpp
if not xmpp:
credentials = get_configuration('account')
xmpp = XmppInstance(credentials['xmpp'], credentials['pass']) xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect() # xmpp.connect()
pubsub = request.query_params.get('pubsub', '') pubsub = request.query_params.get('pubsub', '')
node = request.query_params.get('node', '') node = request.query_params.get('node', '')
item_id = request.query_params.get('item', '') 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: if pubsub and node and item_id:
iq = await xmpp.plugin['xep_0060'].get_item(pubsub, node, item_id) iq = await get_node_item(pubsub, node, item_id)
if iq:
link = form_an_item_link(pubsub, node, item_id)
xml_atom = generate_atom_comment(iq, link) if 'urn:xmpp:microblog:0:comments/' in node else generate_atom_post(iq, link)
iq = await get_node_items(pubsub, node)
if not '/' in node:
if iq:
generate_json(iq)
else:
operator = get_configuration('settings')['operator']
json_data = [{'title' : 'Error retrieving node items.',
'link' : ('javascript:alert("Rivista 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=Rivista;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)
else:
text = 'Please ensure that PubSub node "{}" and item "{}" are valid and accessible.'.format(node, item_id)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
# 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)
if iq:
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom_comment(iq, link) if 'urn:xmpp:microblog:0:comments/' in node else generate_atom_post(iq, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
elif pubsub:
text = 'Node parameter is missing.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
elif node:
text = 'PubSub parameter is missing.'
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
# else:
# text = ('Mandatory parameter PubSub and '
# 'optional parameter Node are missing.')
# xml_atom = error_message(text)
# result = append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, 'atom')
if not result:
default = get_configuration('default')
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)
if iq:
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom_post(iq, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = error_message(text)
elif not settings['service']:
pubsub = default['pubsub']
node = default['nodeid']
iq = await get_node_items(pubsub, node)
if iq:
link = form_a_node_link(pubsub, node)
xml_atom = generate_atom_post(iq, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = error_message(text)
result = append_stylesheet(xml_atom, '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, '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_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 (IqError, IqTimeout) as e:
print(e)
async def get_nodes(pubsub):
try:
iq = await xmpp.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
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( link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
pubsub=pubsub, node=node, item=item_id) pubsub=pubsub, node=node, item=item_id)
xml_atom = pubsub_to_atom(iq, link) return 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): def error_message(text):
"""Convert XEP-0060: Publish-Subscribe to RFC 4287: The Atom Syndication Format.""" """Error message in RFC 4287: The Atom Syndication Format."""
feed = feedgenerator.Atom1Feed( title = 'Rivista'
description = ('This is a syndication feed generated with PubSub to ' subtitle = 'XMPP Journal Publisher'
'Atom, which conveys XEP-0060: Publish-Subscribe nodes ' description = ('This is a syndication feed generated with Rivista, an XMPP '
'to standard RFC 4287: The Atom Syndication Format.'), 'Journal Publisher, which conveys XEP-0060: Publish-'
language = iq['pubsub']['items']['lang'], 'Subscribe nodes to standard RFC 4287: The Atom Syndication '
link = link, 'Format.')
subtitle = 'XMPP PubSub Syndication Feed', language = 'en'
title = iq['pubsub']['items']['node']) feed = ET.Element("feed")
# See also iq['pubsub']['items']['substanzas'] 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':'Rivista','email':'rivista@schimon.i2p'})
ET.SubElement(feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/Rivista',
'version': '0.1'}).text = 'Rivista 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 = 'rivista-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')
# generate_rfc_4287
def generate_atom_post(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'
subtitle = pubsub
description = ('This is a syndication feed generated with Rivista, an 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']
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = title
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(e_feed, 'link', {'rel': 'self', 'href': link})
ET.SubElement(e_feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/Rivista',
'version': '0.1'}).text = 'Rivista XJP'
ET.SubElement(e_feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
for item in list(items)[::-1]:
item_id = item['id']
item_payload = item['payload']
namespace = '{http://www.w3.org/2005/Atom}'
title = item_payload.find(namespace + 'title')
links = item_payload.find(namespace + 'link')
if (not isinstance(title, ET.Element) and
not isinstance(links, ET.Element)): continue
e_entry = ET.SubElement(e_feed, 'entry')
title_text = None if title == None else title.text
ET.SubElement(e_entry, 'title').text = title_text
if isinstance(links, ET.Element):
for link in item_payload.findall(namespace + 'link'):
link_href = link.attrib['href'] if 'href' in link.attrib else ''
link_type = link.attrib['type'] if 'type' in link.attrib else ''
link_rel = link.attrib['rel'] if 'rel' in link.attrib else ''
ET.SubElement(e_entry, 'link', {'href': link_href, 'rel': link_rel, 'type': link_type})
else:
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = form_an_item_link(pubsub, node, item_id)
ET.SubElement(e_entry, 'link', {'href': link_xmpp, 'rel': 'alternate', 'type': 'x-scheme-handler/xmpp'})
contents = item_payload.find(namespace + 'content')
if isinstance(contents, ET.Element):
for content in item_payload.findall(namespace + 'content'):
if not content.text: continue
content_text = content.text
content_type = content.attrib['type'] if 'type' in content.attrib else 'html'
content_type_text = 'html' if 'html' in content_type else 'text'
ET.SubElement(e_entry, 'content', {'type': content_type_text}).text = content_text
else:
summary = item_payload.find(namespace + 'summary')
summary_text = summary.text if summary else None
if summary_text:
summary_type = summary.attrib['type'] if 'type' in summary.attrib else 'html'
summary_type_text = 'html' if 'html' in summary_type else 'text'
ET.SubElement(e_entry, 'content', {'type': summary_type_text}).text = summary_text
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
published = item_payload.find(namespace + 'published')
published_text = None if published == None else published.text
ET.SubElement(e_entry, 'published').text = published_text
updated = item_payload.find(namespace + 'updated')
updated_text = None if updated == None else updated.text
ET.SubElement(e_entry, 'updated').text = updated_text
authors = item_payload.find(namespace + 'author')
if isinstance(authors, ET.Element):
for author in item_payload.findall(namespace + 'author'):
e_author = ET.SubElement(e_entry, 'author')
author_email = author.find(namespace + 'email')
if author_email is not None:
author_email_text = author_email.text
if author_email_text: ET.SubElement(e_author, 'email').text = author_email.text
else:
author_email_text = None
author_uri = author.find(namespace + 'uri')
if author_uri is not None:
author_uri_text = author_uri.text
if author_uri_text: ET.SubElement(e_author, 'uri').text = author_uri.text
else:
author_uri_text = None
author_name = author.find(namespace + 'name')
if author_name is not None and author_name.text:
author_name_text = author_name.text
else:
author_name_text = author_uri_text or author_email_text
ET.SubElement(e_author, 'name').text = author_name_text
for uri in item_payload.iter(namespace + 'author'):
uri_text = uri.text
if uri_text:
ET.SubElement(e_entry, 'uri').text = uri_text
# if not e_author:
# ET.SubElement(e_author, 'name').text = uri_text
# ET.SubElement(e_author, 'uri').text = uri_text
categories = item_payload.find(namespace + 'category')
if isinstance(categories, ET.Element):
for category in item_payload.findall(namespace + 'category'):
if 'term' in category.attrib and category.attrib['term']:
category_term = category.attrib['term']
ET.SubElement(e_entry, 'category', {'term': category_term})
identifier = item_payload.find(namespace + 'id')
if identifier and identifier.attrib: print(identifier.attrib)
identifier_text = None if identifier == None else identifier.text
ET.SubElement(e_entry, 'id').text = identifier_text
# ET.SubElement(e_entry, 'summary', {'type': summary_type_text}).text = summary_text
return ET.tostring(e_feed, encoding='unicode')
# generate_rfc_4287
def generate_atom_comment(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'
subtitle = pubsub
description = ('This is a syndication feed generated with Rivista, an 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']
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = title
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(e_feed, 'link', {'rel': 'self', 'href': link})
ET.SubElement(e_feed, 'generator', {
'uri': 'https://git.xmpp-it.net/sch/Rivista',
'version': '0.1'}).text = 'Rivista XJP'
ET.SubElement(e_feed, 'updated').text = datetime.datetime.now(datetime.UTC).isoformat()
for item in list(items)[::-1]:
item_id = item['id']
item_payload = item['payload']
namespace = '{http://www.w3.org/2005/Atom}'
authors = item_payload.find(namespace + 'author')
links = item_payload.find(namespace + 'link')
if (not isinstance(authors, ET.Element) and
not isinstance(links, ET.Element)): continue
e_entry = ET.SubElement(e_feed, 'entry')
author_text = None
for author in item_payload.findall(namespace + 'author'):
author_email = author.find(namespace + 'email')
if author_email is not None:
author_text = author_email.text
if not author_text:
author_uri = author.find(namespace + 'uri')
if author_uri is not None:
author_text = author_uri.text
if not author_text:
author_name = author.find(namespace + 'name')
if author_name is not None and author_name.text:
author_text = author_name.text
if not author_text:
for uri in item_payload.iter(namespace + 'author'):
author_text = uri.text
if author_text:
ET.SubElement(e_entry, 'title').text = author_text
break
if isinstance(links, ET.Element):
for link in item_payload.findall(namespace + 'link'):
link_href = link.attrib['href'] if 'href' in link.attrib else ''
link_type = link.attrib['type'] if 'type' in link.attrib else ''
link_rel = link.attrib['rel'] if 'rel' in link.attrib else ''
ET.SubElement(e_entry, 'link', {'href': link_href, 'rel': link_rel, 'type': link_type})
else:
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = form_an_item_link(pubsub, node, item_id)
ET.SubElement(e_entry, 'link', {'href': link_xmpp, 'rel': 'alternate', 'type': 'x-scheme-handler/xmpp'})
contents = item_payload.find(namespace + 'content')
if isinstance(contents, ET.Element):
for content in item_payload.findall(namespace + 'content'):
if not content.text: continue
content_text = content.text
content_type = content.attrib['type'] if 'type' in content.attrib else 'html'
content_type_text = 'html' if 'html' in content_type else 'text'
ET.SubElement(e_entry, 'content', {'type': content_type_text}).text = content_text
else:
summary = item_payload.find(namespace + 'summary')
summary_text = summary.text if summary else None
if summary_text:
summary_type = summary.attrib['type'] if 'type' in summary.attrib else 'html'
summary_type_text = 'html' if 'html' in summary_type else 'text'
ET.SubElement(e_entry, 'content', {'type': summary_type_text}).text = summary_text
else:
title = item_payload.find(namespace + 'title')
title_text = None if title == None else title.text
if title_text:
ET.SubElement(e_entry, 'content').text = title_text
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
published = item_payload.find(namespace + 'published')
published_text = None if published == None else published.text
ET.SubElement(e_entry, 'published').text = published_text
updated = item_payload.find(namespace + 'updated')
updated_text = None if updated == None else updated.text
ET.SubElement(e_entry, 'updated').text = updated_text
authors = item_payload.find(namespace + 'author')
if isinstance(authors, ET.Element):
contact_text = None
for author in item_payload.findall(namespace + 'author'):
author_email = author.find(namespace + 'email')
if author_email is not None:
contact_text = author_email.text
if not contact_text:
author_uri = author.find(namespace + 'uri')
if author_uri is not None:
contact_text = author_uri.text
if not contact_text:
for uri in item_payload.iter(namespace + 'author'):
contact_text = uri.text
if contact_text:
if contact_text.startswith('xmpp:'):
contact_type = 'x-scheme-handler/xmpp'
elif contact_text.startswith('mailto:'):
contact_type = 'x-scheme-handler/mailto'
else:
contact_type = None
if contact_type:
ET.SubElement(e_entry, 'link', {'href': contact_text, 'rel': 'contact', 'type': contact_type})
else:
ET.SubElement(e_entry, 'link', {'href': contact_text, 'rel': 'contact'})
break
categories = item_payload.find(namespace + 'category')
if isinstance(categories, ET.Element):
for category in item_payload.findall(namespace + 'category'):
if 'term' in category.attrib and category.attrib['term']:
category_term = category.attrib['term']
ET.SubElement(e_entry, 'category', {'term': category_term})
identifier = item_payload.find(namespace + 'id')
if identifier and identifier.attrib: print(identifier.attrib)
identifier_text = None if identifier == None else identifier.text
ET.SubElement(e_entry, 'id').text = identifier_text
# ET.SubElement(e_entry, 'summary', {'type': summary_type_text}).text = summary_text
return ET.tostring(e_feed, encoding='unicode')
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'] entries = iq['pubsub']['items']
for entry in entries: for entry in entries:
item = entry['payload'] item_id = entry['id']
item_payload = entry['payload']
namespace = '{http://www.w3.org/2005/Atom}' namespace = '{http://www.w3.org/2005/Atom}'
title = item.find(namespace + 'title') title = item_payload.find(namespace + 'title')
title = None if title == None else title.text title_text = '*** No Title ***' if title == None else title.text
feed_url = 'gemini://schimon.i2p/' # updated = item.find(namespace + 'updated')
updated = item.find(namespace + 'updated') # updated = None if updated == None else updated.text
updated = None if updated == None else updated.text
# if updated: updated = datetime.datetime.fromisoformat(updated) # if updated: updated = datetime.datetime.fromisoformat(updated)
content = item.find(namespace + 'content') link_href = form_an_item_link(pubsub, node, item_id)
content = 'No content' if content == None else content.text # link = item.find(namespace + 'link')
link = item.find(namespace + 'link') # link_href = '' if link == None else link.attrib['href']
link = '' if link == None else link.attrib['href'] json_data_entry = {'title' : title_text,
author = item.find(namespace + 'author') 'link' : link_href}
if author and author.attrib: print(author.attrib) json_data.append(json_data_entry)
author = 'None' if author == None else author.text #if len(json_data) > 6: break
# create entry directory = 'data/{}/'.format(pubsub)
feed.add_item( if not exists(directory):
description = content, mkdir(directory)
# enclosure = feedgenerator.Enclosure(enclosure, enclosure_size, enclosure_type) if args.entry_enclosure else None, filename = 'data/{}/{}.json'.format(pubsub, node)
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""" with open(filename, 'w', encoding='utf-8') as f:
def append_element(xml_data, element, text): json.dump(json_data, f, ensure_ascii=False, indent=4)
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""" """Patch function to append XSLT reference to XML"""
"""Why is not this a built-in function of ElementTree or LXML""" """Why is not this a built-in function of ElementTree or LXML"""
def append_stylesheet(xml_data): def append_stylesheet(xml_data, type):
# Register namespace in order to avoide ns0: # Register namespace in order to avoide ns0:
ET.register_namespace("", "http://www.w3.org/2005/Atom") if type == 'atom': ET.register_namespace('', 'http://www.w3.org/2005/Atom')
# Load XML from string # Load XML from string
tree = ET.fromstring(xml_data) tree = ET.fromstring(xml_data)
# The following direction removes the XML declaration # 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 # Add XML declaration and stylesheet
xml_data_declaration = ('<?xml version="1.0" encoding="utf-8"?>' xml_data_declaration = (
'<?xml-stylesheet type="text/xsl" href="xsl/stylesheet.xsl"?>' + '<?xml version="1.0" encoding="utf-8"?>'
xml_data_no_declaration) '<?xml-stylesheet type="text/xsl" href="xsl/{}.xsl"?>'.format(type) +
xml_data_without_a_declaration)
return xml_data_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 = 'An OPML of ' + pubsub
ET.SubElement(head, "description").text = (
"PubSub Nodes of {}").format(pubsub)
ET.SubElement(head, "generator").text = 'Rivista'
ET.SubElement(head, "urlPublic").text = 'https://git.xmpp-it.net/sch/Rivista'
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')

View file

@ -1,14 +1,167 @@
window.onload = function(){ window.onload = async function(){
// Convert ISO8601 To UTC/ let locationHref = new URL(location.href);
for (let element of document.querySelectorAll('#articles > ul > li > div > h4, #feed > #header > h2#subtitle.date')) { 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); let timeStamp = new Date(element.textContent);
element.textContent = timeStamp.toUTCString(); element.textContent = timeStamp.toUTCString();
} }
// Parse Markdown/
for (let element of document.querySelectorAll('#articles > ul > li > div > p')) { // Parse Markdown
let markDown = element.textContent for (let element of document.querySelectorAll('#articles div[type="text"]')) {
element.innerHTML = marked.parse(markDown); 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. // Display a selection of suggested software.
const selection = { const selection = {
'akregator' : { 'akregator' : {
@ -52,24 +205,17 @@ window.onload = function(){
let elementDiv = document.createElement('div'); let elementDiv = document.createElement('div');
elementDiv.id = 'selection-page'; elementDiv.id = 'selection-page';
let elementH1 = document.createElement('h1'); let elementH1 = document.createElement('h1');
elementH1.textContent = 'Get A News Reader'; elementH1.textContent = 'Select A News Reader';
elementDiv.appendChild(elementH1); elementDiv.appendChild(elementH1);
let elementH2 = document.createElement('h2'); let elementH2 = document.createElement('h2');
elementH2.textContent = 'Install Feed Reader Apps For Desktop And Mobile'; elementH2.textContent = 'Install A Feed Reader For Desktop And Mobile';
elementDiv.appendChild(elementH2); 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); const brands = Object.keys(selection);
let elementDivSel = document.createElement('div'); let elementDivSel = document.createElement('div');
elementDivSel.id = 'selection'; elementDivSel.id = 'selection';
for (let i = 0; i < brands.length; i++) { for (let i = 0; i < brands.length; i++) {
let brand = brands[i]; let brand = brands[i];
elementSpan = document.createElement('span'); let elementSpan = document.createElement('span');
let elementA = document.createElement('a'); let elementA = document.createElement('a');
elementA.href = selection[brand].url; elementA.href = selection[brand].url;
elementA.textContent = selection[brand].name; elementA.textContent = selection[brand].name;
@ -80,13 +226,40 @@ window.onload = function(){
elementDivSel.appendChild(elementSpan); elementDivSel.appendChild(elementSpan);
elementDiv.appendChild(elementDivSel); elementDiv.appendChild(elementDivSel);
} }
let elementDivReturn = document.createElement('div'); let elementP1 = document.createElement('p');
elementDivReturn.id = 'return'; elementP1.textContent = '' +
elementDivReturn.textContent = 'Return To PubSub...'; 'This is a selection of desktop, mobile and HTML (sometimes referred to ' +
elementDivReturn.addEventListener ('click', function() { '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 = 'Return';
elementSpan.addEventListener ('click', function() {
document.querySelector('#selection-page').remove(); document.querySelector('#selection-page').remove();
}); });
elementDiv.appendChild(elementDivReturn); elementDiv.appendChild(elementSpan);
document.body.appendChild(elementDiv); 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);
})
}

View file

@ -1,29 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- <!--
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head> element <meta/> inside of html element </head>
--> -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding = 'UTF-8'
<xsl:output
method = 'html'
indent = 'yes' indent = 'yes'
omit-xml-decleration='no' /> media-type = 'application/atom+xml'
method = 'html'
<!-- Atom 1.0 Syndication Format --> version = '4.01' />
<!-- Atom Syndication Format 1.0 -->
<xsl:include href='atom_as_xhtml.xsl'/> <xsl:include href='atom_as_xhtml.xsl'/>
<!-- extract filename from given url string --> <!-- extract filename from given url string -->
<xsl:include href='extract-filename.xsl'/> <xsl:include href='extract-filename.xsl'/>
<!-- set page metadata --> <!-- set page metadata -->
<xsl:include href='metadata.xsl'/> <xsl:include href='metadata.xsl'/>
<!-- transform filesize from given length string --> <!-- transform filesize from given length string -->
<xsl:include href='transform-filesize.xsl'/> <xsl:include href='transform-filesize.xsl'/>
</xsl:stylesheet> </xsl:stylesheet>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- <!--
Copyright (C) 2016 - 2017 Schimon Jehuda. Released under MIT license Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head> element <meta/> inside of html element </head>
@ -15,9 +15,6 @@ xmlns:georss='http://www.georss.org/georss'
xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#' xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#'
xmlns:atom10='http://www.w3.org/2005/Atom' xmlns:atom10='http://www.w3.org/2005/Atom'
xmlns:atom='http://www.w3.org/2005/Atom'> xmlns:atom='http://www.w3.org/2005/Atom'>
<!-- Atom 1.0 Syndication Format -->
<xsl:output
media-type='application/atom+xml' />
<xsl:template match='/atom:feed'> <xsl:template match='/atom:feed'>
<!-- index right-to-left language codes --> <!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang --> <!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
@ -38,7 +35,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:call-template> </xsl:call-template>
<xsl:call-template name='metadata'> <xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' /> <xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='StreamBurner' /> <xsl:with-param name='content' select='Rivista' />
</xsl:call-template> </xsl:call-template>
<xsl:call-template name='metadata'> <xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' /> <xsl:with-param name='name' select='"mimetype"' />
@ -46,14 +43,17 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:call-template> </xsl:call-template>
<title> <title>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:title and not(atom:title="")'> <xsl:when test='atom:title and not(atom:title="") and count(atom:entry) &gt; 1'>
<xsl:value-of select='atom:title'/> <xsl:value-of select='atom:title'/>
</xsl:when> </xsl:when>
<xsl:otherwise>StreamBurner</xsl:otherwise> <xsl:when test='atom:entry'>
<xsl:value-of select='atom:entry/atom:title'/>
</xsl:when>
<xsl:otherwise>Rivista</xsl:otherwise>
</xsl:choose> </xsl:choose>
</title> </title>
<!-- TODO media='print' --> <!-- TODO media='print' -->
<link href='/css/stylesheet.css' rel='stylesheet' type='text/css' media='screen'/> <link rel='stylesheet' type='text/css' media='screen' href='/css/stylesheet.css'/>
<link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/> <link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/>
<!-- whether language code is of direction right-to-left --> <!-- whether language code is of direction right-to-left -->
<xsl:if test='$rtl'> <xsl:if test='$rtl'>
@ -64,18 +64,18 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</head> </head>
<body> <body>
<div id='actions'> <div id='actions'>
<a title='Subscribe the latest updates and news.' <a id='follow' title='Subscribe the latest updates and news.'
onclick='window.open("feed:" + location.href, "_self")'> onclick='window.open(location.href.replace(/^https?:/, "feed:"), "_self")'>
<!-- xsl:attribute name="href"> <!-- xsl:attribute name="href">
feed:<xsl:value-of select="atom:link[@rel='self']/@href" /> feed:<xsl:value-of select="atom:link[@rel='self']/@href" />
</xsl:attribute --> </xsl:attribute -->
Follow Follow
</a> </a>
<a title='Subscribe via SubToMe.'> <a id='subtome' title='Subscribe via SubToMe.'>
<xsl:attribute name="href"> <xsl:attribute name='href'>
https://www.subtome.com/#/subscribe?feeds=<xsl:value-of select="atom:link[@rel='self']/@href" /> javascript:location.href='https://www.subtome.com/#/subscribe?feeds='+location.href;
</xsl:attribute> </xsl:attribute>
<xsl:attribute name="onclick"> <xsl:attribute name='onclick'>
( (
function(btn){ function(btn){
var z=document.createElement('script'); var z=document.createElement('script');
@ -87,15 +87,6 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
SubToMe SubToMe
</a> </a>
<a href='https://git.xmpp-it.net/sch/PubSubToAtom'
title='About PubSub To Atom.'>
About
</a>
<a href='https://aboutfeeds.com/'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Feeds
</a>
<a href='https://xmpp.org/about/technology-overview/' <a href='https://xmpp.org/about/technology-overview/'
title='Of the benefits of XMPP.'> title='Of the benefits of XMPP.'>
XMPP XMPP
@ -104,6 +95,11 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
title='Syndictaion and PubSub.'> title='Syndictaion and PubSub.'>
Groupchat Groupchat
</a> </a>
<a href='https://aboutfeeds.com/'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Help
</a>
</div> </div>
<div id='feed'> <div id='feed'>
<div id='header'> <div id='header'>
@ -117,7 +113,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:value-of select='atom:entry/atom:title'/> <xsl:value-of select='atom:entry/atom:title'/>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
StreamBurner No title
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</h1> </h1>
@ -135,6 +131,9 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:attribute name='class'><xsl:text>date</xsl:text></xsl:attribute> <xsl:attribute name='class'><xsl:text>date</xsl:text></xsl:attribute>
<xsl:value-of select='atom:entry/atom:published'/> <xsl:value-of select='atom:entry/atom:published'/>
</xsl:when> </xsl:when>
<xsl:otherwise>
Rivista XMPP Journal Publisher
</xsl:otherwise>
</xsl:choose> </xsl:choose>
</h2> </h2>
</div> </div>
@ -142,12 +141,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<div id='menu'> <div id='menu'>
<h3>Latest Posts</h3> <h3>Latest Posts</h3>
<!-- xsl:for-each select='atom:entry[position() &lt;21]' --> <!-- xsl:for-each select='atom:entry[position() &lt;21]' -->
<ul> <ol>
<xsl:for-each select='atom:entry[not(position() &gt; 20)]'> <xsl:for-each select='atom:entry[not(position() &gt; 20)]'>
<li> <li>
<xsl:element name='a'> <xsl:element name='a'>
<xsl:attribute name='href'> <xsl:attribute name='href'>
<xsl:text>#stremburner-</xsl:text> <xsl:text>#rivista-</xsl:text>
<xsl:value-of select='position()'/> <xsl:value-of select='position()'/>
</xsl:attribute> </xsl:attribute>
<xsl:choose> <xsl:choose>
@ -161,7 +160,7 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:element> </xsl:element>
</li> </li>
</xsl:for-each> </xsl:for-each>
</ul> </ol>
</div> </div>
</xsl:if> </xsl:if>
<div id='articles'> <div id='articles'>
@ -177,19 +176,16 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:element name='a'> <xsl:element name='a'>
<xsl:attribute name='href'> <xsl:attribute name='href'>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:link[contains(@rel,"alternate")]'> <xsl:when test='atom:link[@rel="self"]'>
<xsl:value-of select='atom:link[contains(@rel,"alternate")]/@href'/> <xsl:value-of select='atom:link[@rel="self"]/@href'/>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
<xsl:value-of select='atom:link/@href'/> <xsl:value-of select='atom:link/@href'/>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</xsl:attribute> </xsl:attribute>
<xsl:attribute name='title'>
<xsl:value-of select='atom:title'/>
</xsl:attribute>
<xsl:attribute name='id'> <xsl:attribute name='id'>
<xsl:text>stremburner-</xsl:text> <xsl:text>rivista-</xsl:text>
<xsl:value-of select='position()'/> <xsl:value-of select='position()'/>
</xsl:attribute> </xsl:attribute>
<xsl:choose> <xsl:choose>
@ -227,10 +223,30 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</span> </span>
</xsl:when> </xsl:when>
</xsl:choose> </xsl:choose>
<!-- div class='posted' --> <!-- entry date -->
<xsl:element name='h4'>
<xsl:choose>
<xsl:when test='atom:updated'>
<xsl:attribute name='class'>
<xsl:text>updated</xsl:text>
</xsl:attribute>
<xsl:value-of select='atom:updated'/>
</xsl:when>
<xsl:when test='atom:published'>
<xsl:attribute name='class'>
<xsl:text>published</xsl:text>
</xsl:attribute>
<xsl:value-of select='atom:published'/>
</xsl:when>
<xsl:otherwise>
<h4 class='warning atom1 published'></h4>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
<!-- entry author --> <!-- entry author -->
<!-- xsl:if test='atom:author'> <xsl:if test='atom:author'>
<span class='author'> <h4 class='author'>
<xsl:text>By </xsl:text>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:author/atom:email'> <xsl:when test='atom:author/atom:email'>
<xsl:element name='a'> <xsl:element name='a'>
@ -256,33 +272,81 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:value-of select='atom:author/atom:name'/> <xsl:value-of select='atom:author/atom:name'/>
</xsl:element> </xsl:element>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:when test='atom:author/atom:name'>
<xsl:value-of select='atom:name'/> <xsl:value-of select='atom:author/atom:name'/>
</xsl:otherwise>
</xsl:choose>
</span>
</xsl:if -->
<!-- entry date -->
<xsl:choose>
<xsl:when test='atom:updated'>
<h4 class='updated'>
<xsl:value-of select='atom:updated'/>
</h4>
</xsl:when> </xsl:when>
<xsl:when test='atom:published'> <xsl:when test='atom:uri'>
<h4 class='published'> <xsl:value-of select='atom:uri'/>
<xsl:value-of select='atom:published'/>
</h4>
</xsl:when> </xsl:when>
<xsl:otherwise>
<h4 class='warning atom1 published'></h4>
</xsl:otherwise>
</xsl:choose> </xsl:choose>
<!-- /div --> </h4>
</xsl:if>
<h5 class='related'>
<xsl:if test='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]/@href'/>
</xsl:attribute>
<xsl:attribute name='class'>
<xsl:text>rivista-jabber</xsl:text>
</xsl:attribute>
💡️ Source
</xsl:element>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="alternate" and @type="x-scheme-handler/xmpp"]/@href'/>
</xsl:attribute>
(XMPP)
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="contact"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="contact"]/@href'/>
</xsl:attribute>
<xsl:attribute name='class'>
<xsl:text>contact-uri</xsl:text>
</xsl:attribute>
🪪️ Contact
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="replies"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="replies"]/@href'/>
</xsl:attribute>
<xsl:attribute name='class'>
<xsl:text>rivista-replies</xsl:text>
</xsl:attribute>
💬 Discussion
</xsl:element>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="replies"]/@href'/>
</xsl:attribute>
(XMPP)
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="alternate" and @type="text/html"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="alternate" and @type="text/html"]/@href'/>
</xsl:attribute>
📜 HTML
</xsl:element>
</xsl:if>
<xsl:if test='atom:link[@rel="related" and @type="text/html"]'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:link[@rel="related" and @type="text/html"]/@href'/>
</xsl:attribute>
📜 HTML (Related)
</xsl:element>
</xsl:if>
</h5>
<!-- entry content --> <!-- entry content -->
<!-- entry summary of GitLab Atom Syndication Feeds -->
<xsl:if test='atom:content or atom:summary'> <xsl:if test='atom:content or atom:summary'>
<p class='content'> <div class='content'>
<xsl:choose> <xsl:choose>
<xsl:when test='atom:summary[contains(@type,"text")]'> <xsl:when test='atom:summary[contains(@type,"text")]'>
<xsl:attribute name='type'> <xsl:attribute name='type'>
@ -290,6 +354,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select='atom:summary'/> <xsl:value-of select='atom:summary'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:summary[contains(@type,"html")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:summary/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:summary' disable-output-escaping='yes'/>
</xsl:when>
<xsl:when test='atom:summary[contains(@type,"base64")]'> <xsl:when test='atom:summary[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media --> <!-- TODO add xsl:template to handle inline media -->
</xsl:when> </xsl:when>
@ -299,6 +369,12 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select='atom:content'/> <xsl:value-of select='atom:content'/>
</xsl:when> </xsl:when>
<xsl:when test='atom:content[contains(@type,"html")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:content/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:content' disable-output-escaping='yes'/>
</xsl:when>
<xsl:when test='atom:content[contains(@type,"base64")]'> <xsl:when test='atom:content[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media --> <!-- TODO add xsl:template to handle inline media -->
</xsl:when> </xsl:when>
@ -313,12 +389,25 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</xsl:choose> </xsl:choose>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</p> </div>
</xsl:if>
<!-- entry tags -->
<xsl:if test='atom:category/@term'>
<h4>Tags</h4>
<span class='tags'>
<xsl:for-each select='atom:category'>
<xsl:element name='div'>
<xsl:value-of select='@term'/>
</xsl:element>
</xsl:for-each>
</span>
</xsl:if> </xsl:if>
<!-- entry enclosure --> <!-- entry enclosure -->
<xsl:if test='atom:link[contains(@rel,"enclosure")]'> <xsl:if test='atom:link[@rel="enclosure"]'>
<h4>Media</h4>
<span class='enclosures' title='Right-click and Save link as…'>
<xsl:for-each select='atom:link[@rel="enclosure"]'>
<div class='enclosure' title='Right-click and Save link as…'> <div class='enclosure' title='Right-click and Save link as…'>
<xsl:for-each select='atom:link[contains(@rel,"enclosure")]'>
<xsl:element name='span'> <xsl:element name='span'>
<xsl:attribute name='icon'> <xsl:attribute name='icon'>
<xsl:value-of select='substring-before(@type,"/")'/> <xsl:value-of select='substring-before(@type,"/")'/>
@ -343,55 +432,41 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:with-param name='length' select='@length' /> <xsl:with-param name='length' select='@length' />
</xsl:call-template> </xsl:call-template>
</xsl:if> </xsl:if>
<xsl:element name='br'/> <br/>
</xsl:for-each>
<xsl:for-each select='media:content'>
<xsl:element name='span'>
<xsl:attribute name='icon'>
<xsl:value-of select='@medium'/>
</xsl:attribute>
</xsl:element>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='@url'/>
</xsl:attribute>
<xsl:attribute name='download'/>
<xsl:call-template name='extract-filename'>
<xsl:with-param name='url' select='@url' />
</xsl:call-template>
</xsl:element>
<xsl:element name='span'>
<xsl:attribute name='class'>
<xsl:value-of select='@medium'/>
</xsl:attribute>
</xsl:element>
<xsl:if test='@fileSize &gt; 0'>
<xsl:call-template name='transform-filesize'>
<xsl:with-param name='length' select='@fileSize' />
</xsl:call-template>
</xsl:if>
<xsl:element name='br'/>
</xsl:for-each>
</div> </div>
</xsl:for-each>
</span>
</xsl:if> </xsl:if>
</div> </div>
<!-- entry id --> <!-- entry id -->
<xsl:if test='not(atom:id)'> <xsl:if test='not(atom:id)'>
<div class='warning atom1 id'></div> <div class='warning atom1 id'>No entry ID</div>
</xsl:if> </xsl:if>
</li> </li>
</xsl:for-each> </xsl:for-each>
</ul> </ul>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
<div class='notice no-entry'></div> <ul>
<li>
<div class='entry'>
<h3 class='title'>
<a href='javascript:alert("Please check that the mother PubSub node is populated with content.")'>
No content
</a>
</h3>
<h4>This entry is empty</h4>
<div class='content'>Please check that the mother PubSub node is populated with content.</div>
</div>
</li>
</ul>
</xsl:otherwise> </xsl:otherwise>
</xsl:choose> </xsl:choose>
</div> </div>
</div> </div>
<div id='references'> <div id='references'>
<a href='https://joinjabber.org/' <a href='https://joinjabber.org/'
title='An inclusive space on the Jabber network.'> title='An Inclusive Space On The Jabber Network.'>
JoinJabber JoinJabber
</a> </a>
<a href='https://libervia.org/' <a href='https://libervia.org/'
@ -399,15 +474,27 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
Libervia Libervia
</a> </a>
<a href='https://join.movim.eu/' <a href='https://join.movim.eu/'
title='The social platform shaped for your community.'> title='The Social Platform Shaped For Your Community.'>
Movim Movim
</a> </a>
<a href='https://git.xmpp-it.net/sch/Rivista'
title='A Journal Publisher And Browser For XMPP.'>
Rivista
</a>
<a href='https://git.xmpp-it.net/sch/Blasta'
title='A Social Bookmark Manager For XMPP.'>
Blasta
</a>
<a href='https://github.com/SeveFP/Reeder'
title='An XMPP-Based Feed Reader.'>
Reeder
</a>
<a href='https://modernxmpp.org/' <a href='https://modernxmpp.org/'
title='A project to improve the quality of user-to-user messaging applications that use XMPP.'> title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use XMPP.'>
Modern Modern
</a> </a>
<a href='https://xmpp.org/' <a href='https://xmpp.org/'
title='The universal messaging standard.'> title='The Universal Messaging Standard.'>
XMPP XMPP
</a> </a>
<a href='https://xmpp.org/extensions/xep-0060.html' <a href='https://xmpp.org/extensions/xep-0060.html'
@ -417,12 +504,25 @@ xmlns:atom='http://www.w3.org/2005/Atom'>
</div> </div>
<!-- note --> <!-- note -->
<p id='note'> <p id='note'>
This an Atom document which can also be viewed with
a Syndication Feed Reader (also referred to as News Reader
or RSS Reader) which provides automated news updates and
notifications on desktop and mobile.
<span id="selection-link">Click here</span> for a
selection of software and pick the ones that would fit
to you best!
</p>
<p id='small'>
<i> <i>
This is an XMPP news feed which is conveyed as HTML, This ASF (Atom Syndication Format)
and it can even be viewed by a syndication feed reader document is conveyed as an XHTML document.
which provides automated notifications on desktop and This document was produced by an
mobile. <span id="selection-link">Click here</span> for XSLT <a href="xsl/atom.xsl">stylesheet</a>.
a selection of software that would fit you best! XSLT is a powerful technology which transforms XML
documents into HTML, JSON, PDF, Text XHTML, and
(modified) XML documents;
<a href="https://www.w3.org/Style/XSL/">Learn more</a>
about The Extensible Stylesheet Language Family (XSL).
</i> </i>
</p> </p>
</body> </body>

20
xsl/opml.xsl Normal file
View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head>
-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding = 'UTF-8'
indent = 'yes'
media-type = 'text/x-opml'
method = 'html'
version = '4.01' />
<!-- Outline Processor Markup Language 1.0 -->
<xsl:include href='opml_as_xhtml.xsl'/>
<!-- set page metadata -->
<xsl:include href='metadata.xsl'/>
</xsl:stylesheet>

234
xsl/opml_as_xhtml.xsl Normal file
View file

@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2024 Schimon Jehuda. Released under MIT license
Feeds rendered using this XSLT stylesheet, or it's derivatives, must
include https://schimon.i2p/ in attribute name='generator' of
element <meta/> inside of html element </head>
-->
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:xml='http://www.w3.org/XML/1998/namespace'>
<xsl:template match='/opml'>
<!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
<xsl:variable name='rtl'
select='lang[
contains(self::node(),"ar") or
contains(self::node(),"fa") or
contains(self::node(),"he") or
contains(self::node(),"ji") or
contains(self::node(),"ku") or
contains(self::node(),"ur") or
contains(self::node(),"yi")]'/>
<html>
<head>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"description"' />
<xsl:with-param name='content' select='subtitle' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='Rivista' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' />
<xsl:with-param name='content' select='"text/x-opml"' />
</xsl:call-template>
<title>
<xsl:choose>
<xsl:when test='//head/title and not(//head/title="")'>
<xsl:value-of select='//head/title'/>
</xsl:when>
<xsl:otherwise>Rivista OPML</xsl:otherwise>
</xsl:choose>
</title>
<!-- TODO media='print' -->
<link rel='stylesheet' type='text/css' media='screen' href='/css/stylesheet.css'/>
<link rel='icon' type='image/svg+xml' href='/graphic/xmpp.svg'/>
<!-- whether language code is of direction right-to-left -->
<xsl:if test='$rtl'>
<link id='semitic' href='/css/stylesheet-rtl.css' rel='stylesheet' type='text/css' />
</xsl:if>
<script type='text/javascript' src='/script/postprocess.js'/>
</head>
<body>
<div id='actions'>
<a href='https://xmpp.org/about/technology-overview/'
title='Of the benefits of XMPP.'>
XMPP
</a>
<a href='https://join.jabber.network/#syndication@conference.movim.eu?join'
title='Syndictaion and PubSub.'>
Groupchat
</a>
<a href='https://aboutfeeds.com/'
title='Of the benefits of syndication feed.'
id='aboutfeeds'>
Help
</a>
</div>
<div id='feed'>
<div id='header'>
<!-- feed title -->
<h1 id='title'>
<xsl:choose>
<xsl:when test='//head/title and not(//head/title="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/title'/>
</xsl:when>
<xsl:otherwise>
Rivista OPML Collection
</xsl:otherwise>
</xsl:choose>
</h1>
<!-- feed subtitle -->
<h2 id='subtitle'>
<xsl:choose>
<xsl:when test='//head/description and not(//head/description="") and count(//outline) &gt; 1'>
<xsl:value-of select='//head/description'/>
</xsl:when>
<xsl:otherwise>
Outline Processor Markup Language
</xsl:otherwise>
</xsl:choose>
</h2>
</div>
<xsl:if test='count(//outline) &gt; 1'>
<div id='menu'>
<h3>Subscriptions</h3>
<!-- xsl:for-each select='outline[position() &lt;21]' -->
<ol>
<xsl:for-each select='//outline[not(position() &gt; 20)]'>
<li>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>#rivista-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</li>
</xsl:for-each>
</ol>
</div>
</xsl:if>
<div id='articles'>
<!-- opml outline -->
<xsl:choose>
<xsl:when test='//outline'>
<ul>
<xsl:for-each select='//outline[not(position() &gt; 20)]'>
<li>
<div class='entry'>
<!-- outline title -->
<h3 class='title'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='@xmlUrl'/>
</xsl:attribute>
<xsl:attribute name='id'>
<xsl:text>rivista-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(@text) &gt; 0'>
<xsl:value-of select='@text'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</h3>
<!-- entry content -->
<h4>
<xsl:value-of select='@text'/>
</h4>
<p class='content'>
<xsl:value-of select='@xmlUrl'/>
</p>
</div>
</li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
<div class='notice no-entry'></div>
</xsl:otherwise>
</xsl:choose>
</div>
</div>
<div id='references'>
<a href='https://joinjabber.org/'
title='An Inclusive Space On The Jabber Network.'>
JoinJabber
</a>
<a href='https://libervia.org/'
title='The Universal Communication Ecosystem.'>
Libervia
</a>
<a href='https://join.movim.eu/'
title='The Social Platform Shaped For Your Community.'>
Movim
</a>
<a href='https://git.xmpp-it.net/sch/Rivista'
title='A Journal Publisher And Browser For XMPP.'>
Rivista
</a>
<a href='https://git.xmpp-it.net/sch/Blasta'
title='A Social Bookmark Manager For XMPP.'>
Blasta
</a>
<a href='https://github.com/SeveFP/Reeder'
title='An XMPP-Based Feed Reader.'>
Reeder
</a>
<a href='https://modernxmpp.org/'
title='A Project To Improve The Quality Of User-To-User Messaging Applications That Use XMPP.'>
Modern
</a>
<a href='https://xmpp.org/'
title='The Universal Messaging Standard.'>
XMPP
</a>
<a href='https://xmpp.org/extensions/xep-0060.html'
title='XEP-0060: Publish-Subscribe.'>
PubSub
</a>
</div>
<!-- note -->
<p id='note'>
This is an OPML document which includes a collection of
subscriptions, and it can be imported to
a Syndication Feed Reader (also referred to as News Reader
or RSS Reader) which provides automated news updates and
notifications on desktop and mobile.
<span id="selection-link">Click here</span> for a
selection of software and pick the ones that would fit
to you best!
</p>
<p id='small'>
<i>
This OPML (Outline Processor Markup Language)
document is conveyed as an XHTML document.
This document was produced by an
XSLT <a href="xsl/opml.xsl">stylesheet</a>.
XSLT is a powerful technology which transforms XML
documents into HTML, JSON, PDF, Text XHTML, and
(modified) XML documents;
<a href="https://www.w3.org/Style/XSL/">Learn more</a>
about The Extensible Stylesheet Language Family (XSL).
</i>
</p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>