This is the initial set of files of this project.

This commit is contained in:
Schimon Jehudah, Adv. 2024-10-08 10:13:40 +03:00
parent 48f767697d
commit 95c1f30ebc
15 changed files with 2212 additions and 1 deletions

View file

@ -1,3 +1,39 @@
# FASI
Fast And Sleek Invite (FASI)
## About
FASI is an HTML invite page for XMPP.
The main purpose, is to provide an interface to share XMPP contacts via HTML
browsers.
It also provides an interface to explore XMPP server conferences, pubsub and
other services.
FASI is written in Python and utilizes Jinja2, FastAPI and Slixmpp.
## Features
- MUC
- Photo
- PubSub
- QR code
- Service discovery
- vCard
## Install
Use the following commands, to begin FASI.
```shell
git clone https://git.xmpp-it.net/sch/FASI
cd FASI/
python -m uvicorn fasi:app
```
## License
AGPL-3.0-only
## Copyright
Schimon Jehudah Zachary 2024

5
configuration.toml Normal file
View file

@ -0,0 +1,5 @@
# An account to connect FASI to the XMPP network
[account]
xmpp = "" # Jabber ID
pass = "" # Password

384
css/stylesheet.css Normal file
View file

@ -0,0 +1,384 @@
* {
user-select: none;
}
div, h1, h2, h3, h4, h5 {
font-family: system-ui;
}
html {
height: 100%;
}
body {
background: url(/img/background.svg);
background-repeat: repeat;
/*height: 100vh;*/
/*
background: linear-gradient(-45deg, rgba(0,0,0,0) 25%, rgba(255,255,255,0.2) 25%, rgba(255,255,255,0.2) 50%, rgba(0,0,0,0) 50%, rgba(0,0,0,0) 75%, rgba(255,255,255,0.2) 75%), linear-gradient(45deg, rgba(0,0,0,0) 25%, rgba(255,255,255,0.2) 25%, rgba(255,255,255,0.2) 50%, rgba(0,0,0,0) 50%, rgba(0,0,0,0) 75%, rgba(255,255,255,0.2) 75%), rgb(2, 115, 127)
*/
/* background: url(/img/background.svg); */
/* background-repeat: repeat; */
/* NOTE Value "contain" can be useful for tiled background */
/* background-size: contain; */
/* background-size: 100vw; */
/* background-size: cover; */
margin: 0;
min-height: 100%;
}
/*
div:has(#bar) {
height: 100vh;
}
*/
#bar {
background: #f5f5f5;
filter: drop-shadow(0 0 4px grey);
min-height: 3em;
padding-top: 1em;
padding-bottom: 1em;
margin-bottom: 2.5em;
/* position: fixed;
width: 100%; */
z-index: 1;
}
#bar > * {
margin-left: 0.5em;
margin-right: 0.5em;
}
#logo {
height: 3em;
}
#xmpp-uri {
user-select: all;
}
input,
input[type="submit" i],
input:not([type="email" i], [type="number" i], [type="password" i], [type="search" i], [type="tel" i], [type="text" i], [type="url" i]),
input:not([type="file" i], [type="image" i], [type="checkbox" i], [type="radio" i]) {
all: unset;
}
label,
#action,
#exception,
#xmpp-uri,
#preview {
line-height: 3em; /* 2em */
}
#download {
float: right;
}
#jid {
font-size: 1.5em;
margin-bottom: 1em;
/* padding-bottom: 1em; */
}
#action,
#download,
#input {
border-radius: 26px;
font-size: 1.4em;
padding: 0.5em;
}
#action,
#input {
background: #13b5ea; /* #002b5c */
}
#action:hover,
#input:hover {
background: #1b3967;
}
#action,
#download,
#download-narrow,
#input {
color: #f5f5f5;
font-weight: bold;
padding-left: 2em;
padding-right: 2em;
text-decoration: none;
text-transform: uppercase;
}
#download,
#download-narrow {
background: #d9541e; /* #e96d1f */
}
#download:hover,
#download-narrow:hover {
background: #439639; /* #a0ce67 */
}
#download-narrow {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
font-size: 2em;
margin-bottom: 3em;
padding: 0.5em;
/* width: 60%; */
width: 360px;
}
#logo-narrow {
height: 1em;
padding-right: 1em;
}
#download-narrow,
#logo-bottom {
display: none;
}
#logo-bottom {
margin: auto;
margin-top: 4em;
/* padding-bottom: 2.5em; */
width: 30%;
}
h1 {
overflow-x: hidden;
/* padding: 10px; */
text-overflow: ellipsis;
}
/*
#count {
margin: 1em;
}
*/
#photo,
#qrcode {
border: 1px solid #c0c0c0;
/* width: 40%; */
/* margin-bottom: 2.5em; */
/* NOTE Reason for dimensions 276x276: To be in accord with generated QR Code. */
height: 276px;
width: 276px;
/*
max-height: 276px;
max-width: 276px;
min-height: 276px;
outline: solid;
outline-color: #7a7a7a;
*/
}
#photo {
background: #fff;
border-right: none;
/* border-radius: 100px; 500px */
border-bottom-left-radius: 50px;
border-top-left-radius: 50px;
margin-right: -3px;
object-fit: scale-down;
}
/*
NOTE
Use border to equalize element #phoro with #qrcode.
Perhaps also contain it within a span, in order to fill the background.
border: 1em solid #fff;
height: 240px;
width: 240px;
*/
#qrcode {
border-left: none;
border-bottom-right-radius: 50px;
border-top-right-radius: 50px;
margin-left: -3px;
}
h3, h4, h5 {
padding-left: 2em;
padding-right: 2em;
}
#profile {
background: #f5f5f5;
border-radius: 30px;
filter: drop-shadow(2px 4px 6px grey);
margin-bottom: 2.5em;
margin-left: auto;
margin-right: auto;
max-width: 55em;
/* FIXME
Elements of element #profile overflow upon decreasing height, once property
min-width has been added.
*/
min-width: 35em;
/* NOTE
The problem is not with elements within element #profile.
Issue has been fixed, once property max-height was commented.
If you would, add @media (max-height: 300px) for lower height.
*/
/* max-height: 75vh; */
padding-top: 1em;
padding-bottom: 3em;
text-align: center;
width: 80%;
}
#entries {
padding: 2em;
text-align: left;
}
.entry > * {
margin-bottom: 1em;
}
.summary {
white-space: pre-wrap;
word-break: break-word;
}
#services {
text-align: left;
}
#count > a,
#preview {
color: #5c5656;
text-decoration: none;
}
#count > a:hover,
#preview:hover {
color: #000;
text-decoration: underline;
}
#note {
color: #fff;
font-weight: bold;
padding-bottom: 1em;
text-align: center;
text-shadow: 1px 1px #000;
}
#message {
background: #000;
color: white;
font-weight: bold;
opacity: 10%;
padding: 1em;
position: fixed;
text-align: center;
bottom: 0;
left: 0;
right: 0;
}
#message:hover {
opacity: unset;
}
/* NOTE This rule useful, for larger images (800x800), to switch from
background-size: 100vw;
*/
/*
@media (max-width: 950px) {
body {
background-size: 100vh;
}
}
*/
@media (max-width: 725px) {
body {
background: #f5f5f5;
}
#bar,
#message {
display: none;
}
#download-narrow {
background: #a3a3a3;
display: inline-block;
margin-bottom: unset;
}
#download-narrow:hover {
background: #a7a7a7;
}
#logo-bottom {
display: unset;
}
#profile {
border-radius: unset;
filter: unset;
margin-bottom: unset;
max-height: unset;
max-width: unset;
min-width: 25em;
padding-top: unset;
width: unset;
}
#note {
background: #f5f5f5;
color: #000;
/* display: none; */
font-weight: unset;
padding-bottom: 1em;
text-shadow: unset;
}
#photo,
#qrcode {
border: unset;
border-radius: 50px;
/* height: unset; */
height: 360px;
margin-bottom: 2.5em;
max-width: 70%;
min-width: 360px;
/* width: 360px; */
}
#photo {
background: unset;
/*
border-bottom-left-radius: unset;
border-top-left-radius: unset;
*/
margin-right: unset;
/* object-fit: unset; */
}
#qrcode {
/*
border-bottom-right-radius: unset;
border-top-right-radius: unset;
*/
margin-left: unset;
}
}

