Add support for preview of conferences;

Fix some errors;
Improve display of node items of type Atom Over XMPP (XEP-0277 and XEP-0472).
This commit is contained in:
Schimon Jehudah, Adv. 2024-10-13 18:42:44 +03:00
parent 44718051d0
commit aa90d922b0
10 changed files with 758 additions and 80 deletions

View file

@ -38,7 +38,7 @@ The main reasons for the realization of FASI are to:
- View journal articles (i.e. PubSub node items that are published as Atom Over - View journal articles (i.e. PubSub node items that are published as Atom Over
XMPP). XMPP).
### Unintended features ### Extended features
- Browse services; - Browse services;
- Explore JID and node items. - Explore JID and node items.

View file

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

View file

@ -1,4 +1,8 @@
* { #bar,
#action,
#action-bar,
#graphics,
#input {
user-select: none; user-select: none;
} }
@ -6,8 +10,8 @@ div, h1, h2, h3, h4, h5 {
font-family: system-ui; font-family: system-ui;
} }
h1, h2, h3, h4, h5 { h1, h2, h3 {
user-select: text; text-align: center;
} }
html { html {
@ -35,19 +39,28 @@ body {
/* background-size: cover; */ /* background-size: cover; */
margin: 0; margin: 0;
min-height: 100%; min-height: 100%;
min-width: 450px;
} }
/* /*
div:has(#bar) { div:has(#bar) {
height: 100vh; height: 100vh;
} }
*/ */
#bar {
#bar,
#content > #entries > .entry,
#profile-top,
#table-of-contents > ol,
#profile {
background: #f5f5f5; background: #f5f5f5;
}
#bar {
display: flex; display: flex;
filter: drop-shadow(0 0 4px grey); filter: drop-shadow(0 0 4px grey);
height: 3em; height: 3em;
justify-content: space-between; justify-content: space-between;
margin-bottom: 2.5em;
padding-bottom: 1em; padding-bottom: 1em;
padding-left: 0.5em; padding-left: 0.5em;
padding-right: 0.5em; padding-right: 0.5em;
@ -64,6 +77,7 @@ div:has(#bar) {
} }
#xmpp-uri { #xmpp-uri {
cursor: default;
user-select: all; user-select: all;
} }
@ -89,6 +103,7 @@ input:not(
label, label,
#action > a, #action > a,
#actions-bar > a,
#exception, #exception,
#xmpp-uri, #xmpp-uri,
#preview { #preview {
@ -101,7 +116,9 @@ label,
/* padding-bottom: 1em; */ /* padding-bottom: 1em; */
} }
#action > a { #action > a,
#action-bar > a,
#number-of-pages > a {
display: inline-block; display: inline-block;
font-size: 1.2em; font-size: 1.2em;
min-width: 90px; min-width: 90px;
@ -109,6 +126,12 @@ label,
width: 15%; width: 15%;
} }
#actions-compact > a {
margin: 0.1em;
padding: 0.8em;
text-decoration: none;
}
#download, #download,
#input { #input {
border-radius: 2em; border-radius: 2em;
@ -116,23 +139,46 @@ label,
padding: 0.5em; padding: 0.5em;
} }
#action { #profile-top {
display: flex;
justify-content: space-between;
text-align: center;
}
#action-bar,
#number-of-pages {
display: flex;
justify-content: center;
text-align: center;
}
#action,
#action-bar,
#actions-compact {
margin-bottom: 1em; margin-bottom: 1em;
} }
#action, #action,
#input { #action-bar,
#actions-compact,
#input,
#number-of-pages {
background: #13b5ea; /* #002b5c */ background: #13b5ea; /* #002b5c */
} }
#action > a:hover, #action > a:hover,
#input:hover { #action-bar > a:hover,
#actions-compact > a:hover,
#input:hover,
#number-of-pages > a:hover {
background: #1b3967; background: #1b3967;
} }
#action > a, #action > a,
#action-bar > a,
#download, #download,
#input { #input,
#number-of-pages > a {
color: #f5f5f5; color: #f5f5f5;
font-weight: bold; font-weight: bold;
padding-left: 2em; padding-left: 2em;
@ -218,10 +264,48 @@ h3, h4, h5 {
padding-bottom: 0.5em; padding-bottom: 0.5em;
} }
#profile-top,
#content > #entries > .entry {
padding: 2em;
}
#profile-top,
#content > #entries > .entry {
border-radius: 30px;
margin-bottom: 2em;
margin-left: 1em; /* 0.5 */
margin-right: 1em; /* 0.5 */
margin-top: 2em;
}
/*
#content > #entries > .entry {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
margin-right: 0;
}
*/
/*
#content > #entries > .entry:first-child {
margin-top: 0;
}
*/
#content,
#profile-compact {
filter: drop-shadow(2px 4px 6px grey);
}
#container-of-content {
/* margin-left: 8em; */
/* overflow: auto; */
}
#profile { #profile {
background: #f5f5f5;
border-radius: 30px; border-radius: 30px;
filter: drop-shadow(2px 4px 6px grey); filter: drop-shadow(2px 4px 6px grey);
margin-top: 2.5em;
margin-bottom: 2.5em; margin-bottom: 2.5em;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -244,13 +328,15 @@ h3, h4, h5 {
} }
#entries { #entries {
padding: 2em; /* padding: 2em; */
text-align: left; text-align: left;
} }
/*
.entry > * { .entry > * {
margin-bottom: 1em; margin-bottom: 1em;
} }
*/
.summary { .summary {
white-space: pre-wrap; white-space: pre-wrap;
@ -261,6 +347,17 @@ h3, h4, h5 {
text-align: left; text-align: left;
} }
.link {
margin: 0.1em;
padding: 0.8em;
text-decoration: none;
}
.permalink {
padding-right: 0.8em;
text-decoration: none;
}
#count > a, #count > a,
#preview { #preview {
color: #5c5656; color: #5c5656;
@ -306,6 +403,8 @@ h3, h4, h5 {
h1, h2 ,h3, h4, h5, h1, h2 ,h3, h4, h5,
label[for="jid"], label[for="jid"],
#count > a, #count > a,
#count > a:hover,
#preview:hover,
#jid, #jid,
#xmpp-uri { #xmpp-uri {
color: #fbfbfe; color: #fbfbfe;
@ -325,7 +424,9 @@ h3, h4, h5 {
url(/img/background.svg); url(/img/background.svg);
} }
#bar { #bar,
#note,
#profile {
background: #2b2a33; background: #2b2a33;
} }
@ -343,7 +444,6 @@ h3, h4, h5 {
} }
#profile { #profile {
background: #2b2a33;
filter: drop-shadow(2px 4px 6px grey); /* TODO Reverse color */ filter: drop-shadow(2px 4px 6px grey); /* TODO Reverse color */
} }
@ -351,13 +451,7 @@ h3, h4, h5 {
color: #2b2a33; color: #2b2a33;
} }
#count > a:hover,
#preview:hover {
color: #fbfbfe;
}
#note { #note {
background: #2b2a33;
color: #f5f5f5; color: #f5f5f5;
text-shadow: 1px 1px #fbfbfe; text-shadow: 1px 1px #fbfbfe;
} }
@ -427,7 +521,6 @@ h3, h4, h5 {
#bar { #bar {
border-bottom: 1px solid #c0c0c0; border-bottom: 1px solid #c0c0c0;
filter: unset; filter: unset;
margin-bottom: 0;
} }
#message { #message {
@ -445,6 +538,7 @@ h3, h4, h5 {
#profile { #profile {
border-radius: unset; border-radius: unset;
filter: unset; filter: unset;
margin-top: unset;
margin-bottom: unset; margin-bottom: unset;
max-height: unset; max-height: unset;
max-width: unset; max-width: unset;

472
fasi.py
View file

@ -16,7 +16,7 @@ import qrcode
import random import random
import re import re
from slixmpp import ClientXMPP, stanza from slixmpp import ClientXMPP, stanza
from slixmpp.exceptions import IqError, IqTimeout from slixmpp.exceptions import IqError, IqTimeout, PresenceError
from starlette.responses import RedirectResponse from starlette.responses import RedirectResponse
#import time #import time
import tomli_w import tomli_w
@ -57,7 +57,7 @@ class XmppInstance(ClientXMPP):
#self.disconnect() #self.disconnect()
class HttpInstance: class HttpInstance:
def __init__(self, jabber_id, password): def __init__(self, jabber_id, password, alias):
self.app = FastAPI() self.app = FastAPI()
templates = Jinja2Templates(directory='xhtml') templates = Jinja2Templates(directory='xhtml')
@ -78,9 +78,231 @@ class HttpInstance:
# def logo_get(): # def logo_get():
# return FileResponse('graphic/hermes.svg') # return FileResponse('graphic/hermes.svg')
@self.app.get('/v/{jid}')
async def view_jid(request: Request, jid):
"""View messages of jabber id"""
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 = node_title = note = number_of_pages = page_number = previous = selection = services_sorted = None
node_name = 'urn:xmpp:microblog:0'
link_href = 'xmpp:{}?join'.format(jid_bare)
link_text = 'Join'
xmpp_uri = '{}'.format(jid_bare)
# 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']
links = []
if jid_info['error']:
message = '{}: {} (XEP-0030)'.format(jid_info['text'], jid_info['condition'])
action = 'Connect with'
link_href = 'xmpp:{}'.format(jid_bare)
links.append(['Connect', link_href])
xmpp_uri = jid_bare
elif jid_kind in ('conference', 'server'):
action = 'Discover'
if jid_kind == 'conference':
instance = 'conferences'
elif jid_kind == 'server':
instance = 'services'
link_href = 'xmpp:{}?disco;type=get;request=items'.format(jid_bare)
links.append(['Discover', link_href])
view_href = '/d/' + jid_bare
xmpp_uri = 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_href = 'xmpp:{}?join'.format(jid_bare)
links.append(['Join', link_href])
view_href = '/v/' + jid_bare
xmpp_uri = 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_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)
view_href = '/d/{}/{}'.format(jid_bare, node_name)
xmpp_uri = '{}?;node={}'.format(jid_bare, node_name)
else:
action = 'Browse'
instance = 'nodes'
link_href = 'xmpp:{}?disco;type=get;request=items'.format(jid_bare)
links.append(['Browse', link_href])
view_href = '/d/' + jid_bare
xmpp_uri = jid_bare
else:
action = 'Message'
instance = 'articles'
link_href = 'xmpp:{}?message'.format(jid_bare)
links.append(['Add', 'xmpp:{}?roster'.format(jid_bare)])
links.append(['Message', link_href])
node_name = 'urn:xmpp:microblog:0'
view_href = '/d/{}/{}'.format(jid_bare, node_name)
xmpp_uri = jid_bare
links.append(['Subscribe', 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)])
links.append(['View', 'xmpp:{}?pubsub;node={}'.format(jid_bare, node_name)])
links.append(['vCard', 'xmpp:{}?vcard'.format(jid_bare)])
# JID info
# NOTE Group chat of Psi+ Project at jabber.ru has a note in its vCard.
# TODO Retrieve group chat title (try also with xep_0045
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']
}
# Group chat messages
action = 'Join'
messages = []
room_info_muc = await XmppXep0045.get_room_information(xmpp_instance, jid_bare, alias, maxstanzas=50)
messages = room_info_muc['iq'][3]
messages.reverse()
subject = room_info_muc['iq'][1]['subject']
page_number = request.query_params.get('page', '')
if page_number:
try:
page_number = int(page_number)
ix = (page_number -1) * 10
except:
ix = page_number = 0
else:
ix = page_number = 0
messages_10 = messages[ix:][:10]
number_of_pages = int(len(messages) / 10)
if number_of_pages < len(messages) / 10: number_of_pages += 1
if not room_info_muc:
action = 'Warning'
node_title = jid_info['condition']
node_note = jid_info['text']
services = services_sorted = None
elif isinstance(room_info_muc, IqTimeout):
action = 'Warning'
node_title = 'Timeout'
node_note = 'Timeout error'
services = services_sorted = None
elif isinstance(room_info_muc, IqError):
action = 'Warning'
breakpoint()
node_title = room_info_muc['condition']
node_note = room_info_muc['text']
services = services_sorted = None
else:
#title = title or node_name
if not node_title: node_title = node_name
node_note = jid_bare
xmpp_instance.disconnect()
# 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)
# QR code
Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare)
except Exception as e:
exception = str(e)
action = 'Error'
title = 'Slixmpp error'
xmpp_uri = note = jid
filename = jid_bare = link_href = link_tex = node_note = node_title = number_of_pages = page_number = previous = selection = services = services_sorted = url = None
#if title == 'remote-server-timeout':
# raise HTTPException(status_code=408, detail='remote-server-timeout')
#else:
template_file = 'conference.xhtml'
template_dict = {
'action' : action,
'exception' : exception,
'filename' : filename,
'jid_bare' : jid,
'jid_note' : jid_detail['note'],
'jid_title' : jid_detail['name'],
'links' : links,
'messages' : messages_10,
'node_title' : node_title,
'node_note' : node_note,
'node_name' : node_name,
'number_of_pages' : number_of_pages,
'page_number' : page_number,
'previous' : previous,
'request' : request,
'subject' : subject,
'url' : request.url._url,
'xmpp_uri' : xmpp_uri}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
# NOTE Was /b/ # NOTE Was /b/
@self.app.get('/d/{jid}/{node_name}') @self.app.get('/d/{jid}/{node_name}')
async def browse_jid_node_get(request: Request, jid, node_name): @self.app.get('/d/{jid}/{node_name}/{item_id}')
async def browse_jid_node_get(request: Request, jid, node_name, item_id=None):
"""Browse items of a pubsub node""" """Browse items of a pubsub node"""
jid_path = urlsplit(jid).path jid_path = urlsplit(jid).path
if parseaddr(jid_path)[1] == jid_path: if parseaddr(jid_path)[1] == jid_path:
@ -90,8 +312,8 @@ class HttpInstance:
note = 'Jabber ID appears to be malformed' note = 'Jabber ID appears to be malformed'
try: try:
exception = note = selection = services_sorted = None exception = note = number_of_pages = page_number = previous = selection = services_sorted = None
title = node_name node_title = node_name
link_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name) link_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)
link_text = 'Subscribe' link_text = 'Subscribe'
xmpp_uri = '{}?;node={}'.format(jid_bare, node_name) xmpp_uri = '{}?;node={}'.format(jid_bare, node_name)
@ -100,9 +322,92 @@ class HttpInstance:
xmpp_instance = XmppInstance(jabber_id, password, jid_bare) xmpp_instance = XmppInstance(jabber_id, password, jid_bare)
xmpp_instance.connect() 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']
links = []
if jid_info['error']:
message = '{}: {} (XEP-0030)'.format(jid_info['text'], jid_info['condition'])
action = 'Connect with'
link_href = 'xmpp:{}'.format(jid_bare)
links.append(['Connect', link_href])
xmpp_uri = jid_bare
elif jid_kind in ('conference', 'server'):
action = 'Discover'
if jid_kind == 'conference':
instance = 'conferences'
elif jid_kind == 'server':
instance = 'services'
link_href = 'xmpp:{}?disco;type=get;request=items'.format(jid_bare)
links.append(['Discover', link_href])
view_href = '/d/' + jid_bare
xmpp_uri = 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_href = 'xmpp:{}?join'.format(jid_bare)
links.append(['Join', link_href])
view_href = '/v/' + jid_bare
xmpp_uri = 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_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)
view_href = '/d/{}/{}'.format(jid_bare, node_name)
xmpp_uri = '{}?;node={}'.format(jid_bare, node_name)
else:
action = 'Browse'
instance = 'nodes'
link_href = 'xmpp:{}?disco;type=get;request=items'.format(jid_bare)
links.append(['Browse', link_href])
view_href = '/d/' + jid_bare
xmpp_uri = jid_bare
else:
action = 'Message'
instance = 'articles'
link_href = 'xmpp:{}?message'.format(jid_bare)
links.append(['Add', 'xmpp:{}?roster'.format(jid_bare)])
links.append(['Message', link_href])
node_name = 'urn:xmpp:microblog:0'
view_href = '/d/{}/{}'.format(jid_bare, node_name)
xmpp_uri = jid_bare
links.append(['Subscribe', 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name)])
links.append(['View', 'xmpp:{}?pubsub;node={}'.format(jid_bare, node_name)])
links.append(['vCard', 'xmpp:{}?vcard'.format(jid_bare)])
# 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 # Title
if '@' in jid_bare and node_name == 'urn:xmpp:microblog:0': if '@' in jid_bare and node_name == 'urn:xmpp:microblog:0':
title = 'Journal' node_title = 'Journal'
else: else:
jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare)
iq = jid_items['iq'] iq = jid_items['iq']
@ -111,47 +416,111 @@ class HttpInstance:
category = 'unsorted' category = 'unsorted'
for item in iq_disco_items_items: for item in iq_disco_items_items:
if item[2] and item[1] == node_name: if item[2] and item[1] == node_name:
title = item[2] node_title = item[2]
break break
# Node items # Node items
action = 'Browse' action = 'Browse'
entries = [] entries = []
node_items = await XmppXep0060.get_node_items(xmpp_instance, jid_bare, node_name) if item_id:
previous = True
node_items = await XmppXep0060.get_node_items(xmpp_instance, jid_bare, node_name, item_ids=[item_id])
else:
item_ids = []
node_item_ids = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name)
for item_id in node_item_ids['disco_items']['items']:
item_ids.append(item_id[2])
# NOTE Consider to skip the reversal of order, because, then, items can be found at the same page.
item_ids.reverse()
page_number = request.query_params.get('page', '')
if page_number:
try:
page_number = int(page_number)
ix = (page_number -1) * 10
except:
ix = page_number = 0
else:
ix = page_number = 0
item_ids_10 = item_ids[ix:][:10]
node_items = await XmppXep0060.get_node_items(xmpp_instance, jid_bare, node_name, item_ids=item_ids_10)
number_of_pages = int(len(item_ids) / 10)
if number_of_pages < len(item_ids) / 10: number_of_pages += 1
if not node_items: if not node_items:
action = 'Warning' action = 'Warning'
title = jid_info['condition'] node_title = jid_info['condition']
note = jid_info['text'] node_note = jid_info['text']
services = services_sorted = None services = services_sorted = None
elif isinstance(node_items, IqTimeout): elif isinstance(node_items, IqTimeout):
action = 'Warning' action = 'Warning'
title = 'Timeout' node_title = 'Timeout'
note = 'Timeout error' node_note = 'Timeout error'
services = services_sorted = None services = services_sorted = None
elif isinstance(node_items, IqError): elif isinstance(node_items, IqError):
action = 'Warning' action = 'Warning'
breakpoint() breakpoint()
title = node_items['condition'] node_title = node_items['condition']
note = node_items['text'] node_note = node_items['text']
services = services_sorted = None services = services_sorted = None
else: else:
#title = title or node_name #title = title or node_name
if not title: title = node_name if not node_title: node_title = node_name
note = jid_bare node_note = jid_bare
for item in node_items['pubsub']['items']: for item in node_items['pubsub']['items']:
item_payload = item['payload'] item_payload = item['payload']
entry = Syndication.extract_items(item_payload) entry = Syndication.extract_items(item_payload)
entry['id'] = item['id']
entries.append(entry) entries.append(entry)
#if len(entries) > 10: break
if entries: entries.reverse() if entries: entries.reverse()
xmpp_instance.disconnect() xmpp_instance.disconnect()
# 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)
# QR code
Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare)
except Exception as e: except Exception as e:
exception = str(e) exception = str(e)
action = 'Error' action = 'Error'
title = 'Slixmpp error' title = 'Slixmpp error'
xmpp_uri = note = jid xmpp_uri = note = jid
filename = jid_bare = services = url = link_href = link_text = None filename = jid_bare = link_href = link_tex = node_note = node_title = number_of_pages = page_number = previous = selection = services = services_sorted = url = None
#if title == 'remote-server-timeout': #if title == 'remote-server-timeout':
# raise HTTPException(status_code=408, detail='remote-server-timeout') # raise HTTPException(status_code=408, detail='remote-server-timeout')
@ -159,16 +528,21 @@ class HttpInstance:
template_file = 'node.xhtml' template_file = 'node.xhtml'
template_dict = { template_dict = {
'action' : action, 'action' : action,
'exception' : exception,
'filename' : 'default.svg',
'jid_bare' : jid,
'note' : note,
'request' : request,
'entries' : entries, 'entries' : entries,
'title' : title, 'exception' : exception,
'filename' : filename,
'jid_bare' : jid,
'jid_note' : jid_detail['note'],
'jid_title' : jid_detail['name'],
'links' : links,
'node_title' : node_title,
'node_note' : node_note,
'node_name' : node_name,
'number_of_pages' : number_of_pages,
'page_number' : page_number,
'previous' : previous,
'request' : request,
'url' : request.url._url, 'url' : request.url._url,
'link_href' : link_href,
'link_text' : link_text,
'xmpp_uri' : xmpp_uri} 'xmpp_uri' : xmpp_uri}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml' response.headers['Content-Type'] = 'application/xhtml+xml'
@ -260,7 +634,7 @@ class HttpInstance:
action = 'Error' action = 'Error'
title = 'Slixmpp error' title = 'Slixmpp error'
xmpp_uri = note = jid xmpp_uri = note = jid
filename = jid_bare = services = url = link_href = link_text = None filename = jid_bare = link_href = link_text = selection = services = services_sorted = url = None
#if title == 'remote-server-timeout': #if title == 'remote-server-timeout':
# raise HTTPException(status_code=408, detail='remote-server-timeout') # raise HTTPException(status_code=408, detail='remote-server-timeout')
@ -294,7 +668,7 @@ class HttpInstance:
return response return response
@self.app.get('/{jid}') @self.app.get('/{jid}')
async def jid_node_get(request: Request, jid): async def jid_get(request: Request, jid):
node_name = request.query_params.get('node', '') node_name = request.query_params.get('node', '')
if node_name: if node_name:
response = RedirectResponse(url='/{}/{}'.format(jid, node_name)) response = RedirectResponse(url='/{}/{}'.format(jid, node_name))
@ -437,6 +811,7 @@ class HttpInstance:
else: else:
title = jid_bare.split('@')[0] title = jid_bare.split('@')[0]
xmpp_instance.disconnect()
# Notes # Notes
jid_detail_note = jid_detail['note'] jid_detail_note = jid_detail['note']
@ -475,8 +850,6 @@ class HttpInstance:
else: else:
selection = Graphics.extract_colours_from_raster(filepath) selection = Graphics.extract_colours_from_raster(filepath)
xmpp_instance.disconnect()
# QR code # QR code
Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare) Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare)
@ -622,6 +995,7 @@ class Graphics:
print(selection) print(selection)
except Exception as e: except Exception as e:
selection = None
exception = str(e) exception = str(e)
print(exception) print(exception)
@ -841,11 +1215,44 @@ class XmppXep0030:
class XmppXep0045: class XmppXep0045:
async def get_room_information(self, jid, alias, maxchars=None, maxstanzas=None, seconds=None):
#logger.info('Joining groupchat\nJID : {}\n'.format(jid))
#jid_from = str(self.boundjid) if self.is_component else None
if not maxchars: maxchars = 1000
if not maxstanzas: maxstanzas = 50
if not seconds: seconds = 864000
try:
error = False
condition = text = None
#since = datetime.fromtimestamp(time.time()-seconds)
iq = await self['xep_0045'].join_muc_wait(
jid,
alias,
#maxchars=maxchars,
maxstanzas=maxstanzas,
#password=None,
#presence_options = {"pfrom" : jid_from},
#seconds=seconds,
#since=since,
#timeout=30
)
except (IqError, IqTimeout, PresenceError) as e:
error = True
iq = None
condition = e.iq['error']['condition']
text = e.iq['error']['text']
result = {
'error' : error,
'condition' : condition,
'text' : text,
'iq' : iq}
return result
async def get_room_data(self, jid_bare): async def get_room_data(self, jid_bare):
return await self['xep_0045'].get_room_config(jid_bare) return await self['xep_0045'].get_room_config(jid_bare)
async def get_number_of_participants(self, jid_bare): async def get_room_participants(self, jid_bare):
return len(await self['xep_0045'].get_roster(jid_bare)) return await self['xep_0045'].get_roster(jid_bare)
# NOTE: "Item not found", yet is a group chat # NOTE: "Item not found", yet is a group chat
@ -938,8 +1345,9 @@ def main():
account = data['account'] account = data['account']
jabber_id = account['xmpp'] jabber_id = account['xmpp']
password = account['pass'] password = account['pass']
alias = account['alias']
http_instance = HttpInstance(jabber_id, password) http_instance = HttpInstance(jabber_id, password, alias)
return http_instance.app return http_instance.app
app = main() app = main()

