This is the initial set of files of this project.
This commit is contained in:
parent
48f767697d
commit
95c1f30ebc
15 changed files with 2212 additions and 1 deletions
38
README.md
38
README.md
|
@ -1,3 +1,39 @@
|
|||
# FASI
|
||||
|
||||
Fast And Sleek Invite (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
5
configuration.toml
Normal 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
384
css/stylesheet.css
Normal 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
934
fasi.py
Normal 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
109
img/favicon.svg
Normal 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 |
123
img/xmpp-logo-wordmark-horizontal.svg
Normal file
123
img/xmpp-logo-wordmark-horizontal.svg
Normal 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 |
30
img/xmpp-logo-wordmark-vertical.svg
Normal file
30
img/xmpp-logo-wordmark-vertical.svg
Normal 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
108
img/xmpp-logo.svg
Normal 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
1
photo/README
Normal file
|
@ -0,0 +1 @@
|
|||
This directory caches photo files.
|
53
photo/default.svg
Normal file
53
photo/default.svg
Normal 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
1
qr/README
Normal file
|
@ -0,0 +1 @@
|
|||
This directory caches QR code files.
|
140
xhtml/disco.xhtml
Normal file
140
xhtml/disco.xhtml
Normal 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
109
xhtml/jid.xhtml
Normal 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
69
xhtml/main.xhtml
Normal 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
109
xhtml/node.xhtml
Normal 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>
|
Loading…
Reference in a new issue