934
fasi.py Normal file
View file

@ -0,0 +1,934 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime
from email.utils import parseaddr
from fastapi import FastAPI, Form, HTTPException, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
#import logging
#from os import mkdir
#from os.path import getsize, exists
import os
import qrcode
import random
import re
from slixmpp import ClientXMPP, stanza
from slixmpp.exceptions import IqError, IqTimeout
from starlette.responses import RedirectResponse
#import time
import tomli_w
from urllib.parse import urlsplit
import uvicorn
import xml.etree.ElementTree as ET
try:
import cv2
except:
print('OpenCV (cv2) is required for dynamic background.')
try:
import numpy
except:
print('NumPy (numpy) is required for dynamic background.')
try:
import tomllib
except:
import tomli as tomllib
class XmppInstance(ClientXMPP):
def __init__(self, jid, password, jid_bare):
super().__init__(jid, password)
self.jid_bare = jid_bare
self.register_plugin('xep_0030') # XEP-0030: Service Discovery
self.register_plugin('xep_0045') # XEP-0045: Multi-User Chat
self.register_plugin('xep_0054') # XEP-0054: vcard-temp
self.register_plugin('xep_0060') # XEP-0060: Publish-Subscribe
self.add_event_handler("session_start", self.on_session_start)
async def on_session_start(self, event):
self.send_presence()
#self.disconnect()
class HttpInstance:
def __init__(self, jabber_id, password):
self.app = FastAPI()
templates = Jinja2Templates(directory='xhtml')
# TODO
# 1) Mount at the same mountpoint /img.
# 2) Image filename to be constant, i.e. /img/photo.png and /img/qr.png.
self.app.mount('/photo', StaticFiles(directory='photo'), name='photo')
self.app.mount('/qr', StaticFiles(directory='qr'), name='qr')
self.app.mount('/css', StaticFiles(directory='css'), name='css')
self.app.mount('/img', StaticFiles(directory='img'), name='img')
# @self.app.get('/favicon.ico', include_in_schema=False)
# def favicon_get():
# return FileResponse('graphic/hermes.ico')
# @self.app.get('/hermes.svg')
# def logo_get():
# return FileResponse('graphic/hermes.svg')
# NOTE Was /b/
@self.app.get('/d/{jid}/{node_name}')
async def browse_jid_node_get(request: Request, jid, node_name):
"""Browse items of a pubsub node"""
jid_path = urlsplit(jid).path
if parseaddr(jid_path)[1] == jid_path:
jid_bare = jid_path.lower()
else:
jid_bare = jid
note = 'Jabber ID appears to be malformed'
try:
exception = note = selection = services_sorted = None
title = node_name
link_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)
link_text = 'Subscribe'
xmpp_uri = 'xmpp:{}?;node={}'.format(jid_bare, node_name)
# Start an XMPP instance and retrieve information
xmpp_instance = XmppInstance(jabber_id, password, jid_bare)
xmpp_instance.connect()
# Title
if '@' in jid_bare and node_name == 'urn:xmpp:microblog:0':
title = 'Journal'
else:
jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare)
iq = jid_items['iq']
iq_disco_items = iq['disco_items']
iq_disco_items_items = iq_disco_items['items']
category = 'unsorted'
for item in iq_disco_items_items:
if item[2] and item[1] == node_name:
title = item[2]
break
# Node items
action = 'Browse'
entries = []
node_items = await XmppXep0060.get_node_items(xmpp_instance, jid_bare, node_name)
if not node_items:
action = 'Warning'
title = jid_info['condition']
note = jid_info['text']
services = services_sorted = None
elif isinstance(node_items, IqTimeout):
action = 'Warning'
title = 'Timeout'
note = 'Timeout error'
services = services_sorted = None
elif isinstance(node_items, IqError):
action = 'Warning'
breakpoint()
title = node_items['condition']
note = node_items['text']
services = services_sorted = None
else:
#title = title or node_name
if not title: title = node_name
note = jid_bare
for item in node_items['pubsub']['items']:
item_payload = item['payload']
entry = Syndication.extract_items(item_payload)
entries.append(entry)
if entries: entries.reverse()
xmpp_instance.disconnect()
except Exception as e:
exception = str(e)
action = 'Error'
title = 'Slixmpp error'
xmpp_uri = note = jid
filename = jid_bare = services = url = link_href = link_text = None
#if title == 'remote-server-timeout':
# raise HTTPException(status_code=408, detail='remote-server-timeout')
#else:
template_file = 'node.xhtml'
template_dict = {
'action' : action,
'exception' : exception,
'filename' : 'default.svg',
'jid_bare' : jid,
'note' : note,
'request' : request,
'entries' : entries,
'title' : title,
'url' : request.url._url,
'link_href' : link_href,
'link_text' : link_text,
'xmpp_uri' : xmpp_uri}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
@self.app.get('/d/{jid}')
async def discover_jid_get(request: Request, jid):
"""View items of a selected service"""
jid_path = urlsplit(jid).path
if parseaddr(jid_path)[1] == jid_path:
jid_bare = jid_path.lower()
else:
jid_bare = jid
note = 'Jabber ID appears to be malformed'
try:
exception = note = selection = services_sorted = None
title = 'Services'
link_href = xmpp_uri = jid_bare
link_text = 'Reload'
# Start an XMPP instance and retrieve information
xmpp_instance = XmppInstance(jabber_id, password, jid_bare)
xmpp_instance.connect()
# JID services
action = 'Discover'
jid_info = await XmppXep0030.get_jid_info(xmpp_instance, jid_bare)
iq = jid_info['iq']
if iq:
jid_kind = jid_info['kind']
iq_disco_info = iq['disco_info']
for identity in iq_disco_info['identities']:
if jid_kind == identity[0] and identity[3]:
note = identity[3]
if not note: note = jid_bare
jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare)
iq = jid_items['iq']
iq_disco_items = iq['disco_items']
iq_disco_items_items = iq_disco_items['items']
services = {}
#services_sorted = {}
category = 'unsorted'
for item in iq_disco_items_items:
jid_bare = item[0]
if len(iq_disco_items_items) > 20 or jid_kind and jid_kind in ('pubsub'):
identity = sub_jid_info = sub_jid_info_iq = ''
if jid_kind and jid_kind in ('conference', 'mix', 'muc'):
category = 'conference'
if jid_kind and jid_kind in ('pubsub'):
category = 'pubsub'
else:
sub_jid_info = await XmppXep0030.get_jid_info(xmpp_instance, jid_bare)
sub_jid_info_iq = sub_jid_info['iq']
try:
for identity_item in sub_jid_info_iq['disco_info']['identities']:
identity = identity_item
break
if sub_jid_info_iq:
category = identity[0] if (identity, list) and identity[0] else 'other'
except:
identity = None
category = 'unavailable'
sub_jid_kind = sub_jid_info['kind'] if 'kind' in sub_jid_info else None
if category not in services: services[category] = []
services[category].append(
{'identity' : identity,
'info' : sub_jid_info,
'jid' : jid_bare,
'kind' : sub_jid_kind,
'name' : item[2] or item[1] or item[0],
'node' : item[1]})
services_sorted = {k: v for k, v in services.items() if k != 'unavailable'}
if 'unavailable' in services: services_sorted['unavailable'] = services['unavailable']
else:
action = 'Warning'
title = jid_info['condition']
note = jid_info['text']
services = services_sorted = None
xmpp_instance.disconnect()
except Exception as e:
exception = str(e)
action = 'Error'
title = 'Slixmpp error'
xmpp_uri = note = jid
filename = jid_bare = services = url = link_href = link_text = None
#if title == 'remote-server-timeout':
# raise HTTPException(status_code=408, detail='remote-server-timeout')
#else:
template_file = 'disco.xhtml'
template_dict = {
'action' : action,
'exception' : exception,
'filename' : 'default.svg',
'jid_bare' : jid,
'note' : note,
'request' : request,
'services' : services_sorted,
'title' : title,
'url' : request.url._url,
'link_href' : link_href,
'link_text' : link_text,
'xmpp_uri' : xmpp_uri}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
@self.app.get('/v/{jid}')
async def view_jid_get(request: Request, jid):
"""View recent messages of a conference"""
pass #TODO
@self.app.get('/{jid}/{node_name}')
async def jid_node_get(request: Request, jid, node_name):
response = await main_jid_node_get(request, jid, node_name)
return response
@self.app.get('/{jid}')
async def jid_node_get(request: Request, jid):
node_name = request.query_params.get('node', '')
if node_name:
response = RedirectResponse(url='/{}/{}'.format(jid, node_name))
else:
response = await main_jid_node_get(request, jid)
return response
async def main_jid_node_get(request: Request, jid, node_name=None):
jid_bare = jid
jid_path = urlsplit(jid).path
if parseaddr(jid_path)[1] == jid_path:
jid_bare = jid_path.lower()
else:
jid_bare = jid
note = 'Jabber ID appears to be malformed'
try:
exception = note = selection = title = view_href = None
# Start an XMPP instance and retrieve information
xmpp_instance = XmppInstance(jabber_id, password, jid_bare)
xmpp_instance.connect()
# JID kind
instance = message = node_id = None
jid_info = await XmppXep0030.get_jid_info(xmpp_instance, jid_bare)
jid_info_iq = jid_info['iq']
jid_kind = jid_info['kind']
if jid_info['error']:
message = '{}: {} (XEP-0030)'.format(jid_info['text'], jid_info['condition'])
action = 'Connect with'
link_text = 'Connect'
link_href = xmpp_uri = 'xmpp:{}'.format(jid_bare)
elif jid_kind in ('conference', 'server'):
action = link_text = 'Discover'
if jid_kind == 'conference':
instance = 'conferences'
elif jid_kind == 'server':
instance = 'services'
link_href = xmpp_uri = 'xmpp:{}'.format(jid_bare)
view_href = '/d/' + jid_bare
elif jid_kind in ('mix', 'muc'):
#title = 'Group Chat ' + title
# TODO Set group chat subject as description.
action = 'Join to'
instance = 'participants'
link_text = 'Join'
link_href = xmpp_uri = 'xmpp:{}?join'.format(jid_bare)
view_href = '/v/' + jid_bare
# room_info = await XmppXep0045.get_room_data(xmpp_instance, jid_bare)
# breakpoint()
elif jid_kind == 'pubsub':
#node_name = request.query_params.get('node', '')
if node_name:
action = 'Subscribe to'
instance = 'articles'
link_text = 'Subscribe'
link_href = xmpp_uri = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)
view_href = '/d/{}/{}'.format(jid_bare, node_name)
else:
action = link_text = 'Browse'
instance = 'nodes'
link_href = xmpp_uri = 'xmpp:{}'.format(jid_bare)
view_href = '/d/' + jid_bare
else:
action = link_text = 'Message'
instance = 'articles'
link_href = xmpp_uri = 'xmpp:{}?message'.format(jid_bare)
node_name = 'urn:xmpp:microblog:0'
view_href = '/d/{}/{}'.format(jid_bare, node_name)
# JID item count
count = None
if jid_kind in ('mix', 'muc', 'conference', 'server'):
jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare)
count = len(jid_items['iq']['disco_items']['items'])
elif jid_kind in ('account', 'pubsub'):
node_items = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name)
if isinstance(node_items, stanza.iq.Iq):
count = len(node_items['disco_items']['items'])
# JID info
# NOTE Group chat of Psi+ Project at jabber.ru has a note in its vCard.
vcard_data = await XmppXep0054.get_vcard_data(xmpp_instance, jid_bare)
if vcard_data['error']:
jid_detail = {}
#jid_detail['note'] = '{}: {}'.format(vcard_data['text'], vcard_data['condition'])
jid_detail['name'] = jid_detail['note'] = jid_detail['note'] = jid_detail['type'] = jid_detail['bin'] = None
else:
conference_title = None
if jid_kind in ('mix', 'muc'):
for identity in jid_info_iq['disco_info']['identities']:
if identity[3]:
conference_title = identity[3]
break
vcard_temp = vcard_data['iq']['vcard_temp']
jid_detail = {
'name' : vcard_temp['FN'] or conference_title,
'note' : vcard_temp['notes'] or node_id,
'type' : vcard_temp['PHOTO']['TYPE'],
'bin' : vcard_temp['PHOTO']['BINVAL']
}
# Title
if jid_kind == 'pubsub':
jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare)
iq = jid_items['iq']
iq_disco_items = iq['disco_items']
iq_disco_items_items = iq_disco_items['items']
for item in iq_disco_items_items:
if item[2] and item[1] == node_name:
title = item[2]
break
if jid_kind == 'server':
if jid_info_iq:
for identity in jid_info_iq['disco_info']['identities']:
if jid_kind == identity[0] and identity[1] == 'im' and identity[3]:
title = identity[3]
print(jid_bare)
print(identity)
print(jid_info)
# String 'undefined' is sourced from JID discuss@conference.conversejs.org
if not title:
if jid_detail['name'] and not 'undefined' in jid_detail['name']:
title = jid_detail['name']
else:
title = jid_bare.split('@')[0]
# Notes
jid_detail_note = jid_detail['note']
if isinstance(jid_detail_note, list) and len(jid_detail_note):
note = jid_detail_note[0]['NOTE']
else:
note = jid_detail_note
#if not note and jid_detail['name'] and not 'undefined' in jid_detail['name'] and title != jid_detail['name']:
# note = jid_detail['name']
# File type
mimetype = filename = filepath = None
if jid_detail['type']:
mimetype = jid_detail['type']
if mimetype:
filetype = mimetype.split('/')[1]
if filetype == 'svg+xml': filetype = 'svg'
filename = '{}.{}'.format(jid_bare, filetype)
filepath = 'photo/{}.{}'.format(jid_bare, filetype)
#img.save(filename)
# Write the decoded bytes to a file
with open(filepath, 'wb') as file:
file.write(jid_detail['bin'])
#from PIL import Image
#img = Image.open(filepath)
#rgb_im = im.convert("RGB")
#rgb_im.save('{}_mod.jpg'.format(jid_bare))
# Default photo. Utilized, if there is no image file.
if not filepath or not os.path.exists(filepath) or os.path.getsize(filepath) == 0:
filename = 'default.svg'
elif filetype == 'svg':
selection = Graphics.extract_colours_from_vector(filepath)
else:
selection = Graphics.extract_colours_from_raster(filepath)
xmpp_instance.disconnect()
# QR code
Graphics.generate_qr_code_graphics_from_string(xmpp_uri, jid_bare)
except Exception as e:
exception = str(e)
print(exception)
action = 'Error'
title = 'Slixmpp error'
xmpp_uri = jid
count = filename = jid_bare = jid_kind = link_href = link_text = message = selection = url = None
template_file = 'jid.xhtml'
template_dict = {
'action' : action,
'count' : count,
'instance' : instance,
'exception' : exception,
'filename' : filename,
'jid_bare' : jid_bare,
'jid_kind' : jid_kind,
'link_href' : link_href,
'link_text' : link_text,
'message' : message,
'note' : note,
'request' : request,
'selection' : selection,
'title' : title,
'url' : request.url._url,
'view_href' : view_href,
'xmpp_uri' : xmpp_uri}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
@self.app.exception_handler(404)
def not_found_exception_handler(request: Request, exc: HTTPException):
action = 'Warning'
title = 'Not Found'
return result_get(request, action, title)
@self.app.exception_handler(500)
def internal_error_exception_handler(request: Request, exc: HTTPException):
action = 'Error'
title = 'Internal Server Error'
return result_get(request, action, title)
def result_get(request: Request, action: str, title: str):
template_file = 'result.xhtml'
template_dict = {
'action' : action,
'request' : request,
'title' : title,
'url' : request.url._url}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
@self.app.get('/')
async def main_get(request: Request):
jabber_id = request.query_params.get('jid', '')
if jabber_id:
response = RedirectResponse(url='/' + jabber_id)
else:
template_file = 'main.xhtml'
template_dict = {
'request' : request,
'url' : request.url._url}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
class Data:
def open_file_toml(filename: str) -> dict:
with open(filename, mode="rb") as fn:
data = tomllib.load(fn)
return data
def save_to_toml(filename: str, data: dict) -> None:
with open(filename, 'w') as fn:
data_as_string = tomli_w.dumps(data)
fn.write(data_as_string)
class Graphics:
def extract_colours_from_raster(filepath):
try:
img = cv2.imread(filepath)
#thresholded = cv2.inRange(img, (50, 100, 200), (50, 100, 200))
thresholded = cv2.inRange(img, (90, 90, 90), (190, 190, 190))
#thresholded = cv2.bitwise_not(thresholded)
#thresholded = cv2.inRange(img, (0, 0, 0), (0, 0, 0))
#res = img + cv2.cvtColor(thresholded, cv2.COLOR_GRAY2BGR)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#result = numpy.clip(img, 90, 190)
#result = numpy.clip(img, 50, 200)
#result = numpy.clip(img, 100, 150)
result = numpy.clip(img, 100, 200)
res = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
"""
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = numpy.all(numpy.logical_and(img >= 90, img <= 190), axis=2)
result = numpy.where(mask[...,None], img, 255)
res = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
"""
"""
# Thresholding for black:
lower_black = numpy.array([0, 0, 0])
upper_black = numpy.array([50, 50, 50]) # Adjust this value for the black range
black_mask = cv2.inRange(img, lower_black, upper_black)
# Thresholding for white:
lower_white = numpy.array([250, 250, 250])
upper_white = numpy.array([255, 255, 255])
white_mask = cv2.inRange(img, lower_white, upper_white)
# Combine the masks
combined_mask = cv2.bitwise_or(black_mask, white_mask)
# Invert the combined mask
inverted_mask = cv2.bitwise_not(combined_mask)
# Apply the mask to the original image
res = cv2.bitwise_and(img, img, mask=inverted_mask)
"""
selection = []
ix_1st = random.randint(1, len(res)-1)
res_ix_1st = res[ix_1st]
ix_ix_1st = random.randint(1, len(res_ix_1st)-1)
res_ix_ix_1st = res_ix_1st[ix_ix_1st]
selection.append(numpy.array(res_ix_ix_1st).tolist())
ix_2nd = random.randint(1, len(res)-1)
res_ix_2nd = res[ix_2nd]
ix_ix_2nd = random.randint(1, len(res_ix_2nd)-1)
res_ix_ix_2nd = res_ix_2nd[ix_ix_2nd]
selection.append(numpy.array(res_ix_ix_2nd).tolist())
print(selection)
except Exception as e:
exception = str(e)
print(exception)
return selection
def extract_colours_from_vector(filepath):
# Parse the SVG file
tree = ET.parse(filepath)
root = tree.getroot()
# Set to store unique colours
colours_hex = set()
colours_rgb = []
# SVG namespace
namespace = {'svg': 'http://www.w3.org/2000/svg'}
# Find all possible elements
for elem in root.findall('.//svg:circle', namespace) + \
root.findall('.//svg:ellipse', namespace) + \
root.findall('.//svg:line', namespace) + \
root.findall('.//svg:path', namespace) + \
root.findall('.//svg:polygon', namespace) + \
root.findall('.//svg:rect', namespace) + \
root.findall('.//svg:text', namespace):
fill = elem.get('fill')
stroke = elem.get('stroke')
# Add colours to the set if they are not None or 'none'
if fill and fill.startswith('#') and len(fill) > 4 and fill.lower() != 'none':
colours_hex.add(fill)
if stroke and stroke.startswith('#') and len(stroke) > 4 and stroke.lower() != 'none':
colours_hex.add(stroke)
for colour in colours_hex:
hex = colour.lstrip('#')
rgb = list(int(hex[i:i+2], 16) for i in (0, 2, 4))
rgb.reverse()
colours_rgb.append(rgb)
selection = []
if len(colours_rgb) > 1:
for i in range(2):
ix = random.randint(0, len(colours_rgb)-1)
selection.append(colours_rgb[ix])
del colours_rgb[ix]
elif len(colours_rgb) == 1:
selection = [colours_rgb[0], colours_rgb[0]]
return selection
def generate_qr_code_graphics_from_string(text, jid_bare):
qrcode_graphics = qrcode.make(text)
qrcode_graphics.save('qr/{}.png'.format(jid_bare))
class Syndication:
def extract_items(item_payload, limit=False):
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)): return None
title_text = '' if title == None else title.text
if isinstance(links, ET.Element):
for link in item_payload.findall(namespace + 'link'):
link_href = link.attrib['href'] if 'href' in link.attrib else ''
if link_href: break
contents = item_payload.find(namespace + 'content')
content_text = ''
if isinstance(contents, ET.Element):
for content in item_payload.findall(namespace + 'content'):
content_text = content.text or ''
if content_text: break
summaries = item_payload.find(namespace + 'summary')
summary_text = ''
if isinstance(summaries, ET.Element):
for summary in item_payload.findall(namespace + 'summary'):
summary_text = summary.text or ''
if summary_text: break
published = item_payload.find(namespace + 'published')
published_text = '' if published == None else published.text
categories = item_payload.find(namespace + 'category')
tags = []
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']
if len(category_term) < 20:
tags.append(category_term)
elif len(category_term) < 50:
tags.append(category_term)
if limit and len(tags) > 4: break
identifier = item_payload.find(namespace + 'id')
if identifier and identifier.attrib: print(identifier.attrib)
identifier_text = '' if identifier == None else identifier.text
instances = '' # TODO Check the Blasta database for instances.
entry = {'content' : content_text,
'link' : link_href,
'published' : published_text,
'summary' : summary_text,
'tags' : tags,
'title' : title_text,
'updated' : published_text} # TODO "Updated" is missing
return entry
class XmppXep0030:
async def get_jid_items(self, jid_bare):
try:
condition = text = None
error = False
iq = await self['xep_0030'].get_items(jid=jid_bare)
except (IqError, IqTimeout) as e:
#logger.warning('Chat type could not be determined for {}'.format(jid_bare))
#logger.error(e)
iq = None
error = True
condition = e.iq['error']['condition']
text = e.iq['error']['text'] or 'Error'
#if not text:
# # NOTE We might want to set a specific photo for condition remote-server-not-found
# if condition:
# text = 'Could not determine JID type'
# else:
# text = 'Unknown Error'
result = {
'condition' : condition,
'error' : error,
'iq' : iq,
'text' : text}
return result
# NOTE
# Feature "urn:xmpp:mucsub:0" is present in both, MUC local and MUC hostname
# Feature "urn:xmpp:serverinfo:0" is present in both, MUC hostname and main hostname
async def get_jid_info(self, jid_bare):
jid_kind = None
try:
error = False
condition = text = None
iq = await self['xep_0030'].get_info(jid=jid_bare)
iq_disco_info = iq['disco_info']
if iq_disco_info:
features = iq_disco_info['features']
if 'http://jabber.org/protocol/muc#unique' in features:
jid_kind = 'conference'
elif 'urn:xmpp:mix:core:1' in features:
jid_kind = 'mix'
elif ('muc_moderated' in features or
'muc_open' in features or
'muc_persistent' in features or
'muc_public' in features or
'muc_semianonymous' in features or
'muc_unmoderated' in features or
'muc_unsecured' in features):
jid_kind = 'muc'
else:
for identity in iq_disco_info['identities']:
if identity[0] == 'pubsub' and identity[1] == 'service':
#if 'http://jabber.org/protocol/pubsub' in features:
#if 'http://jabber.org/protocol/pubsub#access-authorize' in features:
#if 'http://jabber.org/protocol/rsm' in features:
jid_kind = 'pubsub'
break
if identity[0] == 'server' and identity[1] == 'im':
jid_kind = 'server'
break
#if identity[0] == 'pubsub' and identity[1] == 'pep':
if identity[0] == 'account':
#if 'urn:xmpp:bookmarks:1#compat-pep' in features:
#if 'urn:xmpp:bookmarks:1#compat' in features:
#if 'urn:xmpp:push:0' in features:
#if 'urn:xmpp:pep-vcard-conversion:0' in features:
#if 'urn:xmpp:sid:0' in features:
# Also in MIX
#if 'urn:xmpp:mam:2' in features:
#if 'urn:xmpp:mam:2#extended' in features:
jid_kind = 'account'
break
if identity[0] == 'client' and identity[1] == 'bot':
jid_kind = 'bot'
#logger.info('Jabber ID: {}\n'
# 'Chat Type: {}'.format(jid_bare, result))
else:
iq = condition = text = None
except (IqError, IqTimeout) as e:
#logger.warning('Chat type could not be determined for {}'.format(jid_bare))
#logger.error(e)
iq = None
error = True
condition = e.iq['error']['condition']
text = e.iq['error']['text'] or 'Error'
#if not text:
# # NOTE We might want to set a specific photo for condition remote-server-not-found
# if condition:
# text = 'Could not determine JID type'
# else:
# text = 'Unknown Error'
result = {
'condition' : condition,
'error' : error,
'iq' : iq,
'text' : text,
'kind' : jid_kind}
return result
class XmppXep0045:
async def get_room_data(self, jid_bare):
return await self['xep_0045'].get_room_config(jid_bare)
async def get_number_of_participants(self, jid_bare):
return len(await self['xep_0045'].get_roster(jid_bare))
# NOTE: "Item not found", yet is a group chat
# That is, JID has no vcard
# messaging-off@conference.movim.eu
class XmppXep0054:
async def get_vcard_data(self, jid_bare):
try:
error = False
condition = text = None
iq = await self['xep_0054'].get_vcard(jid_bare)
except (IqError, IqTimeout) as e:
error = True
condition = e.iq['error']['condition']
text = e.iq['error']['text']
if not text:
if condition:
text = 'Could not retrieve vCard'
else:
text = 'Unknown Error'
iq = None
result = {
'error' : error,
'condition' : condition,
'text' : text,
'iq' : iq}
return result
class XmppXep0060:
async def get_node_items(self, jid_bare, node_name, item_ids=None, max_items=None):
try:
if max_items:
iq = await self['xep_0060'].get_items(
jid_bare, node_name, timeout=5)
it = self['xep_0060'].get_items(
jid_bare, node_name, timeout=5, max_items=max_items, iterator=True)
q = rsm.Iq()
q['to'] = jid_bare
q['disco_items']['node'] = node_name
async for item in rsm.ResultIterator(q, 'disco_items', '10'):
print(item['disco_items']['items'])
else:
iq = await self['xep_0060'].get_items(
jid_bare, node_name, timeout=5, item_ids=item_ids)
result = iq
except IqError as e:
if e.iq['error']['text'] == 'Node not found':
result = 'Node not found'
elif e.iq['error']['condition'] == 'item-not-found':
result = 'Item not found'
else:
result = None
except IqTimeout as e:
result = e
return result
async def get_node_item_ids(self, jid_bare, node_name):
try:
iq = await self['xep_0030'].get_items(
jid_bare, node_name)
# Broken. See https://codeberg.org/poezio/slixmpp/issues/3548
#iq = await self['xep_0060'].get_item_ids(
# jid_bare, node_name, timeout=5)
result = iq
except IqError as e:
if e.iq['error']['text'] == 'Node not found':
result = 'Node not found'
elif e.iq['error']['condition'] == 'item-not-found':
result = 'Item not found'
else:
result = None
except IqTimeout as e:
result = e
return result
class XmppXep0369:
async def get_room_data(self, jid_bare):
return await self['xep_0369'].get_channel_info(jid_bare)
def main():
filename_configuration = 'configuration.toml'
data = Data.open_file_toml(filename_configuration)
account = data['account']
jabber_id = account['xmpp']
password = account['pass']
http_instance = HttpInstance(jabber_id, password)
return http_instance.app
app = main()
# FIXME
if __name__ == '__main__':
uvicorn.run(app, host='127.0.0.1', port=8000, reload=True)

