diff --git a/README.md b/README.md index c5797a0..ffa99b4 100644 --- a/README.md +++ b/README.md @@ -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 XMPP). -### Unintended features +### Extended features - Browse services; - Explore JID and node items. diff --git a/configuration.toml b/configuration.toml index 958b3bf..e8a6d3e 100644 --- a/configuration.toml +++ b/configuration.toml @@ -1,5 +1,6 @@ # An account to connect FASI to the XMPP network [account] +alias = "FASI" # Alias xmpp = "" # Jabber ID pass = "" # Password diff --git a/css/stylesheet.css b/css/stylesheet.css index d7acc21..18039f3 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -1,4 +1,8 @@ -* { +#bar, +#action, +#action-bar, +#graphics, +#input { user-select: none; } @@ -6,8 +10,8 @@ div, h1, h2, h3, h4, h5 { font-family: system-ui; } -h1, h2, h3, h4, h5 { - user-select: text; +h1, h2, h3 { + text-align: center; } html { @@ -35,19 +39,28 @@ body { /* background-size: cover; */ margin: 0; min-height: 100%; + min-width: 450px; } + /* div:has(#bar) { height: 100vh; } */ -#bar { + +#bar, +#content > #entries > .entry, +#profile-top, +#table-of-contents > ol, +#profile { background: #f5f5f5; +} + +#bar { display: flex; filter: drop-shadow(0 0 4px grey); height: 3em; justify-content: space-between; - margin-bottom: 2.5em; padding-bottom: 1em; padding-left: 0.5em; padding-right: 0.5em; @@ -64,6 +77,7 @@ div:has(#bar) { } #xmpp-uri { + cursor: default; user-select: all; } @@ -89,6 +103,7 @@ input:not( label, #action > a, +#actions-bar > a, #exception, #xmpp-uri, #preview { @@ -101,7 +116,9 @@ label, /* padding-bottom: 1em; */ } -#action > a { +#action > a, +#action-bar > a, +#number-of-pages > a { display: inline-block; font-size: 1.2em; min-width: 90px; @@ -109,6 +126,12 @@ label, width: 15%; } +#actions-compact > a { + margin: 0.1em; + padding: 0.8em; + text-decoration: none; +} + #download, #input { border-radius: 2em; @@ -116,23 +139,46 @@ label, 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; } #action, -#input { +#action-bar, +#actions-compact, +#input, +#number-of-pages { background: #13b5ea; /* #002b5c */ } #action > a:hover, -#input:hover { +#action-bar > a:hover, +#actions-compact > a:hover, +#input:hover, +#number-of-pages > a:hover { background: #1b3967; } #action > a, +#action-bar > a, #download, -#input { +#input, +#number-of-pages > a { color: #f5f5f5; font-weight: bold; padding-left: 2em; @@ -218,10 +264,48 @@ h3, h4, h5 { 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 { - background: #f5f5f5; border-radius: 30px; filter: drop-shadow(2px 4px 6px grey); + margin-top: 2.5em; margin-bottom: 2.5em; margin-left: auto; margin-right: auto; @@ -244,13 +328,15 @@ h3, h4, h5 { } #entries { - padding: 2em; + /* padding: 2em; */ text-align: left; } +/* .entry > * { margin-bottom: 1em; } +*/ .summary { white-space: pre-wrap; @@ -261,6 +347,17 @@ h3, h4, h5 { text-align: left; } +.link { + margin: 0.1em; + padding: 0.8em; + text-decoration: none; +} + +.permalink { + padding-right: 0.8em; + text-decoration: none; +} + #count > a, #preview { color: #5c5656; @@ -306,6 +403,8 @@ h3, h4, h5 { h1, h2 ,h3, h4, h5, label[for="jid"], #count > a, + #count > a:hover, + #preview:hover, #jid, #xmpp-uri { color: #fbfbfe; @@ -325,7 +424,9 @@ h3, h4, h5 { url(/img/background.svg); } - #bar { + #bar, + #note, + #profile { background: #2b2a33; } @@ -343,7 +444,6 @@ h3, h4, h5 { } #profile { - background: #2b2a33; filter: drop-shadow(2px 4px 6px grey); /* TODO Reverse color */ } @@ -351,13 +451,7 @@ h3, h4, h5 { color: #2b2a33; } - #count > a:hover, - #preview:hover { - color: #fbfbfe; - } - #note { - background: #2b2a33; color: #f5f5f5; text-shadow: 1px 1px #fbfbfe; } @@ -427,7 +521,6 @@ h3, h4, h5 { #bar { border-bottom: 1px solid #c0c0c0; filter: unset; - margin-bottom: 0; } #message { @@ -445,6 +538,7 @@ h3, h4, h5 { #profile { border-radius: unset; filter: unset; + margin-top: unset; margin-bottom: unset; max-height: unset; max-width: unset; diff --git a/fasi.py b/fasi.py index 280cc15..568f4f5 100644 --- a/fasi.py +++ b/fasi.py @@ -16,7 +16,7 @@ import qrcode import random import re from slixmpp import ClientXMPP, stanza -from slixmpp.exceptions import IqError, IqTimeout +from slixmpp.exceptions import IqError, IqTimeout, PresenceError from starlette.responses import RedirectResponse #import time import tomli_w @@ -57,7 +57,7 @@ class XmppInstance(ClientXMPP): #self.disconnect() class HttpInstance: - def __init__(self, jabber_id, password): + def __init__(self, jabber_id, password, alias): self.app = FastAPI() templates = Jinja2Templates(directory='xhtml') @@ -78,9 +78,231 @@ class HttpInstance: # def logo_get(): # 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/ @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""" jid_path = urlsplit(jid).path if parseaddr(jid_path)[1] == jid_path: @@ -90,8 +312,8 @@ class HttpInstance: note = 'Jabber ID appears to be malformed' try: - exception = note = selection = services_sorted = None - title = node_name + exception = note = number_of_pages = page_number = previous = selection = services_sorted = None + node_title = node_name link_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name) link_text = 'Subscribe' xmpp_uri = '{}?;node={}'.format(jid_bare, node_name) @@ -100,9 +322,92 @@ class HttpInstance: 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. + 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 '@' in jid_bare and node_name == 'urn:xmpp:microblog:0': - title = 'Journal' + node_title = 'Journal' else: jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) iq = jid_items['iq'] @@ -111,47 +416,111 @@ class HttpInstance: category = 'unsorted' for item in iq_disco_items_items: if item[2] and item[1] == node_name: - title = item[2] + node_title = item[2] break # Node items action = 'Browse' 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: action = 'Warning' - title = jid_info['condition'] - note = jid_info['text'] + node_title = jid_info['condition'] + node_note = jid_info['text'] services = services_sorted = None elif isinstance(node_items, IqTimeout): action = 'Warning' - title = 'Timeout' - note = 'Timeout error' + node_title = 'Timeout' + node_note = 'Timeout error' services = services_sorted = None elif isinstance(node_items, IqError): action = 'Warning' breakpoint() - title = node_items['condition'] - note = node_items['text'] + node_title = node_items['condition'] + node_note = node_items['text'] services = services_sorted = None else: #title = title or node_name - if not title: title = node_name - note = jid_bare + if not node_title: node_title = node_name + node_note = jid_bare for item in node_items['pubsub']['items']: item_payload = item['payload'] entry = Syndication.extract_items(item_payload) + entry['id'] = item['id'] entries.append(entry) + #if len(entries) > 10: break if entries: entries.reverse() 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 = 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': # raise HTTPException(status_code=408, detail='remote-server-timeout') @@ -159,16 +528,21 @@ class HttpInstance: template_file = 'node.xhtml' template_dict = { 'action' : action, - 'exception' : exception, - 'filename' : 'default.svg', - 'jid_bare' : jid, - 'note' : note, - 'request' : request, '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, - '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' @@ -260,7 +634,7 @@ class HttpInstance: action = 'Error' title = 'Slixmpp error' 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': # raise HTTPException(status_code=408, detail='remote-server-timeout') @@ -294,7 +668,7 @@ class HttpInstance: return response @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', '') if node_name: response = RedirectResponse(url='/{}/{}'.format(jid, node_name)) @@ -436,7 +810,8 @@ class HttpInstance: title = jid_detail['name'] else: title = jid_bare.split('@')[0] - + + xmpp_instance.disconnect() # Notes jid_detail_note = jid_detail['note'] @@ -475,8 +850,6 @@ class HttpInstance: else: selection = Graphics.extract_colours_from_raster(filepath) - xmpp_instance.disconnect() - # QR code Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare) @@ -622,6 +995,7 @@ class Graphics: print(selection) except Exception as e: + selection = None exception = str(e) print(exception) @@ -841,11 +1215,44 @@ class XmppXep0030: 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): 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)) + async def get_room_participants(self, jid_bare): + return await self['xep_0045'].get_roster(jid_bare) # NOTE: "Item not found", yet is a group chat @@ -938,8 +1345,9 @@ def main(): account = data['account'] jabber_id = account['xmpp'] password = account['pass'] + alias = account['alias'] - http_instance = HttpInstance(jabber_id, password) + http_instance = HttpInstance(jabber_id, password, alias) return http_instance.app app = main() diff --git a/messages/README b/messages/README new file mode 100644 index 0000000..6cb316f --- /dev/null +++ b/messages/README @@ -0,0 +1 @@ +This directory caches conference messages. diff --git a/photo/README b/photo/README deleted file mode 100644 index 9ff95e8..0000000 --- a/photo/README +++ /dev/null @@ -1 +0,0 @@ -This directory caches photo files. diff --git a/vcard/README b/vcard/README new file mode 100644 index 0000000..918d546 --- /dev/null +++ b/vcard/README @@ -0,0 +1 @@ +This directory caches vcard files. diff --git a/xhtml/conference.xhtml b/xhtml/conference.xhtml new file mode 100644 index 0000000..a22a14c --- /dev/null +++ b/xhtml/conference.xhtml @@ -0,0 +1,134 @@ + + + + + + + + + XMPP: {{action}} {{title}} + + + + + + + + + + + + + + + +
+
+ + + + + Download + +
+
+
+ {% if links %} +
+ {% for link in links %} + + {{link[0]}} + + {% endfor %} +
+ {% endif %} +
+ {% if filename %} + + {% endif %} + +