1
messages/README Normal file
View file

@ -0,0 +1 @@
This directory caches conference messages.

View file

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

1
vcard/README Normal file
View file

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

134
xhtml/conference.xhtml Normal file
View file

@ -0,0 +1,134 @@
<?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/logo-wordmark-horizontal.svg" />
</a>
<a id="download" href="https://xmpp.org/software/">
Download
</a>
</div>
<div id="container-of-content">
<div id="content">
{% if links %}
<div id="action-bar">
{% for link in links %}
<a href="{{link[1]}}">
{{link[0]}}
</a>
{% endfor %}
</div>
{% endif %}
<div id="profile-top">
{% if filename %}
<img id="photo" src="/photo/{{filename}}" />
{% endif %}
<span>
<h1>{% if jid_title %}{{jid_title}}{% else %}Group Chat{% endif %}</h1>
<a href="/{{jid_bare}}">
<h2>{% if jid_title %}{{jid_title}}{% else %}{{jid_bare}}{% endif %}</h2>
</a>
</span>
<img id="qrcode" src="/qr/{{jid_bare}}.png" />
</div>
<div>
{% if subject %}
<h3>
{{subject}}
</h3>
{% endif %}
</div>
{% if messages %}
<div id="entries">
{% for message in messages %}
<div class="entry"
id="{{message['id']}}">
<strong>{{message['mucnick']}}</strong>
<div class="summary">{{message['body']}}</div>
<div class="date">{{message['delay']['stamp'].__str__()}}</div>
<!--
% if reactions %
<div class="reactions">
% for reaction in reactions %
<span>{{reaction}}</span>
% endfor %
</div>
% endif %
-->
</div>
{% endfor %}
</div>
{% endif %}
{% if exception %}
<div>
<code id="exception">{{exception}}</code>
</div>
{% endif %}
{% if number_of_pages %}
<div id="number-of-pages">
{% if number_of_pages > 5 %}
<a href="?page=1">First</a>
<a href="?page={{page_number-1}}">{{page_number-1}}</a>
{% if number_of_pages > page_number %}
<a href="?page={{page_number}}">{{page_number}}</a>
{% endif %}
{% if number_of_pages > page_number+1 %}
<a href="?page={{page_number+1}}">{{page_number+1}}</a>
{% endif %}
<a href="?page={{number_of_pages}}">Last</a>
{% else %}
{% for number in range(number_of_pages) %}
<a href="?page={{number+1}}">{{number+1}}</a>
{% endfor %}
{% endif %}
</div>
{% endif %}
{% if previous %}
<div id="number-of-pages">
<a href="./">Previous</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/logo-wordmark-vertical.svg" />
</a>
</div -->
<!-- div id="note">
The Universal Messaging Standard
</div -->
</div>
</div>
{% if message %}
<div id="message">{{message}}</div>
{% endif %}
</div>
</body>
</html>