109
img/favicon.svg Normal file
View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<svg
version="1.1"
xml:space="preserve"
viewBox="0 0 180 180"
width="18"
height="18"
x="0px"
y="0px"
enable-background="new 0 0 200 200"
id="svg11"
sodipodi:docname="favicon.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs11" /><sodipodi:namedview
id="namedview11"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="32"
inkscape:cx="8.8125"
inkscape:cy="9.765625"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="svg11" />
<linearGradient
id="SVGID_right_"
y2="1.279e-13"
gradientUnits="userSpaceOnUse"
x2="-1073.2"
gradientTransform="translate(1185.688,26.573995)"
y1="126.85"
x1="-1073.2">
<stop
stop-color="#1b3967"
offset=".011"
id="stop1" />
<stop
stop-color="#13b5ea"
offset=".467"
id="stop2" />
<stop
stop-color="#002b5c"
offset=".9945"
id="stop3" />
</linearGradient>
<linearGradient
id="SVGID_left_"
y2="1.279e-13"
gradientUnits="userSpaceOnUse"
x2="-1073.2"
gradientTransform="matrix(-1,0,0,1,-1005.704,26.573995)"
y1="126.85"
x1="-1073.2">
<stop
stop-color="#1b3967"
offset=".011"
id="stop4" />
<stop
stop-color="#13b5ea"
offset=".467"
id="stop5" />
<stop
stop-color="#002b5c"
offset=".9945"
id="stop6" />
</linearGradient>
<path
d="m 140.88912,40.762996 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551002 -46.558,97.366014 -91.687985,108.730004 v 1.639 C 107.36713,147.90501 175.97412,85.624998 177.48412,26.574995 l -36.599,14.189001 z"
style="fill:url(#SVGID_right_)"
id="path6" />
<path
d="m 122.75712,40.671995 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551002 -30.69898,90.497005 -75.826985,101.860005 v 1.639 C 106.09414,145.338 152.86013,85.103997 152.86013,38.927995 c 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z"
style="fill:#e96d1f"
id="path7" />
<path
d="m 152.77512,31.183995 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219002 -37.271,98.204005 -87.271995,107.120005 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 112.27413,143.26 158.12013,76.953997 152.78012,31.179995 Z"
style="fill:#d9541e"
id="path8" />
<path
d="m 39.095,40.762996 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551002 46.558007,97.366014 91.68799,108.730004 v 1.639 C 72.617,147.90501 4.01,85.624998 2.5,26.574995 l 36.599,14.189001 z"
style="fill:url(#SVGID_left_)"
id="path9" />
<path
d="m 57.227,40.671995 c -0.076,1.313 -0.12,2.63 -0.12,3.957001 0,38.551001 30.698995,90.497004 75.82699,101.860004 v 1.639 C 73.89,145.338 27.124,85.103997 27.124,38.927995 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z"
style="fill:#a0ce67"
id="path10" />
<path
d="m 27.209,31.183996 7.617,2.722 c -0.041,0.962 -0.066,2.253999 -0.066,3.225 0,41.219001 37.271,98.204004 87.27199,107.120004 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 67.71,143.26 21.864,76.953998 27.204,31.179996 Z"
style="fill:#439639"
id="path11" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
xml:space="preserve"
viewBox="0 0 760 160"
width="152"
height="32"
x="0px"
y="0px"
enable-background="new 0 0 200 200"
id="svg15"
sodipodi:docname="XMPP.logo.horizontal.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs15" /><sodipodi:namedview
id="namedview15"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2"
inkscape:cx="256.5"
inkscape:cy="-15.75"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="svg15" />
<linearGradient
id="SVGID_right_"
y2="1.279e-13"
gradientUnits="userSpaceOnUse"
x2="-1073.2"
gradientTransform="translate(1196.604,15.368977)"
y1="126.85"
x1="-1073.2">
<stop
stop-color="#1b3967"
offset=".011"
id="stop1" />
<stop
stop-color="#13b5ea"
offset=".467"
id="stop2" />
<stop
stop-color="#002b5c"
offset=".9945"
id="stop3" />
</linearGradient>
<linearGradient
id="SVGID_left_"
y2="1.279e-13"
gradientUnits="userSpaceOnUse"
x2="-1073.2"
gradientTransform="matrix(-1,0,0,1,-994.78801,15.367977)"
y1="126.85"
x1="-1073.2">
<stop
stop-color="#1b3967"
offset=".011"
id="stop4" />
<stop
stop-color="#13b5ea"
offset=".467"
id="stop5" />
<stop
stop-color="#002b5c"
offset=".9945"
id="stop6" />
</linearGradient>
<path
d="m 151.80512,29.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,136.69999 186.89012,74.419978 188.40012,15.369977 l -36.599,14.189001 z"
style="fill:url(#SVGID_right_)"
id="path6" />
<path
d="m 133.67312,34.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.024012 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z"
style="fill:#e96d1f"
id="path7" />
<path
d="m 163.69112,24.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,137.02799 169.03613,70.721978 163.69612,24.947978 Z"
style="fill:#d9541e"
id="path8" />
<path
d="m 50.011,29.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,136.69899 14.926,74.418978 13.416,15.368977 l 36.599,14.189001 z"
style="fill:url(#SVGID_left_)"
id="path9" />
<path
d="m 68.143,34.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,138.96599 38.04,78.731978 38.04,32.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z"
style="fill:#a0ce67"
id="path10" />
<path
d="m 38.125,24.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,137.02699 32.78,70.720978 38.12,24.946978 Z"
style="fill:#439639"
id="path11" />
<path
d="M 251.13317,79.886831 210.11296,35 h 35.67356 L 273.75106,66.4606 301.7216,35 h 35.67378 L 296.37863,79.874529 340.5022,125 H 303.45915 L 273.74821,92.308037 244.04926,125 H 207 l 44.13317,-45.131605 z"
id="path12"
style="stroke-width:3.0645" />
<path
d="m 352.80507,35 h 42.39259 L 426.3977,92.923669 457.59774,35 h 42.38031 v 90 H 473.13192 V 60.363248 H 472.8868 L 436.7109,125 H 416.08144 L 379.90556,60.363248 h -0.25124 V 125 H 352.802 V 35.012256 Z"
id="path13"
style="stroke-width:3.06394" />
<path
d="m 515.494,35.01838 h 75.19197 c 26.21886,0 32.55685,13.178353 32.55685,30.826074 v 7.707284 c 0,13.420355 -5.8448,28.825732 -25.34888,28.825732 H 543.07909 V 125 H 515.494 V 35 Z m 27.56978,45.000003 h 42.87713 c 6.46055,0 8.95714,-4.218176 8.95714,-9.566713 v -3.476855 c 0,-6.095984 -2.98979,-9.578966 -11.31586,-9.578966 h -40.51838 v 22.607217 z"
id="path14"
style="stroke-width:3.06331" />
<path
d="m 638.17769,35.01838 h 75.19199 c 26.22499,0 32.56298,13.178357 32.56298,30.826083 v 7.707286 c 0,13.42036 -5.84173,28.825711 -25.34888,28.825711 h -54.8118 V 125 H 638.17769 V 35 Z m 27.60042,45.000019 h 42.87408 c 6.46358,0 8.95099,-4.21818 8.95099,-9.56672 v -3.476855 c 0,-6.095986 -2.98367,-9.578969 -11.31281,-9.578969 h -40.51226 v 22.607224 z"
id="path15"
style="opacity:1;stroke-width:3.06331" />
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xml:space="preserve" viewBox="0 0 200 200" width="200px" height="200px" x="0px" y="0px" enable-background="new 0 0 200 200">
<linearGradient id="SVGID_right_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="translate(1196.604,15.368977)" y1="126.85" x1="-1073.2">
<stop stop-color="#1b3967" offset=".011"/>
<stop stop-color="#13b5ea" offset=".467"/>
<stop stop-color="#002b5c" offset=".9945"/>
</linearGradient>
<linearGradient id="SVGID_left_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="matrix(-1,0,0,1,-994.78801,15.367977)" y1="126.85" x1="-1073.2">
<stop stop-color="#1b3967" offset=".011"/>
<stop stop-color="#13b5ea" offset=".467"/>
<stop stop-color="#002b5c" offset=".9945"/>
</linearGradient>
<path d="m 151.80512,29.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,136.69999 186.89012,74.419978 188.40012,15.369977 l -36.599,14.189001 z" style="fill:url(#SVGID_right_)"/>
<path d="m 133.67312,34.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.024012 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z" style="fill:#e96d1f"/>
<path d="m 163.69112,24.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,137.02799 169.03613,70.721978 163.69612,24.947978 Z" style="fill:#d9541e"/>
<path d="m 50.011,29.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,136.69899 14.926,74.418978 13.416,15.368977 l 36.599,14.189001 z" style="fill:url(#SVGID_left_)"/>
<path d="m 68.143,34.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,138.96599 38.04,78.731978 38.04,32.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z" style="fill:#a0ce67"/>
<path d="m 38.125,24.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,137.02699 32.78,70.720978 38.12,24.946978 Z" style="fill:#439639"/>
<path d="m 25.988,172.07799 -13.388,-14.65 h 11.643 l 9.127,10.268 9.129,-10.268 h 11.643 l -13.387,14.646 14.401,14.728 h -12.09 l -9.697,-10.67 -9.693,10.67 H 11.584 l 14.404,-14.73 z"/>
<path d="m 58.508,157.42799 h 13.836 l 10.183,18.905 10.183,-18.905 h 13.83199 v 29.374 h -8.761983 v -21.096 h -0.08 L 85.893,186.80199 H 79.16 l -11.807,-21.096 h -0.082 v 21.096 h -8.764 v -29.37 z"/>
<path d="m 112.66199,157.42799 h 24.546 c 8.559,0 10.628,4.302 10.628,10.063 v 2.516 c 0,4.381 -1.908,9.41 -8.275,9.41 h -17.894 v 7.385 h -9.005 v -29.38 z m 9,14.69 h 13.997 c 2.10901,0 2.92401,-1.377 2.92401,-3.123 v -1.135 c 0,-1.99 -0.976,-3.127 -3.694,-3.127 h -13.227 v 7.38 z"/>
<path d="m 152.72199,157.42799 h 24.546 c 8.561,0 10.63,4.302 10.63,10.063 v 2.516 c 0,4.381 -1.907,9.41 -8.275,9.41 h -17.893 v 7.385 h -9.008 v -29.38 z m 9.01,14.69 h 13.996 c 2.11,0 2.922,-1.377 2.922,-3.123 v -1.135 c 0,-1.99 -0.974,-3.127 -3.693,-3.127 h -13.225 v 7.38 z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

