Modularize code;
Add packaging instructions; Add modules to handle Gemini file type (no Gemini server yet); Improve handling of configuration.
122
README.md
|
@ -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
|
@ -0,0 +1,3 @@
|
||||||
|
from rivista.version import __version__, __version_info__
|
||||||
|
|
||||||
|
print('Rivista XJP', __version__)
|
14
__main__.py
Normal 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)
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
724
assets/graphic/leechcraft.svg
Normal 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 |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
8
assets/script/iso8601_to_utc.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
7
assets/script/parse_markdown.js
Normal 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
|
@ -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>
|
501
assets/xsl/stylesheet_all_in_one_still_does_not_work_with_ff.xsl
Normal 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) > 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) > 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) > 1'>
|
||||||
|
<div id='menu'>
|
||||||
|
<h3>Latest Posts</h3>
|
||||||
|
<!-- xsl:for-each select='atom:entry[position() <21]' -->
|
||||||
|
<ul>
|
||||||
|
<xsl:for-each select='atom:entry[not(position() > 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) > 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) > 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 > 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 > 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 < 2'>
|
||||||
|
<xsl:value-of select='$length'/>
|
||||||
|
Byte
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='floor($length div 1024) < 1'>
|
||||||
|
<xsl:value-of select='$length'/>
|
||||||
|
Bytes
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test='floor($length div (1024 * 1024)) < 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)) < 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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
class GmiIndex:
|
52
gmi/markdown.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
|
@ -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
|
@ -0,0 +1,2 @@
|
||||||
|
__version__ = '1.0'
|
||||||
|
__version_info__ = (1, 0)
|
251
xml/atom.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|