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:
parent
44718051d0
commit
aa90d922b0
10 changed files with 758 additions and 80 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
472
fasi.py
|
@ -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
1
messages/README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This directory caches conference messages.
|
|
@ -1 +0,0 @@
|
||||||
This directory caches photo files.
|
|
1
vcard/README
Normal file
1
vcard/README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This directory caches vcard files.
|
134
xhtml/conference.xhtml
Normal file
134
xhtml/conference.xhtml
Normal 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>
|
|
@ -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}}
|
||||||
|
{% else %}
|
||||||
|
Preview
|
||||||
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<!-- 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" />
|
||||||
|
|
|
@ -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 id="action-bar">
|
||||||
|
{% for link in links %}
|
||||||
|
<a href="{{link[1]}}">
|
||||||
|
{{link[0]}}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div id="profile">
|
|
||||||
<h1>{{title}}</h1>
|
|
||||||
{% if note %}
|
|
||||||
<h2>{{note}}</h2>
|
|
||||||
{% 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>
|
||||||
|
|
Loading…
Reference in a new issue