108
img/xmpp-logo.svg Normal file
View file

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
xml:space="preserve"
viewBox="13 35 175.6322 131.72422"
width="160"
height="120"
x="0px"
y="0px"
enable-background="new 0 0 200 200"
id="svg11"
sodipodi:docname="xmpp-logo.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs11" /><sodipodi:namedview
id="namedview11"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="50.232866"
inkscape:cx="41.158313"
inkscape:cy="119.45367"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="svg11" />
<linearGradient
id="SVGID_right_"
y2="1.279e-13"
gradientUnits="userSpaceOnUse"
x2="-1073.2"
gradientTransform="translate(1196.604,37.368977)"
y1="126.85"
x1="-1073.2">
<stop
stop-color="#1b3967"
offset=".011"
id="stop1" />
<stop
stop-color="#13b5ea"
offset=".467"
id="stop2" />
<stop
stop-color="#002b5c"
offset=".9945"
id="stop3" />
</linearGradient>
<linearGradient
id="SVGID_left_"
y2="1.279e-13"
gradientUnits="userSpaceOnUse"
x2="-1073.2"
gradientTransform="matrix(-1,0,0,1,-994.78801,37.367977)"
y1="126.85"
x1="-1073.2">
<stop
stop-color="#1b3967"
offset=".011"
id="stop4" />
<stop
stop-color="#13b5ea"
offset=".467"
id="stop5" />
<stop
stop-color="#002b5c"
offset=".9945"
id="stop6" />
</linearGradient>
<path
d="m 151.80512,51.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551002 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,158.69999 186.89012,96.41998 188.40012,37.369977 l -36.599,14.189001 z"
style="fill:url(#SVGID_right_)"
id="path6" />
<path
d="m 133.67312,56.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551002 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.02401 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z"
style="fill:#e96d1f"
id="path7" />
<path
d="m 163.69112,46.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219002 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,159.02799 169.03613,92.72198 163.69612,46.947978 Z"
style="fill:#d9541e"
id="path8" />
<path
d="m 50.011,51.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551002 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,158.69899 14.926,96.41898 13.416,37.368977 l 36.599,14.189001 z"
style="fill:url(#SVGID_left_)"
id="path9" />
<path
d="m 68.143,56.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551002 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,160.96599 38.04,100.73198 38.04,54.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z"
style="fill:#a0ce67"
id="path10" />
<path
d="m 38.125,46.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219002 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,159.02699 32.78,92.72098 38.12,46.946978 Z"
style="fill:#439639"
id="path11" />
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

