Modularize code;

Add packaging instructions;
Add modules to handle Gemini file type (no Gemini server yet);
Improve handling of configuration.
This commit is contained in:
Schimon Jehudah, Adv. 2024-11-12 15:25:05 +02:00
parent 84e54085b5
commit 766e51af4c
46 changed files with 2359 additions and 663 deletions

122
README.md
View file

@ -1,18 +1,22 @@
# Rivista # Rivista XJP
Previously, XMPP Journal Publisher ("XJP") and XMPP PubSub To Atom ("XPTA"). Rivista XJP ("XMPP Journal Publisher"); previously XMPP PubSub To Atom ("XPTA").
Rivista is a software 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.
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)). 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)).
Rivista includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform PubSub nodes into static XHTML journal sites. Rivista includes [XSLT ](https://www.w3.org/TR/xslt/) stylesheets that transform
PubSub nodes into static XHTML journal sites.
Rivista was inspired from Tigase and was motivated by Movim. Rivista was inspired from Tigase and was motivated by Movim.
## Instances ## Instances
* https://rivista.woodpeckersnest.eu/ * https://rivista.woodpeckersnest.eu
## Preview ## Preview
@ -23,60 +27,90 @@ Rivista was inspired from Tigase and was motivated by Movim.
## Motivation ## Motivation
Rivista is a syndication project which makes journals and publications that are hosted on XMPP PubSub nodes, available Rivista is a syndication project which makes journals and publications that are
from HTTP to both, XML news readers and even HTML browsers. hosted on XMPP PubSub nodes, available from HTTP to both, XML news readers and
even HTML browsers.
This means that instead of hosting a journal or publication site in the old fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an HTTP server to operate Rivista, and the rest of the content is delivered from an XMPP server (i.e. PubSub nodes). This means that instead of hosting a journal or publication site in the old
fashion (i.e. HTML documents hosted on an HTTP server), one only has to have an
HTTP server to operate 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. The project also showcases the non-necessity of HTML, as it automatically
generates valid XHTML pages by HTML browsers (client-side) from XSLT
stylesheets.
Because Rivista reads XMPP PubSub nodes, it is possible to view a complete set of node items, and even a single node item, which means, that a considered and carefully earnest use of Rivista would save bandwidth and system overhead, which includes CPU, I/O and RAM usage. Because 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
* beautifulsoup4
* fastapi * fastapi
* lxml * lxml
* markdown
* slixmpp * slixmpp
* tomllib (Python <= 3.10) * tomllib (Python <= 3.10)
* uvicorn * uvicorn
## Installation ## Installation
### Debian-based Distro It is possible to install Rivista using pip and pipx.
```shell #### pip inside venv
# apt install python3 python3-fastapi python3-feedgenerator python3-lxml python3-slixmpp python3-tomli uvicorn
```
$ python3 -m venv .venv
$ source .venv/bin/activate
``` ```
### Download ##### Install
Extract the source package to a directory that you have permission to run software. ```
$ pip install git+https://git.xmpp-it.net/sch/Rivista
```
```shell #### pipx
$ git clone https://git.xmpp-it.net/sch/Rivista
$ cd Rivista/ ##### 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
``` ```
### Configure ### Configure
Add account credentials to file `configuration.toml`. Add account credentials to file `settings.toml`.
Copy file`settings.toml` to `~/.config/rivista/`.
Copy directories `css`, `graphics`, `img`, `script`, and `xsl` to
`~/.local/share/rivista/`.
Copy directory `json` to `~/.cache/rivista/`.
### Start ### Start
Execute Rivista with one of the following commands: ```
$ rivista
```shell
$ python -m uvicorn main: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
$ uvicorn pubsub_to_atom:app --host 127.0.0.1 --port 8000
$ 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 save system overhead, which includes CPU, I/O and RAM usage. It is possible to view a complete node and even a single item, which means, that
it is possible to save bandwidth and it further means that a considered and
carefully earnest use of this software would save system overhead, which
includes CPU, I/O and RAM usage.
### Viewing PubSub ### Viewing PubSub
@ -145,18 +179,34 @@ Python code is licensed under the license AGPL-3.0 only.
## Acknowledgement ## Acknowledgement
Thank you to Mr. Peter Saint-Andre ([stpeter](https://stpeter.im/journal/731.html)) for manifesting [Atom Over XMPP](https://www.ietf.org/archive/id/draft-saintandre-atompub-notify-07.html). Thank you to Mr. Peter Saint-Andre ([stpeter](https://stpeter.im/journal/731.html))
for manifesting [Atom Over XMPP](https://www.ietf.org/archive/id/draft-saintandre-atompub-notify-07.html).
Thank you to Mr. Wojtek and [Tigase](https://tigase.org/) for publicly exposing an implementation of PubSub as Syndication at [Sure.IM](https://sure.im/) as [feeds.tigase.im](http://feeds.tigase.im/atom?server=pubsub@sure.im&node=news). Thank you to Mr. Wojtek and [Tigase](https://tigase.org/) for publicly exposing
an implementation of PubSub as Syndication at [Sure.IM](https://sure.im/) as
[feeds.tigase.im](http://feeds.tigase.im/atom?server=pubsub@sure.im&node=news).
Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) who consistently and earnestly showcases the potential of PubSub as a publication platform with project [Movim](https://movim.eu/). Thank you to to Mr. Timothée Jaussoin ([edhelas](https://edhelas.movim.eu/)) who
consistently and earnestly showcases the potential of PubSub as a publication
platform with project [Movim](https://movim.eu/).
A special thank you to the gentlemen "d3x" and "cchianel" from IRC channel #python on irc.libera.chat for initial references concerning code, servers and FastAPI. A special thank you to the gentlemen "d3x" and "cchianel" from IRC channel
#python on irc.libera.chat for initial references concerning code, servers and
FastAPI.
And an important thank you to Mr. Simone Canaletti ([roughnecks](https://blog.woodpeckersnest.space/)) for testing and deploying Rivista into production. And an important thank you to Mr. Simone Canaletti
([roughnecks](https://blog.woodpeckersnest.space)) for testing and deploying
Rivista into production.
## Similar Projects ## 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.
# Home site
- https://schapps.woodpeckersnest.eu/rivista/
- gemini://woodpeckersnest.space/~schapps/rivista.gmi

3
__init__.py Normal file
View file

@ -0,0 +1,3 @@
from rivista.version import __version__, __version_info__
print('Rivista XJP', __version__)

14
__main__.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#import importlib.resources
from rivista.http.instance import HttpInstance
import uvicorn
def main():
http_instance = HttpInstance()
return http_instance.app
app = main()
uvicorn.run(app, host='localhost', port=8000, reload=False)

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View file

@ -0,0 +1,724 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="96"
height="96"
id="svg2408"
inkscape:version="0.48.1 r9760"
sodipodi:docname="main.svg"
inkscape:export-filename="/home/buckstabu/Dropbox/Public/lcicons/lcproto2.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1020"
id="namedview68"
showgrid="false"
inkscape:zoom="6.6666666"
inkscape:cx="53.537878"
inkscape:cy="36.716849"
inkscape:window-x="-8"
inkscape:window-y="-4"
inkscape:window-maximized="1"
inkscape:current-layer="svg2408" />
<defs
id="defs2410">
<linearGradient
id="linearGradient4017">
<stop
style="stop-color:#ff6600;stop-opacity:1;"
offset="0"
id="stop4019" />
<stop
style="stop-color:#ff1e00;stop-opacity:1;"
offset="1"
id="stop4021" />
</linearGradient>
<linearGradient
id="linearGradient4145">
<stop
style="stop-color:#ff5d00;stop-opacity:1;"
offset="0"
id="stop4147" />
<stop
style="stop-color:#00b1ff;stop-opacity:0;"
offset="1"
id="stop4149" />
</linearGradient>
<linearGradient
id="linearGradient3224">
<stop
id="stop3226"
style="stop-color:#42394b;stop-opacity:1"
offset="0" />
<stop
id="stop3228"
style="stop-color:#000000;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3465">
<stop
id="stop3467"
style="stop-color:#919191;stop-opacity:1"
offset="0" />
<stop
id="stop3469"
style="stop-color:#fdfdfd;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3187">
<stop
id="stop3189"
style="stop-color:#b4b4b4;stop-opacity:1"
offset="0" />
<stop
id="stop3191"
style="stop-color:#e6e6e6;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
x1="45.447727"
y1="92.539597"
x2="45.447727"
y2="7.0165396"
id="ButtonShadow"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.0058652,0.994169)">
<stop
id="stop3750"
style="stop-color:#000000;stop-opacity:1"
offset="0" />
<stop
id="stop3752"
style="stop-color:#000000;stop-opacity:0.58823532"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3737">
<stop
id="stop3739"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3741"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
color-interpolation-filters="sRGB"
id="filter3174">
<feGaussianBlur
id="feGaussianBlur3176"
stdDeviation="1.71" />
</filter>
<linearGradient
x1="36.357143"
y1="6"
x2="36.357143"
y2="63.893143"
id="linearGradient3188"
xlink:href="#linearGradient3737"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="48"
y1="90"
x2="48"
y2="5.9877172"
id="linearGradient3427"
xlink:href="#linearGradient3187"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="47.098362"
cy="78.286942"
r="38.957481"
fx="47.098362"
fy="78.286942"
id="radialGradient3205"
xlink:href="#linearGradient3224"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3830053,-1.0350102e-8,0,1.3123934,-18.038924,-24.218731)" />
<linearGradient
x1="43.17857"
y1="13"
x2="43.17857"
y2="83.294571"
id="linearGradient3279"
xlink:href="#linearGradient3465"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0294118,0,0,1.0294118,-1.4117647,-1.411765)" />
<linearGradient
id="linearGradient3737-7">
<stop
id="stop3739-2"
style="stop-color:#ffffff;stop-opacity:1;"
offset="0" />
<stop
id="stop3741-7"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<radialGradient
cx="48"
cy="90.171875"
r="42"
fx="48"
fy="90.171875"
id="radialGradient2874"
xlink:href="#linearGradient3737-7"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1573129,0,0,0.99590774,-7.551021,0.1971319)" />
<linearGradient
id="linearGradient3737-4">
<stop
id="stop3739-8"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3741-71"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<clipPath
id="clipPath3613">
<rect
width="84"
height="84"
rx="6"
ry="6"
x="6"
y="6"
id="rect3615"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</clipPath>
<filter
x="-0.192"
y="-0.192"
width="1.3839999"
height="1.3839999"
color-interpolation-filters="sRGB"
id="filter3794">
<feGaussianBlur
id="feGaussianBlur3796"
stdDeviation="5.28" />
</filter>
<linearGradient
x1="48"
y1="20.220806"
x2="48"
y2="138.66119"
id="linearGradient2908"
xlink:href="#linearGradient3737-4"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="45.447727"
y1="92.539597"
x2="45.447727"
y2="7.0165396"
id="ButtonShadow-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0058652,0,0,0.994169,100,0)">
<stop
id="stop3750-8"
style="stop-color:#000000;stop-opacity:1"
offset="0" />
<stop
id="stop3752-5"
style="stop-color:#000000;stop-opacity:0.58823532"
offset="1" />
</linearGradient>
<linearGradient
x1="32.251034"
y1="6.1317081"
x2="32.251034"
y2="90.238609"
id="linearGradient3780"
xlink:href="#ButtonShadow-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0238095,0,0,1.0119048,-1.1428571,-98.071429)" />
<linearGradient
x1="32.251034"
y1="6.1317081"
x2="32.251034"
y2="90.238609"
id="linearGradient3772"
xlink:href="#ButtonShadow-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0238095,0,0,1.0119048,-1.1428571,-98.071429)" />
<linearGradient
x1="32.251034"
y1="6.1317081"
x2="32.251034"
y2="90.238609"
id="linearGradient3725"
xlink:href="#ButtonShadow-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0238095,0,0,1.0119048,-1.1428571,-98.071429)" />
<linearGradient
x1="32.251034"
y1="6.1317081"
x2="32.251034"
y2="90.238609"
id="linearGradient3721"
xlink:href="#ButtonShadow-0"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-97)" />
<linearGradient
x1="32.251034"
y1="6.1317081"
x2="32.251034"
y2="90.238609"
id="linearGradient3224-3"
xlink:href="#ButtonShadow-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0238095,0,0,1.0119048,-1.1428571,-98.071429)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3737-4"
id="linearGradient3053"
gradientUnits="userSpaceOnUse"
x1="48"
y1="20.220806"
x2="48"
y2="138.66119" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3224"
id="radialGradient3071"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3830053,-1.0350102e-8,0,1.3123934,-17.140614,-23.729195)"
cx="47.098362"
cy="78.286942"
fx="47.098362"
fy="78.286942"
r="38.957481" />
<linearGradient
id="linearGradient3304">
<stop
id="stop3306"
style="stop-color:#ff8313;stop-opacity:1"
offset="0" />
<stop
id="stop3308"
style="stop-color:#ffe400;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3304"
id="linearGradient3023"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.2146893,0,0,1.2146893,-90.000003,-246.69493)"
x1="143.75"
y1="334.5"
x2="143.75"
y2="276" />
<linearGradient
id="linearGradient3045">
<stop
id="stop3047"
style="stop-color:#ff8313;stop-opacity:1"
offset="0" />
<stop
id="stop3049"
style="stop-color:#ffe400;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3737"
id="linearGradient3894"
x1="49.010517"
y1="48.650372"
x2="73.444344"
y2="48.650372"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(2.1,0)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3737"
id="linearGradient3896"
x1="23.037859"
y1="48.650372"
x2="47.471485"
y2="48.650372"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(2.1,0)" />
<radialGradient
inkscape:collect="always"
xlink:href="#ButtonShadow-0"
id="radialGradient3908"
cx="61.227432"
cy="48.650372"
fx="61.227432"
fy="48.650372"
r="12.216913"
gradientTransform="translate(2.1,7.9644297e-6)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#ButtonShadow-0"
id="radialGradient3910"
cx="35.254673"
cy="48.650372"
fx="35.254673"
fy="48.650372"
r="12.216814"
gradientTransform="translate(2.1,-5.9733707e-6)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3224"
id="radialGradient3955"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4908884,-1.1072317e-8,0,1.4039702,-22.221992,-28.734351)"
cx="47.098362"
cy="78.286942"
fx="47.098362"
fy="78.286942"
r="38.957481" />
<radialGradient
cx="47.098362"
cy="78.286942"
r="38.957481"
fx="47.098362"
fy="78.286942"
id="radialGradient3205-1"
xlink:href="#linearGradient3224-6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3830053,-1.0350102e-8,0,1.3123934,-18.038924,-24.218731)" />
<linearGradient
id="linearGradient3224-6">
<stop
id="stop3226-2"
style="stop-color:#42394b;stop-opacity:1"
offset="0" />
<stop
id="stop3228-1"
style="stop-color:#000000;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
x1="48"
y1="90"
x2="48"
y2="5.9877172"
id="linearGradient3427-0"
xlink:href="#linearGradient3187-5"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3187-5">
<stop
id="stop3189-3"
style="stop-color:#b4b4b4;stop-opacity:1"
offset="0" />
<stop
id="stop3191-2"
style="stop-color:#e6e6e6;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
x1="36.357143"
y1="6"
x2="36.357143"
y2="63.893143"
id="linearGradient3188-1"
xlink:href="#linearGradient3737-9"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3737-9">
<stop
id="stop3739-7"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3741-6"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
x1="43.17857"
y1="13"
x2="43.17857"
y2="83.294571"
id="linearGradient3279-8"
xlink:href="#linearGradient3465-2"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0294118,0,0,1.0294118,-1.4117647,-1.411765)" />
<linearGradient
id="linearGradient3465-2">
<stop
id="stop3467-0"
style="stop-color:#919191;stop-opacity:1"
offset="0" />
<stop
id="stop3469-6"
style="stop-color:#fdfdfd;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="48"
cy="90.171875"
r="42"
fx="48"
fy="90.171875"
id="radialGradient2874-4"
xlink:href="#linearGradient3737-7-3"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1573129,0,0,0.99590774,-7.551021,0.1971319)" />
<linearGradient
id="linearGradient3737-7-3">
<stop
id="stop3739-2-3"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3741-7-6"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
x1="48"
y1="20.220806"
x2="48"
y2="138.66119"
id="linearGradient2908-9"
xlink:href="#linearGradient3737-4-0"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3737-4-0">
<stop
id="stop3739-8-4"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3741-71-8"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<clipPath
id="clipPath3613-5">
<rect
width="84"
height="84"
rx="6"
ry="6"
x="6"
y="6"
id="rect3615-1"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</clipPath>
<filter
x="-0.192"
y="-0.192"
width="1.3839999"
height="1.3839999"
color-interpolation-filters="sRGB"
id="filter3794-2">
<feGaussianBlur
id="feGaussianBlur3796-9"
stdDeviation="5.28" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3187-5"
id="linearGradient4094"
gradientUnits="userSpaceOnUse"
x1="48"
y1="90"
x2="48"
y2="5.9877172" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3737-9"
id="linearGradient4096"
gradientUnits="userSpaceOnUse"
x1="36.357143"
y1="6"
x2="36.357143"
y2="63.893143" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3465-2"
id="linearGradient4098"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0294118,0,0,1.0294118,-1.4117647,-1.411765)"
x1="43.17857"
y1="13"
x2="43.17857"
y2="83.294571" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3737-7-3"
id="radialGradient4100"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1573129,0,0,0.99590774,-7.551021,0.1971319)"
cx="48"
cy="90.171875"
fx="48"
fy="90.171875"
r="42" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3737-4-0"
id="linearGradient4102"
gradientUnits="userSpaceOnUse"
x1="48"
y1="20.220806"
x2="48"
y2="138.66119" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3224-6"
id="radialGradient4104"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3830053,-1.0350102e-8,0,1.3123934,-18.038924,-24.218731)"
cx="47.098362"
cy="78.286942"
fx="47.098362"
fy="78.286942"
r="38.957481" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3224-6"
id="radialGradient4113"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3830053,-1.0350102e-8,0,1.3123934,-17.140614,-23.729195)"
cx="47.098362"
cy="78.286942"
fx="47.098362"
fy="78.286942"
r="38.957481" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4017"
id="linearGradient4023"
x1="48.732876"
y1="37.021988"
x2="48.609169"
y2="60.526169"
gradientUnits="userSpaceOnUse" />
</defs>
<metadata
id="metadata2413">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="opacity:0.6;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3977-9"
inkscape:connector-curvature="0"
d="m 17.89831,14.489536 c -1.662,0 -3,1.338 -3,3 l 0,62 c 0,1.662 1.338,3 3,3 l 62,0 c 1.662,0 3,-1.338 3,-3 l 0,-62 c 0,-1.662 -1.338,-3 -3,-3 l -62,0 z m 0,0.96875 62,0 c 1.140081,0 2.03125,0.891169 2.03125,2.03125 l 0,62 c 0,1.140081 -0.891169,2.03125 -2.03125,2.03125 l -62,0 c -1.140081,0 -2.03125,-0.891169 -2.03125,-2.03125 l 0,-62 c 0,-1.140081 0.891169,-2.03125 2.03125,-2.03125 z" />
<g
id="layer2"
style="display:none">
<rect
width="86"
height="85"
rx="6"
ry="6"
x="5"
y="7"
id="rect3745"
style="opacity:0.9;fill:url(#ButtonShadow);fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3174)" />
</g>
<rect
style="fill:url(#radialGradient4113);fill-opacity:1;stroke:none"
id="rect3461-7"
y="8.743969"
x="9.0391903"
ry="6.1732869"
rx="5.8527675"
height="78.516495"
width="77.914963" />
<path
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;opacity:0.15;color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Abandoned Bitplane;-inkscape-font-specification:Abandoned Bitplane"
id="rect3222-3-8"
inkscape:connector-curvature="0"
d="m 19.89831,13.489536 c -3.289406,0 -6,2.710594 -6,6 l 0,58 c 0,3.289406 2.710594,6 6,6 l 58,0 c 3.289406,0 6,-2.710594 6,-6 l 0,-58 c 0,-3.289406 -2.710594,-6 -6,-6 l -58,0 z m 0,4 58,0 c 1.142594,0 2,0.857406 2,2 l 0,58 c 0,1.142594 -0.857406,2 -2,2 l -58,0 c -1.142594,0 -2,-0.857406 -2,-2 l 0,-58 c 0,-1.142594 0.857406,-2 2,-2 z" />
<path
style="opacity:0.3;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3981-0"
inkscape:connector-curvature="0"
d="m 17.89831,14.489536 c -1.662,0 -3,1.338 -3,3 l 0,62 c 0,1.662 1.338,3 3,3 l 62,0 c 1.662,0 3,-1.338 3,-3 l 0,-62 c 0,-1.662 -1.338,-3 -3,-3 l -62,0 z m 0,1.9375 62,0 c 0.618163,0 1.0625,0.444337 1.0625,1.0625 l 0,62 c 0,0.618163 -0.444337,1.0625 -1.0625,1.0625 l -62,0 c -0.618163,0 -1.0625,-0.444337 -1.0625,-1.0625 l 0,-62 c 0,-0.618163 0.444337,-1.0625 1.0625,-1.0625 z" />
<g
id="g3912"
style="fill:url(#linearGradient4023);fill-opacity:1.0;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
transform="matrix(1.2125503,0,0,1.2125503,-13.04106,-10.991024)">
<path
d="m 26.099813,37.395511 0,22.509723 22.50972,0 0,-7.118459 -15.198872,0 0,-15.391264 -7.310848,0 z"
id="rect3294"
style="fill:url(#linearGradient4023);fill-opacity:1.0;stroke:#000000;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
d="m 52.072568,37.395511 0,22.509723 22.509727,0 0,-7.310849 -15.583656,0 0,-7.888025 15.583656,0 0,-7.310849 -22.509727,0 z"
id="rect3298"
style="fill:url(#linearGradient4023);fill-opacity:1.0;stroke:#000000;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
inkscape:connector-curvature="0" />
</g>
<path
style="opacity:0.07999998;fill:#d9c6ed;fill-opacity:1;stroke:none"
id="rect3256-3"
inkscape:connector-curvature="0"
d="m 14.89831,8.7395363 c -3.242433,0 -5.8437503,2.7674997 -5.8437503,6.1874997 l 0,49.0625 C 27.878614,50.130638 54.866224,40.195879 86.96081,42.208286 l 0,-27.28125 c 0,-3.420001 -2.63257,-6.1874997 -5.875,-6.1874997 l -66.1875,0 z" />
<g
id="layer1"
style="display:inline">
<path
d="M 12,6 C 8.676,6 6,8.676 6,12 l 0,72 c 0,3.324 2.676,6 6,6 l 72,0 c 3.324,0 6,-2.676 6,-6 L 90,12 C 90,8.676 87.324,6 84,6 L 12,6 z m 5,7 62,0 c 2.216001,0 4,1.784 4,4 l 0,62 c 10e-7,2.216001 -1.784,4 -4,4 l -62,0 c -2.215999,10e-7 -4,-1.784 -4,-4 l 0,-62 c 0,-2.215999 1.784,-4 4,-4 z"
inkscape:connector-curvature="0"
id="rect2419"
style="fill:url(#linearGradient4094);fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="M 12,6 C 8.676,6 6,8.676 6,12 l 0,2 0,68 0,2 c 0,0.334721 0.04135,0.6507 0.09375,0.96875 0.0487,0.295596 0.09704,0.596915 0.1875,0.875 0.00988,0.03038 0.020892,0.0636 0.03125,0.09375 0.098865,0.287771 0.2348802,0.547452 0.375,0.8125 0.1445918,0.273507 0.3156161,0.535615 0.5,0.78125 0.1843839,0.245635 0.3737765,0.473472 0.59375,0.6875 0.439947,0.428056 0.94291,0.814526 1.5,1.09375 0.278545,0.139612 0.5734731,0.246947 0.875,0.34375 -0.2562018,-0.100222 -0.4867109,-0.236272 -0.71875,-0.375 -0.00741,-0.0044 -0.023866,0.0045 -0.03125,0 -0.031933,-0.0193 -0.062293,-0.04251 -0.09375,-0.0625 -0.120395,-0.0767 -0.2310226,-0.163513 -0.34375,-0.25 -0.1061728,-0.0808 -0.2132809,-0.161112 -0.3125,-0.25 C 8.4783201,88.557317 8.3087904,88.373362 8.15625,88.1875 8.0486711,88.057245 7.9378561,87.922215 7.84375,87.78125 7.818661,87.74287 7.805304,87.69538 7.78125,87.65625 7.716487,87.553218 7.6510225,87.451733 7.59375,87.34375 7.4927417,87.149044 7.3880752,86.928049 7.3125,86.71875 7.30454,86.69694 7.288911,86.6782 7.28125,86.65625 7.2494249,86.5643 7.2454455,86.469419 7.21875,86.375 7.1884177,86.268382 7.1483606,86.171969 7.125,86.0625 7.0521214,85.720988 7,85.364295 7,85 L 7,83 7,15 7,13 C 7,10.218152 9.2181517,8 12,8 l 2,0 68,0 2,0 c 2.781848,0 5,2.218152 5,5 l 0,2 0,68 0,2 c 0,0.364295 -0.05212,0.720988 -0.125,1.0625 -0.04415,0.206893 -0.08838,0.397658 -0.15625,0.59375 -0.0077,0.02195 -0.0233,0.04069 -0.03125,0.0625 -0.06274,0.173739 -0.138383,0.367449 -0.21875,0.53125 -0.04158,0.0828 -0.07904,0.169954 -0.125,0.25 -0.0546,0.09721 -0.126774,0.18835 -0.1875,0.28125 -0.09411,0.140965 -0.204921,0.275995 -0.3125,0.40625 -0.143174,0.17445 -0.303141,0.346998 -0.46875,0.5 -0.01117,0.0102 -0.01998,0.02115 -0.03125,0.03125 -0.138386,0.125556 -0.285091,0.234436 -0.4375,0.34375 -0.102571,0.07315 -0.204318,0.153364 -0.3125,0.21875 -0.0074,0.0045 -0.02384,-0.0044 -0.03125,0 -0.232039,0.138728 -0.462548,0.274778 -0.71875,0.375 0.301527,-0.0968 0.596455,-0.204138 0.875,-0.34375 0.55709,-0.279224 1.060053,-0.665694 1.5,-1.09375 0.219973,-0.214028 0.409366,-0.441865 0.59375,-0.6875 0.184384,-0.245635 0.355408,-0.507743 0.5,-0.78125 0.14012,-0.265048 0.276135,-0.524729 0.375,-0.8125 0.01041,-0.03078 0.02133,-0.06274 0.03125,-0.09375 0.09046,-0.278085 0.1388,-0.579404 0.1875,-0.875 C 89.95865,84.6507 90,84.334721 90,84 l 0,-2 0,-68 0,-2 C 90,8.676 87.324,6 84,6 L 12,6 z"
inkscape:connector-curvature="0"
id="rect3728"
style="opacity:0.5;fill:url(#linearGradient4096);fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 17,12 c -2.752703,0 -5,2.247297 -5,5 l 0,62 c 0,2.752703 2.247298,5 5,5 l 62,0 c 2.752703,0 5.000001,-2.247297 5,-5 l 0,-62 c 0,-2.752703 -2.247297,-5 -5,-5 l -62,0 z m 0,2 62,0 c 1.679298,0 3,1.320703 3,3 l 0,62 c 10e-7,1.679297 -1.320703,3 -3,3 l -62,0 c -1.679296,0 -3,-1.320703 -3,-3 l 0,-62 c 0,-1.679297 1.320703,-3 3,-3 z"
inkscape:connector-curvature="0"
id="rect3425"
style="fill:url(#linearGradient4098);fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="M 12,90 C 8.676,90 6,87.324 6,84 L 6,82 6,14 6,12 c 0,-0.334721 0.04135,-0.6507 0.09375,-0.96875 0.0487,-0.295596 0.09704,-0.596915 0.1875,-0.875 C 6.29113,10.12587 6.302142,10.09265 6.3125,10.0625 6.411365,9.774729 6.5473802,9.515048 6.6875,9.25 6.8320918,8.976493 7.0031161,8.714385 7.1875,8.46875 7.3718839,8.223115 7.5612765,7.995278 7.78125,7.78125 8.221197,7.353194 8.72416,6.966724 9.28125,6.6875 9.559795,6.547888 9.8547231,6.440553 10.15625,6.34375 9.9000482,6.443972 9.6695391,6.580022 9.4375,6.71875 c -0.00741,0.0044 -0.023866,-0.0045 -0.03125,0 -0.031933,0.0193 -0.062293,0.04251 -0.09375,0.0625 -0.120395,0.0767 -0.2310226,0.163513 -0.34375,0.25 -0.1061728,0.0808 -0.2132809,0.161112 -0.3125,0.25 C 8.4783201,7.442683 8.3087904,7.626638 8.15625,7.8125 8.0486711,7.942755 7.9378561,8.077785 7.84375,8.21875 7.818661,8.25713 7.805304,8.30462 7.78125,8.34375 7.716487,8.446782 7.6510225,8.548267 7.59375,8.65625 7.4927417,8.850956 7.3880752,9.071951 7.3125,9.28125 7.30454,9.30306 7.288911,9.3218 7.28125,9.34375 7.2494249,9.4357 7.2454455,9.530581 7.21875,9.625 7.1884177,9.731618 7.1483606,9.828031 7.125,9.9375 7.0521214,10.279012 7,10.635705 7,11 l 0,2 0,68 0,2 c 0,2.781848 2.2181517,5 5,5 l 2,0 68,0 2,0 c 2.781848,0 5,-2.218152 5,-5 l 0,-2 0,-68 0,-2 C 89,10.635705 88.94788,10.279012 88.875,9.9375 88.83085,9.730607 88.78662,9.539842 88.71875,9.34375 88.71105,9.3218 88.69545,9.30306 88.6875,9.28125 88.62476,9.107511 88.549117,8.913801 88.46875,8.75 88.42717,8.6672 88.38971,8.580046 88.34375,8.5 88.28915,8.40279 88.216976,8.31165 88.15625,8.21875 88.06214,8.077785 87.951329,7.942755 87.84375,7.8125 87.700576,7.63805 87.540609,7.465502 87.375,7.3125 87.36383,7.3023 87.35502,7.29135 87.34375,7.28125 87.205364,7.155694 87.058659,7.046814 86.90625,6.9375 86.803679,6.86435 86.701932,6.784136 86.59375,6.71875 c -0.0074,-0.0045 -0.02384,0.0044 -0.03125,0 -0.232039,-0.138728 -0.462548,-0.274778 -0.71875,-0.375 0.301527,0.0968 0.596455,0.204138 0.875,0.34375 0.55709,0.279224 1.060053,0.665694 1.5,1.09375 0.219973,0.214028 0.409366,0.441865 0.59375,0.6875 0.184384,0.245635 0.355408,0.507743 0.5,0.78125 0.14012,0.265048 0.276135,0.524729 0.375,0.8125 0.01041,0.03078 0.02133,0.06274 0.03125,0.09375 0.09046,0.278085 0.1388,0.579404 0.1875,0.875 C 89.95865,11.3493 90,11.665279 90,12 l 0,2 0,68 0,2 c 0,3.324 -2.676,6 -6,6 l -72,0 z"
inkscape:connector-curvature="0"
id="path3615"
style="opacity:0.4;fill:url(#radialGradient4100);fill-opacity:1;fill-rule:nonzero;stroke:none" />
<rect
width="66"
height="66"
rx="12"
ry="12"
x="15"
y="15"
clip-path="url(#clipPath3613-5)"
id="rect3171"
style="opacity:0.1;fill:url(#linearGradient4102);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3794-2)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

View file

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,8 @@
// 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();
}
}

View file

@ -0,0 +1,7 @@
// Parse Markdown
window.onload = function(){
for (element of document.querySelectorAll('#articles > ul > li > div > p')) {
element.innerHTML = marked.parse(element.textContent);
}
}

29
assets/xsl/stylesheet.xsl Normal file
View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2017 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
method = 'html'
indent = 'yes'
omit-xml-decleration='no' />
<!-- Atom Syndication Format 1.0 -->
<xsl:include href='atom_as_xhtml.xsl'/>
<!-- extract filename from given url string -->
<xsl:include href='extract-filename.xsl'/>
<!-- set page metadata -->
<xsl:include href='metadata.xsl'/>
<!-- transform filesize from given length string -->
<xsl:include href='transform-filesize.xsl'/>
</xsl:stylesheet>

View file

@ -0,0 +1,501 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2016 - 2017 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'
xmlns:media='http://search.yahoo.com/mrss/'
xmlns:georss='http://www.georss.org/georss'
xmlns:geo='http://www.w3.org/2003/01/geo/wgs84_pos#'
xmlns:atom10='http://www.w3.org/2005/Atom'
xmlns:atom='http://www.w3.org/2005/Atom'>
<xsl:output
method = 'html'
indent = 'yes'
omit-xml-decleration='no' />
<!-- Atom 1.0 Syndication Format -->
<xsl:template match='/atom:feed'>
<!-- index right-to-left language codes -->
<!-- TODO http://www.w3.org/TR/xpath/#function-lang -->
<xsl:variable name='rtl'
select='@xml: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='atom:subtitle' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"generator"' />
<xsl:with-param name='content' select='StreamBurner' />
</xsl:call-template>
<xsl:call-template name='metadata'>
<xsl:with-param name='name' select='"mimetype"' />
<xsl:with-param name='content' select='"application/atom+xml"' />
</xsl:call-template>
<title>
<xsl:choose>
<xsl:when test='atom:title and not(atom:title="")'>
<xsl:value-of select='atom:title'/>
</xsl:when>
<xsl:otherwise>StreamBurner</xsl:otherwise>
</xsl:choose>
</title>
<!-- TODO media='print' -->
<link href='/css/stylesheet.css' rel='stylesheet' type='text/css' media='screen'/>
<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/marked.min.js'/>
<script type='text/javascript' src='/script/postprocess.js'/>
</head>
<body>
<div id='actions'>
<a id='follow' title='Subscribe the latest updates and news.'
onclick='window.open(location.href.replace(/^https?:/, "feed:"), "_self")'>
<!-- xsl:attribute name="href">
feed:<xsl:value-of select="atom:link[@rel='self']/@href" />
</xsl:attribute -->
Follow
</a>
<a id='subtome' title='Subscribe via SubToMe.'>
<xsl:attribute name='href'>
javascript:location.href='https://www.subtome.com/#/subscribe?feeds='+location.href;
</xsl:attribute>
<xsl:attribute name='onclick'>
(
function(btn){
var z=document.createElement('script');
document.subtomeBtn=btn;
z.src='https://www.subtome.com/load.js';document.body.appendChild(z);
}
)(this);
return false;
</xsl:attribute>
SubToMe
</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/'
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>
</div>
<div id='feed'>
<div id='header'>
<!-- feed title -->
<h1 id='title'>
<xsl:choose>
<xsl:when test='atom:title and not(atom:title="") and count(atom:entry) &gt; 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>
StreamBurner
</xsl:otherwise>
</xsl:choose>
</h1>
<!-- feed subtitle -->
<h2 id='subtitle'>
<xsl:choose>
<xsl:when test='atom:title and not(atom:title="") and count(atom:entry) &gt; 1'>
<xsl:value-of select='atom:subtitle'/>
</xsl:when>
<xsl:when test='atom:entry'>
<xsl:attribute name='class'><xsl:text>date</xsl:text></xsl:attribute>
<xsl:value-of select='atom:entry/atom:updated'/>
</xsl:when>
<xsl:when test='atom:entry'>
<xsl:attribute name='class'><xsl:text>date</xsl:text></xsl:attribute>
<xsl:value-of select='atom:entry/atom:published'/>
</xsl:when>
</xsl:choose>
</h2>
</div>
<xsl:if test='count(atom:entry) &gt; 1'>
<div id='menu'>
<h3>Latest Posts</h3>
<!-- xsl:for-each select='atom:entry[position() &lt;21]' -->
<ul>
<xsl:for-each select='atom:entry[not(position() &gt; 20)]'>
<li>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>#stremburner-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(atom:title) &gt; 0'>
<xsl:value-of select='atom:title'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</li>
</xsl:for-each>
</ul>
</div>
</xsl:if>
<div id='articles'>
<!-- feed entry -->
<xsl:choose>
<xsl:when test='atom:entry'>
<ul>
<xsl:for-each select='atom:entry[not(position() >20)]'>
<li>
<div class='entry'>
<!-- entry title -->
<h3 class='title'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:choose>
<xsl:when test='atom:link[contains(@rel,"alternate")]'>
<xsl:value-of select='atom:link[contains(@rel,"alternate")]/@href'/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='atom:link/@href'/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name='title'>
<xsl:value-of select='atom:title'/>
</xsl:attribute>
<xsl:attribute name='id'>
<xsl:text>stremburner-</xsl:text>
<xsl:value-of select='position()'/>
</xsl:attribute>
<xsl:choose>
<xsl:when test='string-length(atom:title) &gt; 0'>
<xsl:value-of select='atom:title'/>
</xsl:when>
<xsl:otherwise>
*** No Title ***
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</h3>
<!-- geographic location -->
<xsl:choose>
<xsl:when test='geo:lat and geo:long'>
<xsl:variable name='lat' select='geo:lat'/>
<xsl:variable name='lng' select='geo:long'/>
<span class='geolocation'>
<a href='geo:{$lat},{$lng}'>📍</a>
</span>
</xsl:when>
<xsl:when test='geo:Point'>
<xsl:variable name='lat' select='geo:Point/geo:lat'/>
<xsl:variable name='lng' select='geo:Point/geo:long'/>
<span class='geolocation'>
<a href='geo:{$lat},{$lng}'>📍</a>
</span>
</xsl:when>
<xsl:when test='georss:point'>
<xsl:variable name='lat' select='substring-before(georss:point, " ")'/>
<xsl:variable name='lng' select='substring-after(georss:point, " ")'/>
<xsl:variable name='name' select='georss:featurename'/>
<span class='geolocation'>
<a href='geo:{$lat},{$lng}' title='{$name}'>📍</a>
</span>
</xsl:when>
</xsl:choose>
<!-- div class='posted' -->
<!-- entry author -->
<!-- xsl:if test='atom:author'>
<span class='author'>
<xsl:choose>
<xsl:when test='atom:author/atom:email'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:text>mailto:</xsl:text>
<xsl:value-of select='atom:author/atom:email'/>
</xsl:attribute>
<xsl:attribute name='title'>
<xsl:text>Send an Email to </xsl:text>
<xsl:value-of select='atom:author/atom:email'/>
</xsl:attribute>
<xsl:value-of select='atom:author/atom:name'/>
</xsl:element>
</xsl:when>
<xsl:when test='atom:author/atom:uri'>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='atom:author/atom:uri'/>
</xsl:attribute>
<xsl:attribute name='title'>
<xsl:value-of select='atom:author/atom:summary'/>
</xsl:attribute>
<xsl:value-of select='atom:author/atom:name'/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='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 test='atom:published'>
<h4 class='published'>
<xsl:value-of select='atom:published'/>
</h4>
</xsl:when>
<xsl:otherwise>
<h4 class='warning atom1 published'></h4>
</xsl:otherwise>
</xsl:choose>
<!-- /div -->
<!-- entry content -->
<!-- entry summary of GitLab Atom Syndication Feeds -->
<xsl:if test='atom:content or atom:summary'>
<p class='content'>
<xsl:choose>
<xsl:when test='atom:summary[contains(@type,"text")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:summary/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:summary'/>
</xsl:when>
<xsl:when test='atom:summary[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media -->
</xsl:when>
<xsl:when test='atom:content[contains(@type,"text")]'>
<xsl:attribute name='type'>
<xsl:value-of select='atom:content/@type'/>
</xsl:attribute>
<xsl:value-of select='atom:content'/>
</xsl:when>
<xsl:when test='atom:content[contains(@type,"base64")]'>
<!-- TODO add xsl:template to handle inline media -->
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test='atom:summary and not(atom:summary="")'>
<xsl:value-of select='atom:summary' disable-output-escaping='yes'/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='atom:content' disable-output-escaping='yes'/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</p>
</xsl:if>
<!-- entry enclosure -->
<xsl:if test='atom:link[contains(@rel,"enclosure")]'>
<div class='enclosure' title='Right-click and Save link as…'>
<xsl:for-each select='atom:link[contains(@rel,"enclosure")]'>
<xsl:element name='span'>
<xsl:attribute name='icon'>
<xsl:value-of select='substring-before(@type,"/")'/>
</xsl:attribute>
</xsl:element>
<xsl:element name='a'>
<xsl:attribute name='href'>
<xsl:value-of select='@href'/>
</xsl:attribute>
<xsl:attribute name='download'/>
<xsl:call-template name='extract-filename'>
<xsl:with-param name='url' select='@href' />
</xsl:call-template>
</xsl:element>
<xsl:element name='span'>
<xsl:attribute name='class'>
<xsl:value-of select='substring-before(@type,"/")'/>
</xsl:attribute>
</xsl:element>
<xsl:if test='@length &gt; 0'>
<xsl:call-template name='transform-filesize'>
<xsl:with-param name='length' select='@length' />
</xsl:call-template>
</xsl:if>
<xsl:element name='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>
</xsl:if>
</div>
<!-- entry id -->
<xsl:if test='not(atom:id)'>
<div class='warning atom1 id'></div>
</xsl:if>
</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://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'>
<i>
This is an XMPP news feed which is conveyed as HTML,
and it can even be viewed by a syndication feed reader
which provides automated notifications on desktop and
mobile. <span id="selection-link">Click here</span> for
a selection of software that would fit you best!
</i>
</p>
</body>
</html>
</xsl:template>
<!-- extract filename from given url string -->
<!-- extract filename from given url string -->
<xsl:template name='extract-filename'>
<xsl:param name='url'/>
<xsl:choose>
<xsl:when test='contains($url,"/")'>
<xsl:call-template name='extract-filename'>
<xsl:with-param name='url' select='substring-after($url,"/")'/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='$url'/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- set page metadata -->
<!-- set page metadata -->
<xsl:template name='metadata'>
<xsl:param name='name'/>
<xsl:param name='content'/>
<xsl:if test='$content and not($content="")'>
<xsl:element name='meta'>
<xsl:attribute name='name'>
<xsl:value-of select='$name'/>
</xsl:attribute>
<xsl:attribute name='content'>
<xsl:value-of select='$content'/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:template>
<!-- transform filesize from given length string -->
<!-- transform filesize from given length string -->
<xsl:template name='transform-filesize'>
<xsl:param name='length'/>
<!-- TODO consider xsl:decimal-format and xsl:number -->
<xsl:choose>
<!-- TODO consider removal of Byte -->
<xsl:when test='$length &lt; 2'>
<xsl:value-of select='$length'/>
Byte
</xsl:when>
<xsl:when test='floor($length div 1024) &lt; 1'>
<xsl:value-of select='$length'/>
Bytes
</xsl:when>
<xsl:when test='floor($length div (1024 * 1024)) &lt; 1'>
<xsl:value-of select='floor($length div 1024)'/>.<xsl:value-of select='substring($length mod 1024,0,2)'/>
KiB
</xsl:when>
<xsl:when test='floor($length div (1024 * 1024 * 1024)) &lt; 1'>
<xsl:value-of select='floor($length div (1024 * 1024))'/>.<xsl:value-of select='substring($length mod (1024 * 1024),0,2)'/>
MiB
</xsl:when>
<xsl:otherwise>
<!-- P2P links -->
<xsl:value-of select='floor($length div (1024 * 1024 * 1024))'/>.<xsl:value-of select='substring($length mod (1024 * 1024 * 1024),0,2)'/>
GiB
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

116
config.py Normal file
View file

@ -0,0 +1,116 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Functions get_directory() were taken from project jarun/buku.
By Arun Prakash Jana (jarun) and Dmitry Marakasov (AMDmi3).
"""
import os
import sys
try:
import tomllib
except:
import tomli as tomllib
class Settings:
def get_directory():
"""
Determine the directory path where setting files be stored.
* If $XDG_CONFIG_HOME is defined, use it;
* else if $HOME exists, use it;
* else if the platform is Windows, use %APPDATA%;
* else use the current directory.
Returns
-------
str
Path to configuration directory.
"""
# config_home = xdg.BaseDirectory.xdg_config_home
config_home = os.environ.get('XDG_CONFIG_HOME')
if config_home is None:
if os.environ.get('HOME') is None:
if sys.platform == 'win32':
config_home = os.environ.get('APPDATA')
if config_home is None:
return os.path.abspath('.')
else:
return os.path.abspath('.')
else:
config_home = os.path.join(
os.environ.get('HOME'), '.config'
)
return os.path.join(config_home, 'rivista')
def get_setting(filename, section):
with open(filename, mode="rb") as settings:
result = tomllib.load(settings)[section]
return result
class Data:
def get_directory():
"""
Determine the directory path where data files be stored.
* If $XDG_DATA_HOME is defined, use it;
* else if $HOME exists, use it;
* else if the platform is Windows, use %APPDATA%;
* else use the current directory.
Returns
-------
str
Path to database file.
"""
# data_home = xdg.BaseDirectory.xdg_data_home
data_home = os.environ.get('XDG_DATA_HOME')
if data_home is None:
if os.environ.get('HOME') is None:
if sys.platform == 'win32':
data_home = os.environ.get('APPDATA')
if data_home is None:
return os.path.abspath('.rivista/data')
else:
return os.path.abspath('.rivista/data')
else:
data_home = os.path.join(
os.environ.get('HOME'), '.local', 'share'
)
return os.path.join(data_home, 'rivista')
class Cache:
def get_directory():
"""
Determine the directory path where cache files be stored.
* If $XDG_CACHE_HOME is defined, use it;
* else if $HOME exists, use it;
* else if the platform is Windows, use %APPDATA%;
* else use the current directory.
Returns
-------
str
Path to cache directory.
"""
# cache_home = xdg.BaseDirectory.xdg_cache_home
cache_home = os.environ.get('XDG_CACHE_HOME')
if cache_home is None:
if os.environ.get('HOME') is None:
if sys.platform == 'win32':
cache_home = os.environ.get('APPDATA')
if cache_home is None:
return os.path.abspath('.rivista/cache')
else:
return os.path.abspath('.rivista/cache')
else:
cache_home = os.path.join(
os.environ.get('HOME'), '.cache'
)
return os.path.join(cache_home, 'rivista')

4
gmi/index.py Normal file
View file

@ -0,0 +1,4 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
class GmiIndex:

52
gmi/markdown.py Normal file
View file

@ -0,0 +1,52 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
class GmiMarkdown:
def convert_to_gmi(markdown_text):
lines = markdown_text.splitlines()
footnotes = []
footnote_counter = 1
output_lines = []
for line in lines:
# Process links in the format [text](url)
while True:
start_bracket = line.find('[')
if start_bracket == -1:
break # No more links in this line
end_bracket = line.find(']', start_bracket)
if end_bracket == -1:
break # Malformed link; exit loop
start_parenthesis = line.find('(', end_bracket)
if start_parenthesis == -1:
break # Malformed link; exit loop
end_parenthesis = line.find(')', start_parenthesis)
if end_parenthesis == -1:
break # Malformed link; exit loop
link_text = line[start_bracket + 1:end_bracket]
url = line[start_parenthesis + 1:end_parenthesis]
# Add footnote
footnotes.append(f" [{footnote_counter}]: {link_text}{url}")
footnote_marker = f"{link_text}[{footnote_counter}]"
footnote_counter += 1
# Replace link with footnote marker
line = line[:start_bracket] + footnote_marker + line[end_parenthesis + 1:]
# Remove Markdown header markers
if line.startswith('#'):
line = line.lstrip('# ').strip()
output_lines.append(line.strip())
# Combine output lines and footnotes
output_text = "\n".join(output_lines).strip()
footnotes_text = "\n".join(footnotes)
return f"{output_text}\n\n{footnotes_text}" if footnotes else output_text

79
gmi/post.py Normal file
View file

@ -0,0 +1,79 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#from markdown_text_clean import clean_text
#from md2gemini import md2gemini
#from rivista.gmi.markdown import GmiMarkdown
from rivista.html.gmi import HtmlGmi
from rivista.markdown.html import MarkdownHtml
class GmiPost:
def generate_gmi(atom: dict):
"""Generate an Gemini document."""
atom_title = atom['title']
atom_subtitle = atom['subtitle']
atom_header = f'# {atom_title}\n### {atom_subtitle}\n\n'
atom_items = []
for item in atom['items']:
item_title = item['title']
item_published = item['published']
item_updated = item['updated']
item_contents = ''
for content in item['contents']:
match content['type']:
case 'text':
content_html = MarkdownHtml.convert_to_html(content['text'])
content_text = HtmlGmi.convert_to_gmi(content_html)
#content_text = GmiMarkdown.convert_to_gmi(content['text'])
#content_text = md2gemini(content['text'], links="at-end")
#content_text = clean_text(content['text'])
item_contents += f'\n{content_text}\n'
case _ if content['type'] in ('html', 'xhtml'):
content_text = HtmlGmi.convert_to_gmi(content['text'])
item_contents += f'\n{content_text}\n'
case _:
content_text = content['text']
item_contents += f'\n```\n{content_text}\n```\n'
links = item['links'] if 'links' in item else None
item_links = ''
if links:
item_links = '\n### Related resources\n\n'
for link in links:
link_href = link['href']
link_rel = link['rel']
link_type = link['type']
if link_type:
item_links += f'=> {link_href} {link_rel} ({link_type})\n'
else:
item_links += f'=> {link_href} {link_rel}\n'
categories = item['categories'] if 'categories' in item else None
item_categories = ''
if categories:
item_categories = '\n### Categories\n\n'
for category in categories:
item_categories += f'{category}, '
item_categories = item_categories[0:len(item_categories)-2] + '.\n'
authors = item['authors'] if 'authors' in item else None
item_authors = ''
if authors:
item_authors = '\n### Authors\n\n'
for author in authors:
author_text = author['name'] or author['uri'] or author['email']
if 'email' in author and author['email']:
item_author_email = 'mailto:' + author['email']
item_authors += f'=> {author_text} {item_author_email}\n'
elif 'uri' in author and author['uri']:
item_author_uri = author['uri']
item_authors += f'=> {author_text} {item_author_uri}\n'
else:
item_authors += f'{author_text}\n'
atom_items. append(f'\n## {item_title}\n\n' +
f'Published: {item_published}\n' +
f'Updated: {item_updated}\n' +
item_contents +
item_links +
item_categories +
item_authors)
gmi_text = atom_header + '\n'.join(atom_items)
return gmi_text

51
html/gmi.py Normal file
View file

@ -0,0 +1,51 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
class HtmlGmi:
def convert_to_gmi(html_content):
soup = BeautifulSoup(html_content, 'html.parser')
footnotes = ['### References\n']
footnote_counter = 1
# Extract text and links
for a in soup.find_all('a'):
link_text = a.get_text()
url = a['href']
if not url.startswith('#') and not url.startswith('/'):
url_modified = url.replace(' ', '%20')
url_modified = url_modified.replace('(', '%28')
url_modified = url_modified.replace(')', '%29')
#footnotes.append(f"=> {url_modified} [{footnote_counter}]: {link_text}")
footnotes.append(f"=> {url_modified} [{footnote_counter}]: {url}")
footnote_marker = f"{link_text}[{footnote_counter}]"
a.replace_with(footnote_marker)
footnote_counter += 1
# Handle <code> tags
for code in soup.find_all('code'):
if code.string:
original_text = code.get_text()
modified_text = '`' + original_text + '`'
code.string.replace_with(modified_text)
# Handle <pre> tags
for pre in soup.find_all('pre'):
pre.insert_before('\n```\n') # Add Markdown code block start
pre.insert_after('\n```\n') # Add Markdown code block end
# Convert <ul> and <li> to Markdown bullet points
for ul in soup.find_all('ul'):
for li in ul.find_all('li'):
if li.string:
original_text = li.get_text()
modified_text = '- ' + original_text
li.string.replace_with(modified_text)
# Get the text without HTML tags
text = soup.get_text().strip().replace('#', '')
# Combine text with footnotes
return f"{text}\n\n" + "\n".join(footnotes) if footnotes else text

215
http/instance.py Normal file
View file

@ -0,0 +1,215 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from fastapi import FastAPI, Request, Response
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import os
from rivista.config import Cache, Data, Settings
from rivista.gmi.post import GmiPost
from rivista.json.index import JsonIndex
from rivista.xml.atom import XmlAtom
from rivista.xml.opml import XmlOpml
from rivista.xml.xhtml import XmlXhtml
from rivista.xml.xslt import XmlXslt
from rivista.xmpp.instance import XmppInstance
from rivista.xmpp.utilities import XmppUtilities
from rivista.xmpp.xep_0060 import XmppXep0060
class HttpInstance:
def __init__(self):
directory_settings = Settings.get_directory()
filename_settings = os.path.join(directory_settings, 'settings.toml')
credentials = Settings.get_setting(filename_settings, 'account')
settings = Settings.get_setting(filename_settings, 'settings')
default = Settings.get_setting(filename_settings, 'default')
operator = Settings.get_setting(filename_settings, 'settings')['operator']
directory_data = Data.get_directory()
directory_data_css = os.path.join(directory_data, 'css')
directory_data_graphic = os.path.join(directory_data, 'graphic')
directory_data_script = os.path.join(directory_data, 'script')
directory_data_xsl = os.path.join(directory_data, 'xsl')
filename_favicon = os.path.join(directory_data, 'img', 'favicon.ico')
directory_cache = Cache.get_directory()
directory_cache_json = os.path.join(directory_cache, 'json')
self.app = FastAPI()
# Mount static graphic, script and stylesheet directories
self.app.mount("/css", StaticFiles(directory=directory_data_css), name="css")
self.app.mount("/data", StaticFiles(directory=directory_cache_json), name="data")
self.app.mount("/graphic", StaticFiles(directory=directory_data_graphic), name="graphic")
self.app.mount("/script", StaticFiles(directory=directory_data_script), name="script")
self.app.mount("/xsl", StaticFiles(directory=directory_data_xsl), name="xsl")
@self.app.get('/favicon.ico', include_in_schema=False)
async def favicon():
return FileResponse(filename_favicon)
@self.app.route('/')
@self.app.get('/opml')
async def view_pubsub_nodes(request: Request):
xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect()
pubsub = request.query_params.get('pubsub', '')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub:
iq = await XmppXep0060.get_nodes(xmpp, pubsub)
if iq:
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = XmlOpml.generate_opml(iq)
result = XmlXslt.append_stylesheet(xml_opml, 'opml')
else:
text = 'Please ensure that PubSub "{}" (Jabber ID) is valid and accessible.'.format(pubsub)
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
if not result:
if default['pubsub']:
if not pubsub:
pubsub = default['pubsub']
iq = await XmppXep0060.get_nodes(xmpp, pubsub)
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = XmlOpml.generate_opml(iq)
result = XmlXslt.append_stylesheet(xml_opml, 'opml')
elif not settings['service']:
pubsub = default['pubsub']
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = XmlOpml.generate_opml(iq)
result = XmlXslt.append_stylesheet(xml_opml, 'opml')
else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
xmpp.disconnect()
response = Response(content=result, media_type="application/xml")
return response
@self.app.get('/atom')
async def view_node_items(request: Request):
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', '')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub and node and item_id:
iq = await XmppXep0060.get_node_item(xmpp, pubsub, node, item_id)
if iq:
link = XmppUtilities.form_an_item_link(pubsub, node, item_id)
if 'urn:xmpp:microblog:0:comments/' in node:
atom = XmlAtom.extract_atom(iq)
xml_atom = XmlAtom.generate_atom_comment(atom, pubsub, node, link)
else:
atom = XmlAtom.extract_atom(iq)
xml_atom = XmlAtom.generate_atom_post(atom, pubsub, node, link)
gmi_text = GmiPost.generate_gmi(atom)
iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
if not '/' in node:
if iq:
JsonIndex.generate_json(iq, directory_cache_json)
else:
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 = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
# try:
# iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
# JsonIndex.generate_json(iq, node)
# except:
# 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 XmppXep0060.get_node_items(xmpp, pubsub, node)
if iq:
link = XmppUtilities.form_a_node_link(pubsub, node)
if 'urn:xmpp:microblog:0:comments/' in node:
atom = XmlAtom.extract_atom(iq)
xml_atom = XmlAtom.generate_atom_comment(atom, pubsub, node, link)
else:
atom = XmlAtom.extract_atom(iq)
xml_atom = XmlAtom.generate_atom_post(atom, pubsub, node, link)
gmi_text = GmiPost.generate_gmi(atom)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
elif pubsub:
text = 'Node parameter is missing.'
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
elif node:
text = 'PubSub parameter is missing.'
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
# else:
# text = ('Mandatory parameter PubSub and '
# 'optional parameter Node are missing.')
# xml_atom = XmlAtom.error_message(text)
# result = XmlXslt.append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
if not result:
if default['pubsub'] and default['nodeid']:
if not pubsub and not node:
pubsub = default['pubsub']
node = default['nodeid']
iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
if iq:
link = XmppUtilities.form_a_node_link(pubsub, node)
atom = XmlAtom.extract_atom(iq)
xml_atom = XmlAtom.generate_atom_post(atom, pubsub, node, link)
gmi_text = GmiPost.generate_gmi(atom)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = XmlAtom.error_message(text)
elif not settings['service']:
pubsub = default['pubsub']
node = default['nodeid']
iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
if iq:
link = XmppUtilities.form_a_node_link(pubsub, node)
atom = XmlAtom.extract_atom(iq)
xml_atom = XmlAtom.generate_atom_post(atom, pubsub, node, link)
gmi_text = GmiPost.generate_gmi(atom)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = XmlAtom.error_message(text)
result = XmlXslt.append_stylesheet(xml_atom, 'atom')
xmpp.disconnect()
response = Response(content=result, media_type="application/xml")
return response

38
json/index.py Normal file
View file

@ -0,0 +1,38 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import json
import os
from rivista.xmpp.utilities import XmppUtilities
class JsonIndex:
def generate_json(iq, directory_cache_json):
"""Create a JSON file from node items."""
json_data = []
pubsub = iq['from'].bare
node = iq['pubsub']['items']['node']
entries = iq['pubsub']['items']
for entry in entries:
item_id = entry['id']
item_payload = entry['payload']
namespace = '{http://www.w3.org/2005/Atom}'
title = item_payload.find(namespace + 'title')
title_text = '*** No Title ***' if title == None else title.text
# updated = item.find(namespace + 'updated')
# updated = None if updated == None else updated.text
# if updated: updated = datetime.datetime.fromisoformat(updated)
link_href = XmppUtilities.form_an_item_link(pubsub, node, item_id)
# link = item.find(namespace + 'link')
# link_href = '' if link == None else link.attrib['href']
json_data_entry = {'title' : title_text,
'link' : link_href}
json_data.append(json_data_entry)
#if len(json_data) > 6: break
directory = os.path.join(directory_cache_json, pubsub)
if not os.path.exists(directory):
os.mkdir(directory)
filename = os.path.join(directory_cache_json, pubsub, node)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(json_data, f, ensure_ascii=False, indent=4)

11
markdown/html.py Normal file
View file

@ -0,0 +1,11 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import markdown
class MarkdownHtml:
def convert_to_html(markdown_content):
# Convert Markdown to HTML
html_content = markdown.markdown(markdown_content)
return html_content

View file

@ -1,627 +0,0 @@
#!/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 json
from os import mkdir
from os.path import exists
from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.stanza.iq import Iq
import xml.etree.ElementTree as ET
#import importlib.resources
try:
import tomllib
except:
import tomli as tomllib
class XmppInstance(ClientXMPP):
def __init__(self, jid, password):
super().__init__(jid, password)
self.register_plugin('xep_0060')
self.connect()
# self.process(forever=False)
class HttpInstance:
app = FastAPI()
# Mount static graphic, script and stylesheet directories
app.mount("/css", StaticFiles(directory="css"), name="css")
app.mount("/data", StaticFiles(directory="data"), name="data")
app.mount("/graphic", StaticFiles(directory="graphic"), name="graphic")
app.mount("/script", StaticFiles(directory="script"), name="script")
app.mount("/xsl", StaticFiles(directory="xsl"), name="xsl")
@app.get('/favicon.ico', include_in_schema=False)
async def favicon():
return FileResponse('favicon.ico')
@app.route('/')
@app.get('/opml')
async def view_pubsub_nodes(request: Request):
credentials = Utilities.get_configuration('account')
xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect()
pubsub = request.query_params.get('pubsub', '')
settings = Utilities.get_configuration('settings')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub:
iq = await XmppXep0060.get_nodes(xmpp, pubsub)
if iq:
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = Xml.generate_opml(iq)
result = Xml.append_stylesheet(xml_opml, 'opml')
else:
text = 'Please ensure that PubSub "{}" (Jabber ID) is valid and accessible.'.format(pubsub)
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
default = Utilities.get_configuration('default')
if not result:
if default['pubsub']:
if not pubsub:
pubsub = default['pubsub']
iq = await XmppXep0060.get_nodes(xmpp, pubsub)
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = Xml.generate_opml(iq)
result = Xml.append_stylesheet(xml_opml, 'opml')
elif not settings['service']:
pubsub = default['pubsub']
link = 'xmpp:{pubsub}'.format(pubsub=pubsub)
xml_opml = Xml.generate_opml(iq)
result = Xml.append_stylesheet(xml_opml, 'opml')
else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
xmpp.disconnect()
response = Response(content=result, media_type="application/xml")
return response
@app.get('/atom')
async def view_node_items(request: Request):
credentials = Utilities.get_configuration('account')
xmpp = XmppInstance(credentials['xmpp'], credentials['pass'])
# xmpp.connect()
pubsub = request.query_params.get('pubsub', '')
node = request.query_params.get('node', '')
item_id = request.query_params.get('item', '')
settings = Utilities.get_configuration('settings')
result = None
if settings['service']:
if settings['include'] in pubsub or not settings['include']:
if pubsub and node and item_id:
iq = await XmppXep0060.get_node_item(xmpp, pubsub, node, item_id)
if iq:
link = Utilities.form_an_item_link(pubsub, node, item_id)
if 'urn:xmpp:microblog:0:comments/' in node:
atom = Xml.extract_atom(iq)
xml_atom = Xml.generate_atom_comment(atom, pubsub, node, link)
else:
atom = Xml.extract_atom(iq)
xml_atom = Xml.generate_atom_post(atom, pubsub, node, link)
iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
if not '/' in node:
if iq:
Utilities.generate_json(iq)
else:
operator = Utilities.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 = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
# try:
# iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
# Utilities.generate_json(iq, node)
# except:
# operator = Utilities.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 XmppXep0060.get_node_items(xmpp, pubsub, node)
if iq:
link = Utilities.form_a_node_link(pubsub, node)
if 'urn:xmpp:microblog:0:comments/' in node:
atom = Xml.extract_atom(iq)
xml_atom = Xml.generate_atom_comment(atom, pubsub, node, link)
else:
atom = Xml.extract_atom(iq)
xml_atom = Xml.generate_atom_post(atom, pubsub, node, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
elif pubsub:
text = 'Node parameter is missing.'
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
elif node:
text = 'PubSub parameter is missing.'
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
# else:
# text = ('Mandatory parameter PubSub and '
# 'optional parameter Node are missing.')
# xml_atom = Xml.error_message(text)
# result = Xml.append_stylesheet(xml_atom, 'atom')
else:
text = 'The given domain {} is not allowed.'.format(pubsub)
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
if not result:
default = Utilities.get_configuration('default')
if default['pubsub'] and default['nodeid']:
if not pubsub and not node:
pubsub = default['pubsub']
node = default['nodeid']
iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
if iq:
link = Utilities.form_a_node_link(pubsub, node)
atom = Xml.extract_atom(iq)
xml_atom = Xml.generate_atom_post(atom, pubsub, node, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = Xml.error_message(text)
elif not settings['service']:
pubsub = default['pubsub']
node = default['nodeid']
iq = await XmppXep0060.get_node_items(xmpp, pubsub, node)
if iq:
link = Utilities.form_a_node_link(pubsub, node)
atom = Xml.extract_atom(iq)
xml_atom = Xml.generate_atom_post(atom, pubsub, node, link)
else:
text = 'Please ensure that PubSub node "{}" is valid and accessible.'.format(node)
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
else:
text = 'Please contact the administrator and ask him to set default PubSub and Node ID.'
xml_atom = Xml.error_message(text)
result = Xml.append_stylesheet(xml_atom, 'atom')
xmpp.disconnect()
response = Response(content=result, media_type="application/xml")
return response
class XmppXep0060:
async def get_node_item(self, pubsub, node, item_id):
try:
iq = await self.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(self, pubsub, node):
try:
iq = await self.plugin['xep_0060'].get_items(pubsub, node, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
async def get_nodes(self, pubsub):
try:
iq = await self.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
class Xml:
def error_message(text):
"""Error message in RFC 4287: The Atom Syndication Format."""
title = 'Rivista'
subtitle = 'XMPP Journal Publisher'
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 = 'en'
feed = ET.Element("feed")
feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(feed, 'title', {'type': 'text'}).text = title
ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(feed, 'author', {'name':'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')
"""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, type):
# Register namespace in order to avoide ns0:
if type == 'atom': 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_without_a_declaration = ET.tostring(tree, encoding='unicode')
# Add XML declaration and stylesheet
xml_data_declaration = (
'<?xml version="1.0" encoding="utf-8"?>'
'<?xml-stylesheet type="text/xsl" href="xsl/{}.xsl"?>'.format(type) +
xml_data_without_a_declaration)
return xml_data_declaration
def generate_opml(iq):
pubsub = iq['from'].bare
items = iq['disco_items']['items']
opml = ET.Element("opml")
opml.set("version", "1.0")
head = ET.SubElement(opml, "head")
ET.SubElement(head, "title").text = '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 = Utilities.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')
def extract_atom(iq: Iq):
"""Extract data from an Atom Syndication Format (RFC 4287) of a Publish-Subscribe (XEP-0060) node item."""
jid = iq['from'].bare
node = iq['pubsub']['items']['node']
atom = {}
atom['title'] = jid
atom['subtitle'] = node
atom['language'] = iq['pubsub']['items']['lang']
atom['items'] = []
items = iq['pubsub']['items']
for item in list(items)[::-1]:
atom_item = {}
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
title_text = 'No title' if title == None else title.text
atom_item['title'] = title_text
if isinstance(links, ET.Element):
atom_item['links'] = []
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 ''
atom_item['links'].append({'href': link_href,
'rel': link_rel,
'type': link_type})
contents = item_payload.find(namespace + 'content')
atom_item['contents'] = []
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'
atom_item['contents'].append({'text' : content_text,
'type' : content_type_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'
atom_item['contents'].append(summary_text)
# else:
# atom_item['contents'].append('No content.')
published = item_payload.find(namespace + 'published')
published_text = '' if published == None else published.text
atom_item['published'] = published_text
updated = item_payload.find(namespace + 'updated')
updated_text = '' if updated == None else updated.text
atom_item['updated'] = updated_text
atom_item['authors'] = []
authors = item_payload.find(namespace + 'author')
if isinstance(authors, ET.Element):
for author in item_payload.findall(namespace + 'author'):
atom_item_author = {}
author_email = author.find(namespace + 'email')
if author_email is not None:
author_email_text = author_email.text
if author_email_text:
atom_item_author['email'] = 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:
atom_item_author['uri'] = 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
atom_item_author['name'] = author_name_text
atom_item['authors'].append(atom_item_author)
categories = item_payload.find(namespace + 'category')
atom_item['categories'] = []
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']
atom_item['categories'].append(category_term)
identifier = item_payload.find(namespace + 'id')
if identifier is not None and identifier.attrib: print(identifier.attrib)
identifier_text = item['id'] if identifier == None else identifier.text
atom_item['id'] = identifier_text
#atom_item['id'] = item['id']
atom['items'].append(atom_item)
return atom
def generate_xhtml(atom: dict):
"""Generate an XHTML document."""
e_html = ET.Element('html')
e_html.set('xmlns', 'http://www.w3.org/1999/xhtml')
e_head = ET.SubElement(e_html, 'head')
ET.SubElement(e_head, 'title').text = atom['title']
ET.SubElement(e_head, 'link', {'rel': 'stylesheet',
'href': 'pubsub.css'})
e_body = ET.SubElement(e_html, "body")
ET.SubElement(e_body, "h1").text = atom['title']
ET.SubElement(e_body, "h2").text = atom['subtitle']
for item in atom['items']:
item_id = item['id']
title = item['title']
links = item['links']
e_article = ET.SubElement(e_body, 'article')
e_title = ET.SubElement(e_article, 'h3')
e_title.text = item['title']
e_title.set('id', item['id'])
e_date = ET.SubElement(e_article, 'h4')
e_date.text = item['published']
e_date.set('title', 'Updated: ' + item['updated'])
authors = item['authors']
if authors:
e_authors = ET.SubElement(e_article, "dl")
ET.SubElement(e_authors, "dt").text = 'Authors'
for author in authors:
e_dd = ET.SubElement(e_authors, 'dd')
e_author = ET.SubElement(e_dd, 'a')
e_author.text = author['name'] or author['uri'] or author['email']
if 'email' in author and author['email']:
e_author.set('href', 'mailto:' + author['email'])
elif 'uri' in author and author['uri']:
e_author.set('href', author['uri'])
for content in item['contents']:
ET.SubElement(e_article, 'p', {'type': content['type']}).text = content['text']
if links:
e_links = ET.SubElement(e_article, "dl")
e_links.set('class', 'links')
ET.SubElement(e_links, "dt").text = 'Links'
for link in links:
e_dd = ET.SubElement(e_links, 'dd')
e_link = ET.SubElement(e_dd, 'a')
e_link.set('href', link['href'])
e_link.text = link['rel']
if link['type']: ET.SubElement(e_dd, 'span').text = link['type']
categories = item['categories']
if categories:
e_categories = ET.SubElement(e_article, "dl")
e_categories.set('class', 'categories')
ET.SubElement(e_categories, "dt").text = 'Categories'
for category in categories:
ET.SubElement(e_categories, 'dd').text = category
return ET.tostring(e_html, encoding='unicode')
# generate_rfc_4287
def generate_atom_post(atom: dict, pubsub: str, node: str, link: str):
"""Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
# link = Utilities.form_a_node_link(pubsub, node)
# subtitle = 'XMPP PubSub Syndication Feed'
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.')
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = atom['title']
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = atom['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 atom['items']:
e_entry = ET.SubElement(e_feed, 'entry')
ET.SubElement(e_entry, 'title').text = item['title']
links = item['links']
if links:
for link in links:
ET.SubElement(e_entry, 'link', {'href': link['href'],
'rel': link['rel'],
'type': link['type']})
else:
# NOTE What does this instruction line do?
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = Utilities.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['contents']
if contents:
for content in contents:
ET.SubElement(e_entry, 'content', {'type': content['type']}).text = content['text']
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
ET.SubElement(e_entry, 'published').text = item['published']
ET.SubElement(e_entry, 'updated').text = item['updated']
authors = item['authors']
if authors:
for author in authors:
e_author = ET.SubElement(e_entry, 'author')
if 'email' in author and author['email']:
ET.SubElement(e_author, 'email').text = author['email']
if 'uri' in author and author['uri']:
ET.SubElement(e_entry, 'uri').text = author['uri']
ET.SubElement(e_author, 'uri').text = author['uri']
ET.SubElement(e_author, 'name').text = author['name'] or author['uri'] or author['email']
categories = item['categories']
if categories:
for category in categories:
ET.SubElement(e_entry, 'category', {'term': category})
ET.SubElement(e_entry, 'id').text = item['id']
return ET.tostring(e_feed, encoding='unicode')
# generate_rfc_4287
def generate_atom_comment(atom: dict, pubsub: str, node: str, link: str):
"""Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
# link = Utilities.form_a_node_link(pubsub, node)
# subtitle = 'XMPP PubSub Syndication Feed'
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.')
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = atom['title']
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = atom['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 atom['items']:
e_entry = ET.SubElement(e_feed, 'entry')
ET.SubElement(e_entry, 'title').text = item['authors'][0]['name']
links = item['links']
if links:
for link in links:
ET.SubElement(e_entry, 'link', {'href': link['href'],
'rel': link['rel'],
'type': link['type']})
else:
# NOTE What does this instruction line do?
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = Utilities.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['contents']
if contents:
for content in contents:
ET.SubElement(e_entry, 'content', {'type': content['type']}).text = content['text']
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
ET.SubElement(e_entry, 'published').text = item['published']
ET.SubElement(e_entry, 'updated').text = item['updated']
authors = item['authors']
if authors:
for author in authors:
e_author = ET.SubElement(e_entry, 'author')
if 'email' in author and author['email']:
ET.SubElement(e_author, 'email').text = author['email']
if 'uri' in author and author['uri']:
ET.SubElement(e_entry, 'uri').text = author['uri']
ET.SubElement(e_author, 'uri').text = author['uri']
ET.SubElement(e_author, 'name').text = author['name'] or author['uri'] or author['email']
categories = item['categories']
if categories:
for category in categories:
ET.SubElement(e_entry, 'category', {'term': category})
ET.SubElement(e_entry, 'id').text = item['id']
return ET.tostring(e_feed, encoding='unicode')
class Utilities:
def get_configuration(section):
with open('configuration.toml', mode="rb") as configuration:
result = tomllib.load(configuration)[section]
return result
def form_a_node_link(pubsub, node):
link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node)
return link
def form_an_item_link(pubsub, node, item_id):
link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
pubsub=pubsub, node=node, item=item_id)
return link
def generate_json(iq):
"""Create a JSON file from node items."""
json_data = []
pubsub = iq['from'].bare
node = iq['pubsub']['items']['node']
entries = iq['pubsub']['items']
for entry in entries:
item_id = entry['id']
item_payload = entry['payload']
namespace = '{http://www.w3.org/2005/Atom}'
title = item_payload.find(namespace + 'title')
title_text = '*** No Title ***' if title == None else title.text
# updated = item.find(namespace + 'updated')
# updated = None if updated == None else updated.text
# if updated: updated = datetime.datetime.fromisoformat(updated)
link_href = Utilities.form_an_item_link(pubsub, node, item_id)
# link = item.find(namespace + 'link')
# link_href = '' if link == None else link.attrib['href']
json_data_entry = {'title' : title_text,
'link' : link_href}
json_data.append(json_data_entry)
#if len(json_data) > 6: break
directory = 'data/{}/'.format(pubsub)
if not exists(directory):
mkdir(directory)
filename = 'data/{}/{}.json'.format(pubsub, node)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(json_data, f, ensure_ascii=False, indent=4)
def main():
http_instance = HttpInstance()
return http_instance.app
app = main()

2
version.py Normal file
View file

@ -0,0 +1,2 @@
__version__ = '1.0'
__version_info__ = (1, 0)

251
xml/atom.py Normal file
View file

@ -0,0 +1,251 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import datetime
from rivista.xmpp.utilities import XmppUtilities
from slixmpp.stanza.iq import Iq
import xml.etree.ElementTree as ET
class XmlAtom:
def error_message(text):
"""Error message in RFC 4287: The Atom Syndication Format."""
title = 'Rivista'
subtitle = 'XMPP Journal Publisher'
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 = 'en'
feed = ET.Element("feed")
feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(feed, 'title', {'type': 'text'}).text = title
ET.SubElement(feed, 'subtitle', {'type': 'text'}).text = subtitle
ET.SubElement(feed, 'author', {'name':'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')
def extract_atom(iq: Iq):
"""Extract data from an Atom Syndication Format (RFC 4287) of a Publish-Subscribe (XEP-0060) node item."""
jid = iq['from'].bare
node = iq['pubsub']['items']['node']
atom = {}
atom['title'] = jid
atom['subtitle'] = node
atom['language'] = iq['pubsub']['items']['lang']
atom['items'] = []
items = iq['pubsub']['items']
for item in list(items)[::-1]:
atom_item = {}
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
title_text = 'No title' if title == None else title.text
atom_item['title'] = title_text
if isinstance(links, ET.Element):
atom_item['links'] = []
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 ''
atom_item['links'].append({'href': link_href,
'rel': link_rel,
'type': link_type})
contents = item_payload.find(namespace + 'content')
atom_item['contents'] = []
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'
atom_item['contents'].append({'text' : content_text,
'type' : content_type_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'
atom_item['contents'].append(summary_text)
# else:
# atom_item['contents'].append('No content.')
published = item_payload.find(namespace + 'published')
published_text = '' if published == None else published.text
atom_item['published'] = published_text
updated = item_payload.find(namespace + 'updated')
updated_text = '' if updated == None else updated.text
atom_item['updated'] = updated_text
atom_item['authors'] = []
authors = item_payload.find(namespace + 'author')
if isinstance(authors, ET.Element):
for author in item_payload.findall(namespace + 'author'):
atom_item_author = {}
author_email = author.find(namespace + 'email')
if author_email is not None:
author_email_text = author_email.text
if author_email_text:
atom_item_author['email'] = 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:
atom_item_author['uri'] = 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
atom_item_author['name'] = author_name_text
atom_item['authors'].append(atom_item_author)
categories = item_payload.find(namespace + 'category')
atom_item['categories'] = []
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']
atom_item['categories'].append(category_term)
identifier = item_payload.find(namespace + 'id')
if identifier is not None and identifier.attrib: print(identifier.attrib)
identifier_text = item['id'] if identifier == None else identifier.text
atom_item['id'] = identifier_text
#atom_item['id'] = item['id']
atom['items'].append(atom_item)
return atom
# generate_rfc_4287
def generate_atom_post(atom: dict, pubsub: str, node: str, link: str):
"""Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
# link = XmppUtilities.form_a_node_link(pubsub, node)
# subtitle = 'XMPP PubSub Syndication Feed'
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.')
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = atom['title']
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = atom['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 atom['items']:
e_entry = ET.SubElement(e_feed, 'entry')
ET.SubElement(e_entry, 'title').text = item['title']
links = item['links'] if 'links' in item else None
if links:
for link in links:
ET.SubElement(e_entry, 'link', {'href': link['href'],
'rel': link['rel'],
'type': link['type']})
else:
# NOTE What does this instruction line do?
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = XmppUtilities.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['contents'] if 'contents' in item else None
if contents:
for content in contents:
ET.SubElement(e_entry, 'content', {'type': content['type']}).text = content['text']
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
ET.SubElement(e_entry, 'published').text = item['published']
ET.SubElement(e_entry, 'updated').text = item['updated']
authors = item['authors'] if 'authors' in item else None
if authors:
for author in authors:
e_author = ET.SubElement(e_entry, 'author')
if 'email' in author and author['email']:
ET.SubElement(e_author, 'email').text = author['email']
if 'uri' in author and author['uri']:
ET.SubElement(e_entry, 'uri').text = author['uri']
ET.SubElement(e_author, 'uri').text = author['uri']
ET.SubElement(e_author, 'name').text = author['name'] or author['uri'] or author['email']
categories = item['categories']
if categories:
for category in categories:
ET.SubElement(e_entry, 'category', {'term': category})
ET.SubElement(e_entry, 'id').text = item['id']
return ET.tostring(e_feed, encoding='unicode')
# generate_rfc_4287
def generate_atom_comment(atom: dict, pubsub: str, node: str, link: str):
"""Generate an Atom Syndication Format (RFC 4287) from a Publish-Subscribe (XEP-0060) node items."""
# link = XmppUtilities.form_a_node_link(pubsub, node)
# subtitle = 'XMPP PubSub Syndication Feed'
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.')
e_feed = ET.Element("feed")
e_feed.set('xmlns', 'http://www.w3.org/2005/Atom')
ET.SubElement(e_feed, 'title', {'type': 'text'}).text = atom['title']
ET.SubElement(e_feed, 'subtitle', {'type': 'text'}).text = atom['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 atom['items']:
e_entry = ET.SubElement(e_feed, 'entry')
ET.SubElement(e_entry, 'title').text = item['authors'][0]['name']
links = item['links'] if 'links' in item else None
if links:
for link in links:
ET.SubElement(e_entry, 'link', {'href': link['href'],
'rel': link['rel'],
'type': link['type']})
else:
# NOTE What does this instruction line do?
ET.SubElement(e_entry, 'content', {'href': ''})
link_xmpp = XmppUtilities.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['contents'] if 'contents' in item else None
if contents:
for content in contents:
ET.SubElement(e_entry, 'content', {'type': content['type']}).text = content['text']
else:
ET.SubElement(e_entry, 'content').text = 'No content.'
ET.SubElement(e_entry, 'published').text = item['published']
ET.SubElement(e_entry, 'updated').text = item['updated']
authors = item['authors'] if 'authors' in item else None
if authors:
for author in authors:
e_author = ET.SubElement(e_entry, 'author')
if 'email' in author and author['email']:
ET.SubElement(e_author, 'email').text = author['email']
if 'uri' in author and author['uri']:
ET.SubElement(e_entry, 'uri').text = author['uri']
ET.SubElement(e_author, 'uri').text = author['uri']
ET.SubElement(e_author, 'name').text = author['name'] or author['uri'] or author['email']
categories = item['categories']
if categories:
for category in categories:
ET.SubElement(e_entry, 'category', {'term': category})
ET.SubElement(e_entry, 'id').text = item['id']
return ET.tostring(e_feed, encoding='unicode')

32
xml/opml.py Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import datetime
from rivista.xmpp.utilities import XmppUtilities
import xml.etree.ElementTree as ET
class XmlOpml:
def generate_opml(iq):
"""Generate an OPML Collection document."""
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 = XmppUtilities.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')

62
xml/xhtml.py Normal file
View file

@ -0,0 +1,62 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import datetime
import xml.etree.ElementTree as ET
class XmlXhtml:
def generate_xhtml(atom: dict):
"""Generate an XHTML document."""
e_html = ET.Element('html')
e_html.set('xmlns', 'http://www.w3.org/1999/xhtml')
e_head = ET.SubElement(e_html, 'head')
ET.SubElement(e_head, 'title').text = atom['title']
ET.SubElement(e_head, 'link', {'rel': 'stylesheet',
'href': 'pubsub.css'})
e_body = ET.SubElement(e_html, "body")
ET.SubElement(e_body, "h1").text = atom['title']
ET.SubElement(e_body, "h2").text = atom['subtitle']
for item in atom['items']:
item_id = item['id']
title = item['title']
links = item['links']
e_article = ET.SubElement(e_body, 'article')
e_title = ET.SubElement(e_article, 'h3')
e_title.text = item['title']
e_title.set('id', item['id'])
e_date = ET.SubElement(e_article, 'h4')
e_date.text = item['published']
e_date.set('title', 'Updated: ' + item['updated'])
authors = item['authors']
if authors:
e_authors = ET.SubElement(e_article, "dl")
ET.SubElement(e_authors, "dt").text = 'Authors'
for author in authors:
e_dd = ET.SubElement(e_authors, 'dd')
e_author = ET.SubElement(e_dd, 'a')
e_author.text = author['name'] or author['uri'] or author['email']
if 'email' in author and author['email']:
e_author.set('href', 'mailto:' + author['email'])
elif 'uri' in author and author['uri']:
e_author.set('href', author['uri'])
for content in item['contents']:
ET.SubElement(e_article, 'p', {'type': content['type']}).text = content['text']
if links:
e_links = ET.SubElement(e_article, "dl")
e_links.set('class', 'links')
ET.SubElement(e_links, "dt").text = 'Links'
for link in links:
e_dd = ET.SubElement(e_links, 'dd')
e_link = ET.SubElement(e_dd, 'a')
e_link.set('href', link['href'])
e_link.text = link['rel']
if link['type']: ET.SubElement(e_dd, 'span').text = link['type']
categories = item['categories']
if categories:
e_categories = ET.SubElement(e_article, "dl")
e_categories.set('class', 'categories')
ET.SubElement(e_categories, "dt").text = 'Categories'
for category in categories:
ET.SubElement(e_categories, 'dd').text = category
return ET.tostring(e_html, encoding='unicode')

22
xml/xslt.py Normal file
View file

@ -0,0 +1,22 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
class XmlXslt:
"""This is a patch function to append XSLT reference to XML."""
"""Why is there no built-in function of ElementTree or LXML?"""
def append_stylesheet(xml_data, type):
# Register namespace in order to avoide ns0:
if type == 'atom': 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_without_a_declaration = ET.tostring(tree, encoding='unicode')
# Add XML declaration and stylesheet
xml_data_declaration = (
'<?xml version="1.0" encoding="utf-8"?>'
'<?xml-stylesheet type="text/xsl" href="xsl/{}.xsl"?>'.format(type) +
xml_data_without_a_declaration)
return xml_data_declaration

11
xmpp/instance.py Normal file
View file

@ -0,0 +1,11 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from slixmpp import ClientXMPP
class XmppInstance(ClientXMPP):
def __init__(self, jid, password):
super().__init__(jid, password)
self.register_plugin('xep_0060')
self.connect()
# self.process(forever=False)

14
xmpp/utilities.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
class XmppUtilities:
def form_a_node_link(pubsub, node):
link = 'xmpp:{pubsub}?;node={node}'.format(pubsub=pubsub, node=node)
return link
def form_an_item_link(pubsub, node, item_id):
link = 'xmpp:{pubsub}?;node={node};item={item}'.format(
pubsub=pubsub, node=node, item=item_id)
return link

27
xmpp/xep_0060.py Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from slixmpp.exceptions import IqError, IqTimeout
class XmppXep0060:
async def get_node_item(self, pubsub, node, item_id):
try:
iq = await self.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(self, pubsub, node):
try:
iq = await self.plugin['xep_0060'].get_items(pubsub, node, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)
async def get_nodes(self, pubsub):
try:
iq = await self.plugin['xep_0060'].get_nodes(pubsub, timeout=5)
return iq
except (IqError, IqTimeout) as e:
print(e)