{% if jid_title %}{{jid_title}}{% else %}Group Chat{% endif %}

+ +

{% if jid_title %}{{jid_title}}{% else %}{{jid_bare}}{% endif %}

+
+
+ +
+
+ {% if subject %} +

+ {{subject}} +

+ {% endif %} +
+ {% if messages %} +
+ {% for message in messages %} +
+ {{message['mucnick']}} +
{{message['body']}}
+
{{message['delay']['stamp'].__str__()}}
+ +
+ {% endfor %} +
+ {% endif %} + {% if exception %} +
+ {{exception}} +
+ {% endif %} + {% if number_of_pages %} +
+ {% if number_of_pages > 5 %} + First + {{page_number-1}} + {% if number_of_pages > page_number %} + {{page_number}} + {% endif %} + {% if number_of_pages > page_number+1 %} + {{page_number+1}} + {% endif %} + Last + {% else %} + {% for number in range(number_of_pages) %} + {{number+1}} + {% endfor %} + {% endif %} +
+ {% endif %} + {% if previous %} +
+ Previous +
+ {% endif %} + + + +
+
+ {% if message %} +
{{message}}
+ {% endif %} +
+ + diff --git a/xhtml/jid.xhtml b/xhtml/jid.xhtml index a00663d..c6e4c7d 100644 --- a/xhtml/jid.xhtml +++ b/xhtml/jid.xhtml @@ -92,13 +92,15 @@ Preview journal OR Preview group chat - {% if count %} -
- +
+ + {% if count %} {{count}} {{instance}} - -
- {% endif %} + {% else %} + Preview + {% endif %} + +
- {% if link_href %} -
- - {{link_text}} - + {% if number_of_pages %} +
+ {% if number_of_pages > 5 %} + First + {{page_number-1}} + {% if number_of_pages > page_number %} + {{page_number}} + {% endif %} + {% if number_of_pages > page_number+1 %} + {{page_number+1}} + {% endif %} + Last + {% else %} + {% for number in range(number_of_pages) %} + {{number+1}} + {% endfor %} + {% endif %} +
+ {% endif %} + {% if previous %} +
+ Previous
{% endif %}