1
photo/README Normal file
View file

@ -0,0 +1 @@
This directory caches photo files.

53
photo/default.svg Normal file
View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="1333.3333"
height="936"
viewBox="0 0 1333.3333 936"
sodipodi:docname="man-and-woman-faces-profiles_0003.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.70710678"
inkscape:cx="613.76868"
inkscape:cy="471.64022"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="g8794" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g1">
<g
id="g8794"
transform="translate(-2.6666735,-12.61628)">
<path
style="fill:#1f0d01;stroke:none"
d="m 778.66662,330.66667 c -11.54549,1.25126 -20.26286,-0.0486 -30.66667,-5.33334 7.67343,10.9939 15.993,16.05445 29.33334,17.33334 C 764.72318,362.7476 748.5854,379.17855 732.04162,396 c -6.99943,7.1169 -18.55119,16.85307 -17.9178,27.94938 0.85913,15.04981 21.67724,17.56047 24.29931,32.05648 3.19011,17.63656 -17.83992,27.83032 5.57682,41.32747 v 1.33334 c -25.15966,11.08516 3.61166,26.34334 6.63062,40.20337 5.22363,23.98177 -13.09611,43.30798 16.08097,59.6333 45.78524,25.61792 103.69458,-6.27731 149.28767,15.36836 29.67464,14.08826 45.67895,49.89706 55.14778,79.46163 24.79737,77.42473 13.08749,152.06592 -13.81372,226.66667 34.18506,-5.66569 69.10483,-11.48389 95.94943,-35.36157 22.156,-19.7072 34.0021,-46.0669 50.58,-69.97176 22.3841,-32.2771 53.8691,-62.79973 90.8039,-77.33334 -10.2422,-11.77604 -22.262,-21.80875 -31.2444,-34.67114 -28.7341,-41.14648 -46.4761,-95.21322 -47.4223,-145.32886 21.854,1.87334 42.8049,1.52751 64,-4.7882 19.1696,-5.71216 37.3508,-15.66065 53.3334,-27.5907 C 1329.0865,453.48031 1330.2467,324.12638 1283.5076,224 1247.5468,146.96362 1176.7448,65.53776 1087.9999,52.038818 c -16.8437,-2.562093 -35.3522,-2.3007 -52,1.396078 -10.7737,2.392415 -22.0835,9.318847 -33.3333,7.489014 -19.21695,-3.125733 -35.96272,-11.545492 -55.99999,-12.50114 -63.55761,-3.031331 -154.3894,30.578613 -173.20857,98.2439 -4.5376,16.31502 -2.57731,33.88842 4.11654,49.33333 3.7338,8.61515 11.57568,17.05273 12.23698,26.66667 0.84708,12.31518 -6.52889,26.36059 -8.36898,38.66666 -3.43547,22.97575 -2.77596,46.17957 -2.77596,69.33334 z"
id="path8797" />
<path
style="fill:#1f0d01;stroke:none"
d="M 331.99998,874.66667 C 326.36971,853.71134 320.79922,832.75138 314.44446,812 c -3.98787,-13.02238 -10.09967,-26.27327 -10.9611,-40 -1.36816,-21.80151 10.12443,-41.76676 9.229,-64 -1.07794,-26.76506 3.83187,-73.47038 32.63975,-84.86593 13.22078,-5.22973 30.07772,-3.50484 43.9812,-2.60335 21.26009,1.37854 42.68921,2.68433 64,2.00362 13.84163,-0.44214 31.94921,-1.16715 42.69421,-11.1123 19.80338,-18.32927 2.4587,-44.05676 0.87015,-64.73942 -1.14095,-14.85543 33.68177,-37.19389 5.59591,-49.41529 -3.33309,-1.45035 -6.9412,-1.98738 -10.49361,-2.60066 12.94092,-2.83041 27.70906,-8.49764 28.69429,-24.01599 0.46957,-7.39629 -3.99532,-15.01441 -0.29223,-22.10149 8.44779,-16.16756 33.6361,-5.32885 42.02795,-22.35046 6.45345,-13.08984 -2.22457,-28.16471 -8.45056,-39.53206 -12.7961,-23.36292 -32.80457,-49.28736 -37.40332,-76 -3.62777,-21.07235 10.31502,-42.47897 9.37842,-64 C 524.87582,221.88062 512.84693,199.24276 505.58884,176 500.08416,158.37223 505.70179,140.3396 498.10076,122.66675 481.70887,84.554687 427.30755,60.378337 389.33331,50.966471 366.64052,45.342122 343.26003,43.739746 319.99998,42.394857 251.20911,38.417318 169.26332,42.934001 110.66666,83.377767 69.92654,111.49683 43.182116,157.7216 32.17731,205.33333 c -6.111989,26.4432 -8.251512,54.25716 -6.379221,81.33334 3.063721,44.30607 19.142368,93.85852 39.702639,133.17134 5.349182,10.22807 15.245641,15.58797 22.127471,24.49943 8.336659,10.79533 12.700101,26.31165 14.513331,39.66256 4.60025,33.87183 -10.544235,67.52421 -27.425424,96 -7.070791,11.92725 -26.380995,30.46826 -19.293568,45.29797 6.328765,13.24231 21.87802,22.28325 33.91079,29.67342 21.730792,13.3464 42.852682,25.98442 62.666662,42.21334 65.68961,53.80404 117.5131,120.80567 179.99999,177.48194 z"
id="path8795" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