View file

@ -92,13 +92,15 @@
Preview journal OR Preview group chat Preview journal OR Preview group chat
</a> </a>
</div --> </div -->
{% if count %} <div id="count">
<div id="count"> <a href="{{view_href}}">
<a href="{{view_href}}"> {% if count %}
{{count}} {{instance}} {{count}} {{instance}}
</a> {% else %}
</div> Preview
{% endif %} {% endif %}
</a>
</div>
<!-- div> <!-- div>
<a href="https://xmpp.org"> <a href="https://xmpp.org">
<img id="logo-bottom" src="/img/logo-wordmark-vertical.svg" /> <img id="logo-bottom" src="/img/logo-wordmark-vertical.svg" />

View file

@ -31,19 +31,39 @@
Download Download
</a> </a>
</div> </div>
<div id="container-of-profile"> <div id="container-of-content">
<div id="profile-compact"> <div id="content">
{% if links %}
</div> <div id="action-bar">
<div id="profile"> {% for link in links %}
<h1>{{title}}</h1> <a href="{{link[1]}}">
{% if note %} {{link[0]}}
<h2>{{note}}</h2> </a>
{% endfor %}
</div>
{% endif %} {% endif %}
<div id="profile-top">
{% if filename %}
<img id="photo" src="/photo/{{filename}}" />
{% endif %}
<span>
<h1>{{node_title}}</h1>
<a href="/{{jid_bare}}/{{node_name}}">
<h2>{{jid_title}}</h2>
</a>
{% if node_note %}
<h3>
{{node_note}}
</h3>
{% endif %}
</span>
<img id="qrcode" src="/qr/{{jid_bare}}.png" />
</div>
{% if entries %} {% if entries %}
<div id="entries"> <div id="entries">
{% for entry in entries %} {% for entry in entries %}
<div class="entry"> <div class="entry"
id="{{entry['id']}}">
<strong>{{entry['title']}}</strong> <strong>{{entry['title']}}</strong>
<div class="summary">{{entry['content'] or entry['summary']}}</div> <div class="summary">{{entry['content'] or entry['summary']}}</div>
<div class="date">{{entry['updated'] or entry['published']}}</div> <div class="date">{{entry['updated'] or entry['published']}}</div>
@ -56,6 +76,11 @@
</div> </div>
{% endif %} {% endif %}
<div class="link"> <div class="link">
<a href="/d/{{jid_bare}}/{{node_name}}/{{entry['id']}}"
title="Permalink (i.e. permanent link)"
class="permalink">
🔗️
</a>
<a href="{{entry['link']}}"> <a href="{{entry['link']}}">
{{entry['title']}} {{entry['title']}}
</a> </a>
@ -64,20 +89,33 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<div>
<pre id="xmpp-uri">{{xmpp_uri}}</pre>
</div>
{% if exception %} {% if exception %}
<div> <div>
<code id="exception">{{exception}}</code> <code id="exception">{{exception}}</code>
</div> </div>
{% endif %} {% endif %}
<!-- % if mix or muc % --> {% if number_of_pages %}
{% if link_href %} <div id="number-of-pages">
<div id="action"> {% if number_of_pages > 5 %}
<a href="{{link_href}}"> <a href="?page=1">First</a>
{{link_text}} <a href="?page={{page_number-1}}">{{page_number-1}}</a>
</a> {% if number_of_pages > page_number %}
<a href="?page={{page_number}}">{{page_number}}</a>
{% endif %}
{% if number_of_pages > page_number+1 %}
<a href="?page={{page_number+1}}">{{page_number+1}}</a>
{% endif %}
<a href="?page={{number_of_pages}}">Last</a>
{% else %}
{% for number in range(number_of_pages) %}
<a href="?page={{number+1}}">{{number+1}}</a>
{% endfor %}
{% endif %}
</div>
{% endif %}
{% if previous %}
<div id="number-of-pages">
<a href="./">Previous</a>
</div> </div>
{% endif %} {% endif %}
<!-- div> <!-- div>