From 92b4f5d6d2b910e2d04d156c85da963ecc7eaaba Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Tue, 15 Oct 2024 15:38:27 +0300 Subject: [PATCH] [WIP] Add caching functionality. --- css/stylesheet.css | 12 +- details/README | 1 + fasi.py | 1137 +++++++++++++++++++--------------------- vcard/README | 1 - xhtml/conference.xhtml | 4 +- xhtml/jid.xhtml | 6 +- xhtml/node.xhtml | 4 +- 7 files changed, 562 insertions(+), 603 deletions(-) create mode 100644 details/README delete mode 100644 vcard/README diff --git a/css/stylesheet.css b/css/stylesheet.css index 9b99627..dbc74f1 100644 --- a/css/stylesheet.css +++ b/css/stylesheet.css @@ -409,7 +409,7 @@ h3, h4, h5 { text-shadow: 1px 1px #000; } -#message { +#xmpp-message { background: #000; font-weight: bold; opacity: 10%; @@ -421,11 +421,11 @@ h3, h4, h5 { right: 0; } -#message { +#xmpp-message { color: #fff; } -#message:hover { +#xmpp-message:hover { opacity: unset; } @@ -538,7 +538,7 @@ h3, h4, h5 { width: unset; } - #message { + #xmpp-message { display: none; } @@ -551,6 +551,10 @@ h3, h4, h5 { margin-bottom: 2.5em; } + #notice { + padding-bottom: 1.5em; + } + #profile #qrcode { margin-bottom: 1em; outline: solid; diff --git a/details/README b/details/README new file mode 100644 index 0000000..8d3aed9 --- /dev/null +++ b/details/README @@ -0,0 +1 @@ +This directory caches textual data of Jabber IDs. diff --git a/fasi.py b/fasi.py index 1bb6c71..ac670ea 100644 --- a/fasi.py +++ b/fasi.py @@ -8,6 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +import glob #import logging #from os import mkdir #from os.path import getsize, exists @@ -80,7 +81,7 @@ class HttpInstance: @self.app.get('/v/{jid}') async def view_jid(request: Request, jid): - """View messages of jabber id""" + """View recent messages of a conference""" jid_path = urlsplit(jid).path if parseaddr(jid_path)[1] == jid_path: jid_bare = jid_path.lower() @@ -88,205 +89,70 @@ class HttpInstance: 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 + #try: + if True: + exception = jid_detail = messages_10 = note = node_title = \ + node_note = number_of_pages = page_number = previous = \ + selection = services_sorted = subject = 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({'name' : 'Connect', - 'href' : link_href, - 'iden' : 'connect'}) - 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({'name' : 'Discover', - 'href' : link_href, - 'iden' : 'discover'}) - 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' - instance = 'participants' - link_href = 'xmpp:{}?join'.format(jid_bare) - links.append({'name' : 'Join', - 'href' : link_href, - 'iden' : 'join'}) - 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' - 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) + filename = 'details/{}.toml'.format(jid_bare) + if os.path.exists(filename) and os.path.getsize(filename) > 0: + jid_details = Data.open_file_toml(filename) + action = jid_details['action'] + count = jid_details['count'] + instance = jid_details['instance'] + jid_info = { + 'error' : jid_details['error'], + 'text' : jid_details['error_text'], + 'condition' : jid_details['error_condition']} + jid_kind = jid_details['kind'] + link_href = jid_details['link_href'] + messages = jid_details['messages'] + nodes = jid_details['nodes'] + note = jid_details['note'] + title = jid_details['name'] + xmpp_uri = jid_details['uri'] + view_href = jid_details['view_href'] + else: + jid_data = await FileUtilities.cache_jid_data(jabber_id, password, jid_bare, node_name, alias=alias) + count = jid_data['count'] + jid_detail = jid_data['jid_detail'] + jid_info = jid_data['jid_info'] + jid_kind = jid_data['jid_kind'] + jid_details = jid_data['jid_details'] + note = jid_data['note'] + title = jid_data['title'] + + # Group chat messages + # NOTE TODO + page_number = request.query_params.get('page', '') + if page_number: + try: + page_number = int(page_number) + ix = (page_number -1) * 10 + except: + ix = 0 + page_number = 1 else: - action = 'Browse' - instance = 'nodes' - link_href = 'xmpp:{}?disco;type=get;request=items'.format(jid_bare) - links.append({'name' : 'Browse', - 'href' : link_href, - 'iden' : 'browse'}) - view_href = '/d/' + jid_bare - xmpp_uri = jid_bare - else: - action = 'Message' - instance = 'articles' - link_href = 'xmpp:{}?message'.format(jid_bare) - links.append({'name' : 'Add', - 'href' : 'xmpp:{}?roster'.format(jid_bare), - 'id' : 'add'}) - links.append({'name' : 'Message', - 'href' : link_href, - 'iden' : 'message'}) - node_name = 'urn:xmpp:microblog:0' - view_href = '/d/{}/{}'.format(jid_bare, node_name) - xmpp_uri = jid_bare - links.append({'name' : 'Subscribe', - 'href' : 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name), - 'iden' : 'subscribe'}) - links.append({'name' : 'View', - 'href' : 'xmpp:{}?pubsub;node={}'.format(jid_bare, node_name), - 'iden' : 'view'}) - links.append({'name' : 'vCard', - 'href' : 'xmpp:{}?vcard'.format(jid_bare), - 'iden' : 'vcard'}) - - # 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 = [] - # TODO Create configurations for group chat preview - 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 = 0 page_number = 1 - else: - ix = 0 - page_number = 1 - 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 + messages_10 = messages[ix:][:10] + number_of_pages = int(len(messages) / 10) + if number_of_pages < len(messages) / 10: number_of_pages += 1 - xmpp_instance.disconnect() + # Query URI links + action, instance, link_href, links, node_name, view_href, xmpp_uri = XmppUtilities.set_query_uri_link( + jid_bare, jid_info, jid_kind, node_name) - # 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'] + # Graphic files + filename, filepath, filetype, selection = FileUtilities.handle_photo(jid_bare, jid_detail) - # 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: + else: exception = str(e) action = 'Error' title = 'Slixmpp error' @@ -302,8 +168,8 @@ class HttpInstance: 'exception' : exception, 'filename' : filename, 'jid_bare' : jid, - 'jid_note' : jid_detail['note'], - 'jid_title' : jid_detail['name'], + 'jid_note' : note, + 'jid_title' : title, 'links' : links, 'messages' : messages_10, 'node_title' : node_title, @@ -314,7 +180,7 @@ class HttpInstance: 'previous' : previous, 'request' : request, 'subject' : subject, - 'title' : jid_detail['name'], + 'title' : title, 'url' : request.url._url, 'xmpp_uri' : xmpp_uri} response = templates.TemplateResponse(template_file, template_dict) @@ -333,245 +199,121 @@ class HttpInstance: jid_bare = jid note = 'Jabber ID appears to be malformed' - try: - exception = note = number_of_pages = page_number = previous = selection = services_sorted = None + #try: + if True: + entries = exception = jid_detail = note = node_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) - # 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({'name' : 'Connect', - 'href' : link_href, - 'iden' : 'connect'}) - 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({'name' : 'Discover', - 'href' : link_href, - 'iden' : 'discover'}) - 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' - instance = 'participants' - link_href = 'xmpp:{}?join'.format(jid_bare) - links.append({'name' : 'Join', - 'href' : link_href, - 'iden' : 'join'}) - 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' - 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({'name' : 'Browse', - 'href' : link_href, - 'iden' : 'browse'}) - view_href = '/d/' + jid_bare - xmpp_uri = jid_bare + filename = 'details/{}.toml'.format(jid_bare) + if os.path.exists(filename) and os.path.getsize(filename) > 0: + jid_details = Data.open_file_toml(filename) + action = jid_details['action'] + count = jid_details['count'] + instance = jid_details['instance'] + jid_info = { + 'error' : jid_details['error'], + 'text' : jid_details['error_text'], + 'condition' : jid_details['error_condition']} + jid_kind = jid_details['kind'] + link_href = jid_details['link_href'] + messages = jid_details['messages'] + nodes = jid_details['nodes'] + note = jid_details['note'] + title = jid_details['name'] + xmpp_uri = jid_details['uri'] + view_href = jid_details['view_href'] + if node_name not in nodes: + node_item_ids = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name) + if isinstance(node_item_ids, stanza.iq.Iq): + nodes[node_name] = node_item_ids['disco_items']['items'] else: - action = 'Message' - instance = 'articles' - link_href = 'xmpp:{}?message'.format(jid_bare) - links.append({'name' : 'Add', - 'href' : 'xmpp:{}?roster'.format(jid_bare), - 'iden' : 'add'}) - links.append({'name' : 'Add', - 'href' : link_href, - 'iden' : 'message'}) - node_name = 'urn:xmpp:microblog:0' - view_href = '/d/{}/{}'.format(jid_bare, node_name) - xmpp_uri = jid_bare - if item_id: - links.append({'name' : 'Subscribe', - 'href' : 'xmpp:{}?pubsub;node={};item={};action=subscribe'.format(jid_bare, node_name, item_id), - 'iden' : 'subscribe'}) - links.append({'name' : 'View', - 'href' : 'xmpp:{}?pubsub;node={};item={}'.format(jid_bare, node_name, item_id), - 'iden' : 'view'}) - else: - links.append({'name' : 'Subscribe', - 'href' : 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name), - 'iden' : 'subscribe'}) - links.append({'name' : 'View', - 'href' : 'xmpp:{}?pubsub;node={}'.format(jid_bare, node_name), - 'iden' : 'view'}) - links.append({'name' : 'vCard', - 'href' : 'xmpp:{}?vcard'.format(jid_bare), - 'iden' : 'vcard'}) + jid_data = await FileUtilities.cache_jid_data(jabber_id, password, jid_bare, node_name, item_id) + count = jid_data['count'] + jid_detail = jid_data['jid_detail'] + jid_info = jid_data['jid_info'] + jid_kind = jid_data['jid_kind'] + jid_details = jid_data['jid_details'] + note = jid_data['note'] + title = jid_data['title'] - # 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'] - } + # Node items + # NOTE TODO + # page_number = request.query_params.get('page', '') + # if page_number: + # try: + # page_number = int(page_number) + # ix = (page_number -1) * 10 + # except: + # ix = 0 + # page_number = 1 + # else: + # ix = 0 + # page_number = 1 + # item_ids_10 = item_ids[ix:][:10] - # Title - # TODO /d/pubsub.nicoco.fr/blog/urn-uuid-53e43061-1962-3112-bb8a-1473dca61719 - if '@' in jid_bare and node_name == 'urn:xmpp:microblog:0': - node_title = 'Journal' - else: - jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) - iq = jid_items['iq'] - iq_disco_items = iq['disco_items'] - iq_disco_items_items = iq_disco_items['items'] - category = 'unsorted' - for item in iq_disco_items_items: - if item[2] and item[1] == node_name: - node_title = item[2] - break + # if item_id: + # previous = True + # node_items = await XmppXep0060.get_node_items(xmpp_instance, jid_bare, node_name, item_ids=[item_id]) + # else: + # 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 - # Node items - action = 'Browse' - entries = [] - 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 = 0 - page_number = 1 - else: - ix = 0 - page_number = 1 - 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' - node_title = jid_info['condition'] - node_note = jid_info['text'] - services = services_sorted = None - elif isinstance(node_items, IqTimeout): - action = 'Warning' - node_title = 'Timeout' - node_note = 'Timeout error' - services = services_sorted = None - elif isinstance(node_items, IqError): - action = 'Warning' - breakpoint() - node_title = node_items['condition'] - node_note = node_items['text'] - services = services_sorted = None - else: - #title = title or node_name - 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) - if entry: entry['id'] = item['id'] - entries.append(entry) - #if len(entries) > 10: break - if entries: entries.reverse() + # if not node_items: + # action = 'Warning' + # node_title = jid_info['condition'] + # node_note = jid_info['text'] + # services = services_sorted = None + # elif isinstance(node_items, IqTimeout): + # action = 'Warning' + # node_title = 'Timeout' + # node_note = 'Timeout error' + # services = services_sorted = None + # elif isinstance(node_items, IqError): + # action = 'Warning' + # breakpoint() + # node_title = node_items['condition'] + # node_note = node_items['text'] + # services = services_sorted = None + # else: + # if 'items' in node_item_ids['disco_items']: + # 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() + # nodes[node_name] = item_ids + #title = title or node_name + # 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) + # if entry: entry['id'] = item['id'] + # entries.append(entry) + # #if len(entries) > 10: break + # if entries: entries.reverse() - xmpp_instance.disconnect() + # Query URI links + action, instance, link_href, links, node_name, view_href, xmpp_uri = XmppUtilities.set_query_uri_link( + jid_bare, jid_info, jid_kind, node_name, item_id) - # 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'] + # Graphic files + filename, filepath, filetype, selection = FileUtilities.handle_photo(jid_bare, jid_detail) - # 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: + else: 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 + 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') @@ -583,8 +325,8 @@ class HttpInstance: 'exception' : exception, 'filename' : filename, 'jid_bare' : jid, - 'jid_note' : jid_detail['note'], - 'jid_title' : jid_detail['name'], + 'jid_note' : note, + 'jid_title' : title, 'links' : links, 'node_title' : node_title, 'node_note' : node_note, @@ -611,7 +353,8 @@ class HttpInstance: jid_bare = jid note = 'Jabber ID appears to be malformed' - try: + #try: + if True: exception = note = selection = services_sorted = None title = 'Services' link_href = xmpp_uri = jid_bare @@ -681,7 +424,8 @@ class HttpInstance: xmpp_instance.disconnect() - except Exception as e: + #except Exception as e: + else: exception = str(e) action = 'Error' title = 'Slixmpp error' @@ -709,21 +453,16 @@ class HttpInstance: response.headers['Content-Type'] = 'application/xhtml+xml' return response - @self.app.get('/v/{jid}') - async def view_jid_get(request: Request, jid): - """View recent messages of a conference""" - pass #TODO - - @self.app.get('/{jid}/{node_name}') + @self.app.get('/j/{jid}/{node_name}') async def jid_node_get(request: Request, jid, node_name): response = await main_jid_node_get(request, jid, node_name) return response - @self.app.get('/{jid}') + @self.app.get('/j/{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)) + response = RedirectResponse(url='/j/{}/{}'.format(jid, node_name)) else: response = await main_jid_node_get(request, jid) return response @@ -738,180 +477,57 @@ class HttpInstance: jid_bare = jid note = 'Jabber ID appears to be malformed' - try: - exception = note = selection = title = view_href = None + #try: + if True: + action = count = exception = instance = jid_detail = \ + jid_info = link_href = message = note = selection = title = \ + view_href = xmpp_uri = None + #node_name = 'urn:xmpp:microblog:0' - # 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' - 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' - instance = 'articles' - link_href = 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name) - links.append(['Subscribe', link_href]) - links.append(['View', 'xmpp:{}?pubsub;node={}'.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 + filename = 'details/{}.toml'.format(jid_bare) + if os.path.exists(filename) and os.path.getsize(filename) > 0: + jid_details = Data.open_file_toml(filename) + action = jid_details['action'] + count = jid_details['count'] + instance = jid_details['instance'] + jid_info = { + 'error' : jid_details['error'], + 'text' : jid_details['error_text'], + 'condition' : jid_details['error_condition']} + jid_kind = jid_details['kind'] + link_href = jid_details['link_href'] + messages = jid_details['messages'] + nodes = jid_details['nodes'] + note = jid_details['note'] + title = jid_details['name'] + xmpp_uri = jid_details['uri'] + view_href = jid_details['view_href'] 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(['vCard', 'xmpp:{}?vcard'.format(jid_bare)]) + jid_data = await FileUtilities.cache_jid_data(jabber_id, password, jid_bare, node_name, alias=alias) + count = jid_data['count'] + jid_detail = jid_data['jid_detail'] + jid_info = jid_data['jid_info'] + jid_kind = jid_data['jid_kind'] + jid_details = jid_data['jid_details'] + note = jid_data['note'] + title = jid_data['title'] - # JID item count - count = None - if jid_kind in ('mix', 'muc', 'conference', 'server'): - jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) - count = len(jid_items['iq']['disco_items']['items']) - elif jid_kind in ('account', 'pubsub'): - node_items = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name) - if isinstance(node_items, stanza.iq.Iq): - count = len(node_items['disco_items']['items']) + # Query URI links + action, instance, link_href, links, node_name, view_href, xmpp_uri = XmppUtilities.set_query_uri_link( + jid_bare, jid_info, jid_kind, node_name) - # 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'] - } + # Graphic files + filename, filepath, filetype, selection = FileUtilities.handle_photo(jid_bare, jid_detail) - # Title - if jid_kind == 'pubsub': - jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) - iq = jid_items['iq'] - iq_disco_items = iq['disco_items'] - iq_disco_items_items = iq_disco_items['items'] - for item in iq_disco_items_items: - if item[2] and item[1] == node_name: - title = item[2] - break - if jid_kind == 'server': - if jid_info_iq: - for identity in jid_info_iq['disco_info']['identities']: - if jid_kind == identity[0] and identity[1] == 'im' and identity[3]: - title = identity[3] - print(jid_bare) - print(identity) - print(jid_info) - # String 'undefined' is sourced from JID discuss@conference.conversejs.org - if not title: - if jid_detail['name'] and not 'undefined' in jid_detail['name']: - title = jid_detail['name'] - else: - title = jid_bare.split('@')[0] - - 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: + else: exception = str(e) print(exception) action = 'Error' title = 'Slixmpp error' xmpp_uri = jid - count = filename = jid_bare = jid_kind = links = message = selection = url = None + count = filename = jid_bare = jid_detail = jid_kind = links = \ + message = selection = url = None template_file = 'jid.xhtml' template_dict = { @@ -984,6 +600,247 @@ class Data: data_as_string = tomli_w.dumps(data) fn.write(data_as_string) +class FileUtilities: + + async def cache_jid_data(jabber_id, password, jid_bare, node_name=None, item_id=None, alias=None): + + count = title = '' + jid_detail = { + 'name' : '', + 'note' : '', + 'type' : '', + 'bin' : ''} + + #filename = 'details/{}.toml'.format(jid_bare) + #if os.path.exists(filename): jid_details = Data.open_file_toml(filename) + + # Start an XMPP instance and retrieve information + xmpp_instance = XmppInstance(jabber_id, password, jid_bare) + xmpp_instance.connect() + + # JID kind + print('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'] + + # Query URI links + print('Query URI links') + action, instance, link_href, links, node_name, view_href, xmpp_uri = XmppUtilities.set_query_uri_link( + jid_bare, jid_info, jid_kind, node_name) + + # JID info + print('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 not vcard_data['error']: + 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 or '', + 'note' : vcard_temp['notes'] or node_id or '', + 'type' : vcard_temp['PHOTO']['TYPE'] or '', + 'bin' : vcard_temp['PHOTO']['BINVAL'] or '' + } + + # TODO /d/pubsub.nicoco.fr/blog/urn-uuid-53e43061-1962-3112-bb8a-1473dca61719 + jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) + iq = jid_items['iq'] + iq_disco_items = iq['disco_items'] + iq_disco_items_items = iq_disco_items['items'] + iq_disco_items_set = {''} + #iq_disco_items_list = [] + iq_disco_items_items_list = [] + for item in iq_disco_items_items: + if jid_kind == 'muc': + iq_disco_items_set.update([item[2]]) + # iq_disco_items_list.append(item[1]) + else: + iq_disco_items_set.update([item[1]]) + iq_disco_items_items_list.append( + [item[0] or '', item[1] or '', item[2] or '']) + count = len(iq_disco_items_set) + + # Title + print('Title') + if jid_kind not in ('conference', 'mix', 'muc') and '@' in jid_bare: + node_name = 'urn:xmpp:microblog:0' + title = node_title = 'Journal' + elif jid_kind == 'pubsub': + category = 'unsorted' + for item in iq_disco_items_items: + if item[2] and item[1] == node_name: + #title = item[2] + title = node_title = item[2] + break + else: + jid_items = None + + if jid_kind == 'server': + if jid_info_iq: + for identity in jid_info_iq['disco_info']['identities']: + if jid_kind == identity[0] and identity[1] == 'im' and identity[3]: + title = identity[3] + print(jid_bare) + print(identity) + print(jid_info) + # String 'undefined' is sourced from JID discuss@conference.conversejs.org + if not title: + if jid_detail['name'] and not 'undefined' in jid_detail['name']: + title = jid_detail['name'] + else: + title = jid_bare.split('@')[0] + + # JID item count + #count = await XmppUtilities.count_jid_items(xmpp_instance, jid_bare, node_name, jid_kind) + #if jid_kind in ('mix', 'muc', 'conference', 'server'): + # jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) + # if isinstance(jid_items['iq'], stanza.iq.Iq): + # count = len(jid_items['iq']['disco_items']['items']) + #elif jid_kind in ('account', 'pubsub'): + # node_item_ids = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name) + # if isinstance(node_item_ids, stanza.iq.Iq): + # count = len(node_item_ids['disco_items']['items']) + + # Group chat messages + print('Group chat messages') + messages = [] + if jid_kind == 'muc muc': #FIXME + print(jid_bare) + print(jid_kind) + print(alias) + action = 'Join' + # TODO Create configurations for group chat preview + room_info_muc = await XmppXep0045.get_room_information(xmpp_instance, jid_bare, alias) # maxstanzas=50 + breakpoint() + 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: + messages = room_info_muc['iq'][3] + messages.reverse() + subject = room_info_muc['iq'][1]['subject'] + #title = title or node_name + if not node_title: node_title = node_name + node_note = jid_bare + + # Node items + print('Node items') + nodes = {} + nodes[node_name] = {} + if node_name and node_name in iq_disco_items_set: + #if node_name and node_name in iq_disco_items_list: + action = 'Browse' + node_item_ids = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name) + if isinstance(node_item_ids, stanza.iq.Iq): + nodes[node_name]['count'] = len(node_item_ids['disco_items']['items']) + nodes[node_name]['item_ids'] = [] + for item_id in node_item_ids['disco_items']['items']: + nodes[node_name]['item_ids'].append( + [item_id[0] or '', item_id[1] or '', item_id[2] or '']) + + xmpp_instance.disconnect() + + # Notes + print('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'] + + jid_details = { + 'action' : action or '', + 'count' : count or '', + 'error' : jid_info['error'], + 'error_text' : jid_info['text'] or '', + 'error_condition' : jid_info['condition'] or '', + 'items' : iq_disco_items_items_list, + 'link_href' : link_href, + 'instance' : instance or '', + 'kind' : jid_kind or '', + 'messages' : messages or '', + 'name' : title, + 'nodes' : nodes, + 'note' : note or '', + 'uri' : xmpp_uri or '', + 'view_href' : view_href} + + print(jid_details) + + filename = 'details/{}.toml'.format(jid_bare) + Data.save_to_toml(filename, jid_details) + + result = { + 'count' : nodes[node_name]['count'] if node_name else count or '', + 'jid_detail' : jid_detail, + 'jid_info' : jid_info, + 'jid_items' : jid_items, + 'jid_kind' : jid_kind, + 'jid_details' : jid_details, + 'note' : note, + 'title' : title} + + return result + + def handle_photo(jid_bare, jid_detail): + filetype = selection = None + filecirca = 'photo/{}.*'.format(jid_bare) + filepath = glob.glob(filecirca) + if filepath: + filepath = filepath[0] + filetype = filepath.split('.').pop() + filename = '{}.{}'.format(jid_bare, filetype) + elif jid_detail: + 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']) + + 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 + filepath_qrcode = 'qr/{}.png'.format(jid_bare) + if not os.path.exists(filepath_qrcode) or os.path.getsize(filepath_qrcode) == 0: + Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare) + + return filename, filepath, filetype, selection + class Graphics: @@ -1162,6 +1019,104 @@ class Syndication: 'updated' : published_text} # TODO "Updated" is missing return entry +class XmppUtilities: + + async def count_jid_items(xmpp_instance, jid_bare, node_name, jid_kind): + if jid_kind in ('mix', 'muc', 'conference', 'server'): + jid_items = await XmppXep0030.get_jid_items(xmpp_instance, jid_bare) + if isinstance(jid_items['iq'], stanza.iq.Iq): + count = len(jid_items['iq']['disco_items']['items']) + elif jid_kind in ('account', 'pubsub'): + node_item_ids = await XmppXep0060.get_node_item_ids(xmpp_instance, jid_bare, node_name) + if isinstance(node_item_ids, stanza.iq.Iq): + count = len(node_item_ids['disco_items']['items']) + return count + + def set_query_uri_link(jid_bare, jid_info, jid_kind, node_name=None, item_id=None): + 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({'name' : 'Connect', + 'href' : link_href, + 'iden' : 'connect'}) + xmpp_uri = jid_bare + instance = view_href = '' + 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({'name' : 'Discover', + 'href' : link_href, + 'iden' : 'discover'}) + 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' + instance = 'participants' + link_href = 'xmpp:{}?join'.format(jid_bare) + links.append({'name' : 'Join', + 'href' : link_href, + 'iden' : 'join'}) + 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' + 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({'name' : 'Browse', + 'href' : link_href, + 'iden' : 'browse'}) + view_href = '/d/' + jid_bare + xmpp_uri = jid_bare + else: + action = 'Message' + instance = 'articles' + link_href = 'xmpp:{}?message'.format(jid_bare) + links.append({'name' : 'Add', + 'href' : 'xmpp:{}?roster'.format(jid_bare), + 'iden' : 'add'}) + links.append({'name' : 'Message', + 'href' : link_href, + 'iden' : 'message'}) + #node_name = 'urn:xmpp:microblog:0' + view_href = '/d/{}/{}'.format(jid_bare, node_name) + xmpp_uri = jid_bare + if item_id: + links.append({'name' : 'Subscribe', + 'href' : 'xmpp:{}?pubsub;node={};item={};action=subscribe'.format(jid_bare, node_name, item_id), + 'iden' : 'subscribe'}) + links.append({'name' : 'View', + 'href' : 'xmpp:{}?pubsub;node={};item={}'.format(jid_bare, node_name, item_id), + 'iden' : 'view'}) + elif node_name: + links.append({'name' : 'Subscribe', + 'href' : 'xmpp:{}?pubsub;node={};action=subscribe'.format(jid_bare, node_name), + 'iden' : 'subscribe'}) + links.append({'name' : 'View', + 'href' : 'xmpp:{}?pubsub;node={}'.format(jid_bare, node_name), + 'iden' : 'view'}) + links.append({'name' : 'vCard', + 'href' : 'xmpp:{}?vcard'.format(jid_bare), + 'iden' : 'vcard'}) + return action, instance, link_href, links, node_name, view_href, xmpp_uri + class XmppXep0030: async def get_jid_items(self, jid_bare): diff --git a/vcard/README b/vcard/README deleted file mode 100644 index 918d546..0000000 --- a/vcard/README +++ /dev/null @@ -1 +0,0 @@ -This directory caches vcard files. diff --git a/xhtml/conference.xhtml b/xhtml/conference.xhtml index b87c361..acb663b 100644 --- a/xhtml/conference.xhtml +++ b/xhtml/conference.xhtml @@ -48,7 +48,7 @@ {% endif %}

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

- +

{% if jid_note %}{{jid_note}}{% else %}{{jid_bare}}{% endif %}

@@ -131,7 +131,7 @@ {% if message %} -
{{message}}
+
{{message}}
{% endif %} diff --git a/xhtml/jid.xhtml b/xhtml/jid.xhtml index 7496a7f..cd18d01 100644 --- a/xhtml/jid.xhtml +++ b/xhtml/jid.xhtml @@ -91,8 +91,8 @@ {% if links %}
{% for link in links %} - - {{link[0]}} + + {{link['name']}} {% endfor %}
@@ -144,7 +144,7 @@ {% if message %} -
{{message}}
+
{{message}}
{% endif %} diff --git a/xhtml/node.xhtml b/xhtml/node.xhtml index e56349b..ffd650e 100644 --- a/xhtml/node.xhtml +++ b/xhtml/node.xhtml @@ -48,7 +48,7 @@ {% endif %}

{{node_title}}

- +

{{jid_title}}

{% if node_note %} @@ -141,7 +141,7 @@ {% if message %} -
{{message}}
+
{{message}}
{% endif %}