1
qr/README Normal file
View file

@ -0,0 +1 @@
This directory caches QR code files.

140
xhtml/disco.xhtml Normal file
View file

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Kayla (Céile) XMPP Invite -->
<!-- Zenya (Xenia) XMPP Invite -->
<!-- Fast And Sleek Invite (FASI) -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<title>XMPP: {{action}} {{title}}</title>
<meta name="description" content="{{action}} {{title}}" />
<meta name="generator" content="Fast And Sleek Invite" />
<meta name="uri" content="{{xmpp_uri}}" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:description" content="{{action}} {{title}}" />
<meta property="og:image" content="/photo/{{filename}}" />
<meta property="og:site_name" content="XMPP" />
<meta property="og:title" content="{{title}}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{url}}" />
<link rel="alternate icon" href="/img/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/img/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/css/stylesheet.css" media="screen" type="text/css" />
</head>
<body>
<div id="overlay">
<div id="bar">
<a href="https://xmpp.org">
<img id="logo" src="/img/xmpp-logo-wordmark-horizontal.svg" />
</a>
<a id="download" href="https://xmpp.org/software/">
Download
</a>
</div>
<div id="container-of-profile">
<div id="profile-compact">
</div>
<div id="profile">
<div>
<a id="download-narrow" href="https://xmpp.org/software/">
<img id="logo-narrow" src="/img/xmpp-logo.svg" />
Download
</a>
</div>
<h1>{{title}}</h1>
{% if note %}
<h2>{{note}}</h2>
{% endif %}
{% if services %}
<div id="services">
{% for category in services %}
{% if services[category] %}
<h3>Services ({{category}})</h3>
<dl>
{% for service in services[category] %}
{% if service['info']['text'] %}
<dt>
<h4>{{service['jid']}}</h4>
</dt>
<dd>
<strong>
<code>{{service['info']['condition']}}</code>
</strong>
</dd>
<dd>
<code>{{service['info']['text']}}</code>
</dd>
{% else %}
<dt>
<h4>
{% if service['identity'] and service['identity'][3] %}
{{service['identity'][3]}}
{% else %}
{{service['name']}}
{% endif %}
</h4>
</dt>
<dd>
<pre>Jabber ID: <a href="/d/{{service['jid']}}">{{service['jid']}}</a></pre>
{% if service['node'] %}
<pre>Node Name: <a href="/d/{{service['jid']}}/{{service['node']}}">{{service['node']}}</a></pre>
{% endif %}
</dd>
{% if service['kind'] %}
<dd>
<pre> Kind: {{service['kind']}}</pre>
</dd>
{% endif %}
{% if service['identity'] %}
<dd>
<pre> Category: {{service['identity'][0]}}</pre>
</dd>
<dd>
<pre> Type: {{service['identity'][1]}}</pre>
</dd>
{% endif %}
{% endif %}
{% endfor %}
</dl>
{% endif %}
{% endfor %}
</div>
{% endif %}
<div>
<pre id="xmpp-uri">{{xmpp_uri}}</pre>
</div>
{% if exception %}
<div>
<code id="exception">{{exception}}</code>
</div>
{% endif %}
<!-- % if mix or muc % -->
{% if link_href %}
<div>
<a id="action" href="{{link_href}}">
{{link_text}}
</a>
</div>
{% endif %}
<!-- div>
<a id="preview" href="/view/{{jid_bare}}">
Preview journal OR Preview group chat
</a>
</div -->
<!-- div>
<a href="https://xmpp.org">
<img id="logo-bottom" src="/img/xmpp-logo-wordmark-vertical.svg" />
</a>
</div -->
</div>
</div>
<!-- div id="note">
The Universal Messaging Standard
</div -->
{% if message %}
<div id="message">{{message}}</div>
{% endif %}
</div>
</body>
</html>

109
xhtml/jid.xhtml Normal file
View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Kayla (Céile) XMPP Invite -->
<!-- Zenya (Xenia) XMPP Invite -->
<!-- Fast And Sleek Invite (FASI) -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<title>XMPP: {{action}} {{title}}</title>
<meta name="description" content="{{action}} {{title}}" />
<meta name="generator" content="Fast And Sleek Invite" />
<meta name="uri" content="{{xmpp_uri}}" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:description" content="{{action}} {{title}}" />
<meta property="og:image" content="/photo/{{filename}}" />
<meta property="og:site_name" content="XMPP" />
<meta property="og:title" content="{{title}}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{url}}" />
<link rel="alternate icon" href="/img/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/img/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/css/stylesheet.css" media="screen" type="text/css" />
{% if selection %}
<style>
html {
background-repeat: no-repeat;
background: linear-gradient(
to right,
RGB({{selection[0][2]}}, {{selection[0][1]}}, {{selection[0][0]}}),
RGB({{selection[1][2]}}, {{selection[1][1]}}, {{selection[1][0]}})
);
}
</style>
{% endif %}
</head>
<body>
<div id="overlay">
<div id="bar">
<a href="https://xmpp.org">
<img id="logo" src="/img/xmpp-logo-wordmark-horizontal.svg" />
</a>
<a id="download" href="https://xmpp.org/software/">
Download
</a>
</div>
<div id="container-of-profile">
<div id="profile">
<div>
<a id="download-narrow" href="https://xmpp.org/software/">
<img id="logo-narrow" src="/img/xmpp-logo.svg" />
Download
</a>
</div>
<h1>{{title}}</h1>
{% if filename %}
<div id="graphics">
<!-- a href="xmpp:{{jid_bare}}" -->
<img id="photo" src="/photo/{{filename}}" />
<img id="qrcode" src="/qr/{{jid_bare}}.png" />
<!-- /a -->
</div>
{% endif %}
{% if note %}
<h3>{{note}}</h3>
{% endif %}
<div>
<pre id="xmpp-uri">{{xmpp_uri}}</pre>
</div>
{% if exception %}
<div>
<code>{{exception}}</code>
</div>
{% endif %}
<!-- % if mix or muc % -->
{% if link_href %}
<div>
<a id="action" href="{{link_href}}">
{{link_text}}
</a>
</div>
{% endif %}
<!-- div>
<a id="preview" href="/view/{{jid_bare}}">
Preview journal OR Preview group chat
</a>
</div -->
{% if count %}
<div id="count">
<a href="{{view_href}}">
{{count}} {{instance}}
</a>
</div>
{% endif %}
<!-- div>
<a href="https://xmpp.org">
<img id="logo-bottom" src="/img/xmpp-logo-wordmark-vertical.svg" />
</a>
</div -->
</div>
</div>
<!-- div id="note">
The Universal Messaging Standard
</div -->
{% if message %}
<div id="message">{{message}}</div>
{% endif %}
</div>
</body>
</html>

69
xhtml/main.xhtml Normal file
View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Kayla (Céile) XMPP Invite -->
<!-- Zenya (Xenia) XMPP Invite -->
<!-- Fast And Sleek Invite (FASI) -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<title>FASI : Welcome</title>
<meta name="description" content="This is an XMPP lookup service, powered by FASI (Fast And Sleek Invite)." />
<meta name="generator" content="Fast And Sleek Invite" />
<meta name="uri" content="{{xmpp_uri}}" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:description" content="FASI : Welcome" />
<meta property="og:image" content="/img/xmpp-logo-wordmark-vertical.svg" />
<meta property="og:site_name" content="XMPP" />
<meta property="og:title" content="FASI : Welcome" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{url}}" />
<link rel="alternate icon" href="/img/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/img/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/css/stylesheet.css" media="screen" type="text/css" />
</head>
<body>
<div id="overlay">
<div id="bar">
<a href="https://xmpp.org">
<img id="logo" src="/img/xmpp-logo-wordmark-horizontal.svg" />
</a>
<a id="download" href="https://xmpp.org/software/">
Download
</a>
</div>
<div id="container-of-profile">
<div id="profile">
<div>
<a id="download-narrow" href="https://xmpp.org/software/">
<img id="logo-narrow" src="/img/xmpp-logo.svg" />
Download
</a>
</div>
<h1>FASI</h1>
<h3>Fast And Sleek Invite</h3>
<div>
<form action="/" method="get">
<div>
<label for="jid">Enter a Jabber ID to create an invitation card for.</label>
</div>
<div>
<input type="text" id="jid" name="jid" placeholder="e.g. romeo@jabber.org" />
</div>
<div>
<input id="input" type="submit" value="Invite" />
</div>
</form>
</div>
<div>
<a href="https://xmpp.org">
<img id="logo-bottom" src="/img/xmpp-logo-wordmark-vertical.svg" />
</a>
</div>
</div>
</div>
<div id="note">
The Universal Messaging Standard
</div>
</div>
</body>
</html>

109
xhtml/node.xhtml Normal file
View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Kayla (Céile) XMPP Invite -->
<!-- Zenya (Xenia) XMPP Invite -->
<!-- Fast And Sleek Invite (FASI) -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<title>XMPP: {{action}} {{title}}</title>
<meta name="description" content="{{action}} {{title}}" />
<meta name="generator" content="Fast And Sleek Invite" />
<meta name="uri" content="{{xmpp_uri}}" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:description" content="{{action}} {{title}}" />
<meta property="og:image" content="/photo/{{filename}}" />
<meta property="og:site_name" content="XMPP" />
<meta property="og:title" content="{{title}}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{url}}" />
<link rel="alternate icon" href="/img/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/img/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/css/stylesheet.css" media="screen" type="text/css" />
</head>
<body>
<div id="overlay">
<div id="bar">
<a href="https://xmpp.org">
<img id="logo" src="/img/xmpp-logo-wordmark-horizontal.svg" />
</a>
<a id="download" href="https://xmpp.org/software/">
Download
</a>
</div>
<div id="container-of-profile">
<div id="profile-compact">
</div>
<div id="profile">
<div>
<a id="download-narrow" href="https://xmpp.org/software/">
<img id="logo-narrow" src="/img/xmpp-logo.svg" />
Download
</a>
</div>
<h1>{{title}}</h1>
{% if note %}
<h2>{{note}}</h2>
{% endif %}
{% if entries %}
<div id="entries">
{% for entry in entries %}
<div class="entry">
<strong>{{entry['title']}}</strong>
<div class="summary">{{entry['content'] or entry['summary']}}</div>
<div class="date">{{entry['updated'] or entry['published']}}</div>
{% if entry['tags'] %}
<div class="tags">
<span>Tags:</span>
{% for tag in entry['tags'] %}
<span>{{tag}}</span>
{% endfor %}
</div>
{% endif %}
<div class="link">
<a href="{{entry['link']}}">
{{entry['title']}}
</a>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<div>
<pre id="xmpp-uri">{{xmpp_uri}}</pre>
</div>
{% if exception %}
<div>
<code id="exception">{{exception}}</code>
</div>
{% endif %}
<!-- % if mix or muc % -->
{% if link_href %}
<div>
<a id="action" href="{{link_href}}">
{{link_text}}
</a>
</div>
{% endif %}
<!-- div>
<a id="preview" href="/view/{{jid_bare}}">
Preview journal OR Preview group chat
</a>
</div -->
<!-- div>
<a href="https://xmpp.org">
<img id="logo-bottom" src="/img/xmpp-logo-wordmark-vertical.svg" />
</a>
</div -->
</div>
</div>
<!-- div id="note">
The Universal Messaging Standard
</div -->
{% if message %}
<div id="message">{{message}}</div>
{% endif %}
</div>
</body>
</html>