Change node names;

Soft code (i.e. opposite of hard code) node names;
Improve parsing of Netscape HTML boomark files;
Add IQ module;
Catch more exceptions;
Display Jabber ID on template profile.
This commit is contained in:
Schimon Jehudah, Adv. 2025-01-08 00:43:25 +02:00
parent a3d50fe8d8
commit 2103b061a2
8 changed files with 277 additions and 214 deletions

View file

@ -74,6 +74,12 @@
This page provides a general survey of your XMPP account and
stored bookmarks.
</p>
<h4 id="jid">
Jabber ID
</h4>
<p>
Your Jabber identifier is <a href="xmpp:{{jabber_id}}?message">{{jabber_id}}</a>.
</p>
<!--
<h4 id="enrollment">
Enrollment
@ -471,7 +477,7 @@ retrieve items only if on a whitelist managed by the node owner.">
to='{{jabber_id}}'
id='delete1'&gt;
&lt;pubsub xmlns='http://jabber.org/protocol/pubsub#owner'&gt;
&lt;delete node='urn:xmpp:bibliography:0'/&gt;
&lt;delete node='blasta:annotation:0'/&gt;
&lt;/pubsub&gt;
&lt;/iq&gt;
</pre>
@ -484,7 +490,7 @@ retrieve items only if on a whitelist managed by the node owner.">
to='{{jabber_id}}'
id='delete2'&gt;
&lt;pubsub xmlns='http://jabber.org/protocol/pubsub#owner'&gt;
&lt;delete node='xmpp:bibliography:private:0'/&gt;
&lt;delete node='blasta:annotation:private:0'/&gt;
&lt;/pubsub&gt;
&lt;/iq&gt;
</pre>
@ -497,7 +503,7 @@ retrieve items only if on a whitelist managed by the node owner.">
to='{{jabber_id}}'
id='delete3'&gt;
&lt;pubsub xmlns='http://jabber.org/protocol/pubsub#owner'&gt;
&lt;delete node='xmpp:bibliography:read:0'/&gt;
&lt;delete node='blasta:annotation:read:0'/&gt;
&lt;/pubsub&gt;
&lt;/iq&gt;
</pre>

View file

@ -13,22 +13,24 @@ journal = ""
pubsub = ""
# Bibliography
node_id = "blasta:annotation:0"
node_title = "Blasta"
node_subtitle = "Annotation"
node_public_id = "blasta:annotation:0"
node_public_title = "Blasta (Public)"
node_public_subtitle = "Public annotations"
# Private bibliography
node_id_private = "blasta:annotation:private:0"
node_title_private = "Blasta (Private)"
node_subtitle_private = "Private annotation"
node_private_id = "blasta:annotation:private:0"
node_private_title = "Blasta (Private)"
node_private_subtitle = "Private annotations"
# Reading list
node_id_read = "blasta:annotation:read:0"
node_title_read = "Blasta (Read)"
node_subtitle_read = "Reading list"
node_read_id = "blasta:annotation:read:0"
node_read_title = "Blasta (Read)"
node_read_subtitle = "Reading list"
# Settings node
node_settings = "blasta:settings:0"
node_settings_id = "blasta:settings:0"
node_settings_title = "Blasta (Settings)"
node_settings_subtitle = "Blasta Settings Node"
# Acceptable protocol types that would be aggregated to the Blasta database
schemes = [

View file

@ -11,6 +11,7 @@ from blasta.utilities.http import UtilitiesHttp
from blasta.utilities.syndication import UtilitiesSyndication
from blasta.xmpp.form import DataForm
from blasta.xmpp.instance import XmppInstance
from blasta.xmpp.iq import XmppIq
from blasta.xmpp.pubsub import XmppPubsub
from datetime import datetime
from fastapi import Cookie, FastAPI, File, Form, HTTPException, Request, Response, UploadFile
@ -73,33 +74,37 @@ class HttpInstance:
jabber_id_pubsub = settings['pubsub']
journal = settings['journal']
node_id_public = settings['node_id']
node_title_public = settings['node_title']
node_subtitle_public = settings['node_subtitle']
node_settings_id = settings['node_settings_id']
node_settings_title = settings['node_settings_title']
node_settings_subtitle = settings['node_settings_subtitle']
node_id_private = settings['node_id_private']
node_title_private = settings['node_title_private']
node_subtitle_private = settings['node_subtitle_private']
node_public_id = settings['node_public_id']
node_public_title = settings['node_public_title']
node_public_subtitle = settings['node_public_subtitle']
node_id_read = settings['node_id_read']
node_title_read = settings['node_title_read']
node_subtitle_read = settings['node_subtitle_read']
node_private_id = settings['node_private_id']
node_private_title = settings['node_private_title']
node_private_subtitle = settings['node_private_subtitle']
node_read_id = settings['node_read_id']
node_read_title = settings['node_read_title']
node_read_subtitle = settings['node_read_subtitle']
nodes = {
'public' : {
'name' : node_id_public,
'title' : node_title_public,
'subtitle' : node_subtitle_public,
'name' : node_public_id,
'title' : node_public_title,
'subtitle' : node_public_subtitle,
'access_model' : 'presence'},
'private' : {
'name' : node_id_private,
'title' : node_title_private,
'subtitle' : node_subtitle_private,
'name' : node_private_id,
'title' : node_private_title,
'subtitle' : node_private_subtitle,
'access_model' : 'whitelist'},
'read' : {
'name' : node_id_read,
'title' : node_title_read,
'subtitle' : node_subtitle_read,
'name' : node_read_id,
'title' : node_read_title,
'subtitle' : node_read_subtitle,
'access_model' : 'whitelist'}
}
@ -522,7 +527,7 @@ class HttpInstance:
result, reason = await UtilitiesData.update_cache_and_database(
db_file, directory_cache, xmpp_instance, jabber_id, node_type, node_id)
if result == 'error':
message = 'XMPP system message » {}.'.format(reason)
message = f'XMPP system message » {reason}.'
description = 'IQ Error'
path = 'error'
return result_post(request, jabber_id, description, message, path)
@ -545,7 +550,7 @@ class HttpInstance:
ask = invite = name = origin = start = ''
# pubsub_jid = syndicate = jid
# message = 'Find and share bookmarks with family and friends!'
# description = 'Bookmarks of {}'.format(jid)
# description = f'Bookmarks of {jid}'
max_count = 10
entries = None
related_tags = None
@ -585,43 +590,10 @@ class HttpInstance:
if param_query:
query = param_query
entries_cache = UtilitiesData.open_file_toml(filename_items)
entries_cache_node = entries_cache[node_type]
filename_cache = os.path.join(directory_cache, 'data', jid + '_query.toml')
UtilitiesData.cache_items_and_tags_search(directory_cache, entries_cache_node, jid, query)
if os.path.exists(filename_cache) and os.path.getsize(filename_cache):
data = UtilitiesData.open_file_toml(filename_cache)
item_ids_all = data['item_ids']
related_tags = data['tags']
if len(item_ids_all) <= index_last:
index_last = len(item_ids_all)
page_next = None
item_ids_selection = []
for item_id in item_ids_all[index_first:index_last]:
item_ids_selection.append(item_id)
entries = []
for entry in entries_cache_node:
for item_id in item_ids_selection:
if entry['url_hash'] == item_id:
entries.append(entry)
for entry in entries:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(entry['published'])
entry['tags'] = entry['tags'][:5]
description = 'Your {} bookmarks with "{}"'.format(node_type, query)
message = 'Listing {} bookmarks {} - {} out of {}.'.format(node_type, index_first+1, index_last, len(item_ids_all))
#item_id_next = entries[len(entries)-1]
else:
description = 'No {} bookmarks with "{}" were found for {}'.format(node_type, query, jid)
message = 'Blasta system message » No entries.'
page_next = None
page_prev = None
elif param_tags or param_tld or param_filetype or param_protocol:
tags_list = param_tags.split('+')
if len(tags_list) == 1:
tag = param_tags
entries_cache = UtilitiesData.open_file_toml(filename_items)
if node_type in entries_cache:
entries_cache_node = entries_cache[node_type]
filename_cache = os.path.join(directory_cache, 'data', jid, tag + '.toml')
UtilitiesData.cache_items_and_tags_filter(directory_cache, entries_cache_node, jid, tag)
filename_cache = os.path.join(directory_cache, 'data', jid + '_query.toml')
UtilitiesData.cache_items_and_tags_search(directory_cache, entries_cache_node, jid, query)
if os.path.exists(filename_cache) and os.path.getsize(filename_cache):
data = UtilitiesData.open_file_toml(filename_cache)
item_ids_all = data['item_ids']
@ -640,11 +612,56 @@ class HttpInstance:
for entry in entries:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(entry['published'])
entry['tags'] = entry['tags'][:5]
description = 'Your {} bookmarks tagged with "{}"'.format(node_type, tag)
message = 'Listing {} bookmarks {} - {} out of {}.'.format(node_type, index_first+1, index_last, len(item_ids_all))
description = f'Your {node_type} bookmarks with "{query}"'
message = f'Listing {node_type} bookmarks {index_first+1} - {index_last} out of {len(item_ids_all)}.'
#item_id_next = entries[len(entries)-1]
else:
description = 'No {} bookmarks tagged with "{}" were found for {}'.format(node_type, tag, jid)
description = f'No {node_type} bookmarks with "{query}" were found for {jid}'
message = 'Blasta system message » No entries.'
page_next = None
page_prev = None
else:
description = f'No {node_type} bookmarks with "{query}" were found for {jid}'
message = 'Blasta system message » No entries.'
page_next = None
page_prev = None
elif param_tags or param_tld or param_filetype or param_protocol:
tags_list = param_tags.split('+')
if len(tags_list) == 1:
tag = param_tags
entries_cache = UtilitiesData.open_file_toml(filename_items)
if node_type in entries_cache:
entries_cache_node = entries_cache[node_type]
filename_cache = os.path.join(directory_cache, 'data', jid, tag + '.toml')
UtilitiesData.cache_items_and_tags_filter(directory_cache, entries_cache_node, jid, tag)
if os.path.exists(filename_cache) and os.path.getsize(filename_cache):
data = UtilitiesData.open_file_toml(filename_cache)
item_ids_all = data['item_ids']
related_tags = data['tags']
if len(item_ids_all) <= index_last:
index_last = len(item_ids_all)
page_next = None
item_ids_selection = []
for item_id in item_ids_all[index_first:index_last]:
item_ids_selection.append(item_id)
entries = []
for entry in entries_cache_node:
for item_id in item_ids_selection:
if entry['url_hash'] == item_id:
entries.append(entry)
for entry in entries:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(entry['published'])
entry['tags'] = entry['tags'][:5]
description = f'Your {node_type} bookmarks tagged with "{tag}"'
message = f'Listing {node_type} bookmarks {index_first+1} - {index_last} out of {len(item_ids_all)}.'
#item_id_next = entries[len(entries)-1]
else:
description = 'No {node_type} bookmarks tagged with "{tag}" were found for {jid}'
message = 'Blasta system message » No entries.'
page_next = None
page_prev = None
else:
description = 'No {node_type} bookmarks tagged with "{tag}" were found for {jid}'
message = 'Blasta system message » No entries.'
page_next = None
page_prev = None
@ -654,31 +671,37 @@ class HttpInstance:
else:
name = jabber_id.split('@')[0]
entries_cache = UtilitiesData.open_file_toml(filename_items)
entries_cache_node = entries_cache[node_type]
filename_cache = os.path.join(directory_cache, 'data', jabber_id + '.toml')
#if len(entries_cache_node) and not os.path.exists(filename_cache):
UtilitiesData.cache_items_and_tags(directory_cache, entries_cache_node, jabber_id)
if os.path.exists(filename_cache) and os.path.getsize(filename_cache):
data = UtilitiesData.open_file_toml(filename_cache)
item_ids_all = data['item_ids']
related_tags = data['tags']
if len(item_ids_all) <= index_last:
index_last = len(item_ids_all)
page_next = None
item_ids_selection = []
for item_id in item_ids_all[index_first:index_last]:
item_ids_selection.append(item_id)
entries = []
for entry in entries_cache_node:
for item_id in item_ids_selection:
if entry['url_hash'] == item_id:
entries.append(entry)
for entry in entries:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(entry['published'])
entry['tags'] = entry['tags'][:5]
description = 'Your {} bookmarks'.format(node_type)
message = 'Listing {} bookmarks {} - {} out of {}.'.format(node_type, index_first+1, index_last, len(item_ids_all))
#item_id_next = entries[len(entries)-1]
if node_type in entries_cache:
entries_cache_node = entries_cache[node_type]
filename_cache = os.path.join(directory_cache, 'data', jabber_id + '.toml')
#if len(entries_cache_node) and not os.path.exists(filename_cache):
UtilitiesData.cache_items_and_tags(directory_cache, entries_cache_node, jabber_id)
if os.path.exists(filename_cache) and os.path.getsize(filename_cache):
data = UtilitiesData.open_file_toml(filename_cache)
item_ids_all = data['item_ids']
related_tags = data['tags']
if len(item_ids_all) <= index_last:
index_last = len(item_ids_all)
page_next = None
item_ids_selection = []
for item_id in item_ids_all[index_first:index_last]:
item_ids_selection.append(item_id)
entries = []
for entry in entries_cache_node:
for item_id in item_ids_selection:
if entry['url_hash'] == item_id:
entries.append(entry)
for entry in entries:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(
entry['published'])
entry['tags'] = entry['tags'][:5]
description = f'Your {node_type} bookmarks'
message = f'Listing {node_type} bookmarks {index_first+1} - {index_last} out of {len(item_ids_all)}.'
#item_id_next = entries[len(entries)-1]
else:
description = 'Your bookmarks directory appears to be empty'
message = 'Blasta system message » Zero count.'
start = True
else:
description = 'Your bookmarks directory appears to be empty'
message = 'Blasta system message » Zero count.'
@ -692,19 +715,19 @@ class HttpInstance:
xmpp_instance = accounts[jabber_id]
tags_dict = {}
if param_query:
description = 'Bookmarks from {} with "{}"'.format(jid, param_query)
description = f'Bookmarks from {jid} with "{param_query}"'
entries_database = DatabaseSQLite.get_entries_by_jid_and_query(db_file, jid, param_query, index_first)
entries_count = DatabaseSQLite.get_entries_count_by_jid_and_query(db_file, jid, param_query)
for tag, instances in DatabaseSQLite.get_30_tags_by_jid_and_query(db_file, jid, param_query, index_first):
tags_dict[tag] = instances
elif param_tags:
description = 'Bookmarks from {} tagged with "{}"'.format(jid, param_tags)
description = f'Bookmarks from {jid} tagged with "{param_tags}"'
entries_database = DatabaseSQLite.get_entries_by_jid_and_tag(db_file, jid, param_tags, index_first)
entries_count = DatabaseSQLite.get_entries_count_by_jid_and_tag(db_file, jid, param_tags)
for tag, instances in DatabaseSQLite.get_30_tags_by_jid_and_tag(db_file, jid, param_tags, index_first):
tags_dict[tag] = instances
else:
description = 'Bookmarks from {}'.format(jid)
description = f'Bookmarks from {jid}'
entries_database = DatabaseSQLite.get_entries_by_jid(db_file, jid, index_first)
entries_count = DatabaseSQLite.get_entries_count_by_jid(db_file, jid)
for tag, instances in DatabaseSQLite.get_30_tags_by_jid(db_file, jid, index_first):
@ -749,10 +772,10 @@ class HttpInstance:
if entries_count <= index_last:
index_last = entries_count
page_next = None
message = 'Listing bookmarks {} - {} out of {}.'.format(index_first+1, index_last, entries_count)
message = f'Listing bookmarks {index_first+1} - {index_last} out of {entries_count}.'
else:
# TODO Check permission, so there is no unintended continuing to cached data which is not authorized for.
iq = await XmppPubsub.get_node_item_ids(xmpp_instance, jid, node_id_public)
iq = await XmppPubsub.get_node_item_ids(xmpp_instance, jid, node_public_id)
if isinstance(iq, Iq):
iq_items_remote = iq['disco_items']
@ -776,19 +799,19 @@ class HttpInstance:
for item_id in item_ids_all[index_first:index_last]:
item_ids_selection.append(item_id)
iq = await XmppPubsub.get_node_items(xmpp_instance, jid, node_id_public, item_ids_selection)
iq = await XmppPubsub.get_node_items(xmpp_instance, jid, node_public_id, item_ids_selection)
entries = UtilitiesData.extract_iq_items_extra(db_file, iq, jid)
if entries:
for entry in entries:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(entry['published'])
message = 'Listing bookmarks {} - {} out of {}.'.format(index_first+1, index_last, len(item_ids_all))
description = 'Bookmarks from {}'.format(jid)
message = f'Listing bookmarks {index_first+1} - {index_last} out of {len(item_ids_all)}.'
description = f'Bookmarks from {jid}'
else:
message = 'Blasta system message » Zero count.'
description = 'Bookmarks directory appears to be empty'
invite = True
else:
message = 'XMPP system message » {}.'.format(iq)
message = f'XMPP system message » {iq}.'
name = jid.split('@')[0]
path = 'error'
if not iq:
@ -811,7 +834,7 @@ class HttpInstance:
invite = True
elif 'DNS lookup failed' in iq:
domain = jid.split('@')[1] if '@' in jid else jid
description = 'Blasta could not connect to server {}'.format(domain)
description = f'Blasta could not connect to server {domain}'
elif iq == 'Connection failed: connection refused':
description = 'Connection with ' + name + ' has been refused'
elif 'Timeout' in iq or 'timeout' in iq:
@ -929,15 +952,15 @@ class HttpInstance:
entries_count = DatabaseSQLite.get_entries_count_by_tag(db_file, param_tags)
match page_type:
case 'new':
description = 'New bookmarks tagged with "{}"'.format(param_tags)
description = f'New bookmarks tagged with "{param_tags}"'
entries_database = DatabaseSQLite.get_entries_new_by_tag(db_file, param_tags, index_first)
tags_of_entries = DatabaseSQLite.get_30_tags_by_entries_new_by_tag(db_file, param_tags, index_first)
case 'popular':
description = 'Popular bookmarks tagged with "{}"'.format(param_tags) # 'Most popular'
description = f'Popular bookmarks tagged with "{param_tags}"' # 'Most popular'
entries_database = DatabaseSQLite.get_entries_popular_by_tag(db_file, param_tags, index_first)
tags_of_entries = DatabaseSQLite.get_30_tags_by_entries_popular_by_tag(db_file, param_tags, index_first)
case 'recent':
description = 'Recent bookmarks tagged with "{}"'.format(param_tags)
description = f'Recent bookmarks tagged with "{param_tags}"'
entries_database = DatabaseSQLite.get_entries_recent_by_tag(db_file, param_tags, index_first)
tags_of_entries = DatabaseSQLite.get_30_tags_by_entries_recent_by_tag(db_file, param_tags, index_first)
# TODO case 'query':
@ -955,7 +978,7 @@ class HttpInstance:
entries_count = DatabaseSQLite.get_entries_count(db_file)
case 'query':
node_id = syndicate = 'new'
description = 'Posted bookmarks with "{}"'.format(param_query)
description = f'Posted bookmarks with "{param_query}"'
entries_database = DatabaseSQLite.get_entries_by_query(db_file, param_query, index_first)
tags_of_entries = DatabaseSQLite.get_30_tags_by_entries_by_query_recent(db_file, param_query, index_first)
entries_count = DatabaseSQLite.get_entries_count_by_query(db_file, param_query)
@ -1010,7 +1033,7 @@ class HttpInstance:
page_next = None
#if page_type != 'new' or page_prev or param_tags or param_tld or param_filetype or param_protocol:
if request.url.path != '/' or request.url.query:
message = 'Listing bookmarks {} - {} out of {}.'.format(index_first+1, index_last, entries_count)
message = f'Listing bookmarks {index_first+1} - {index_last} out of {entries_count}.'
message_link = None
else:
message = ('Welcome to Blasta, an XMPP PubSub oriented social '
@ -1052,8 +1075,8 @@ class HttpInstance:
@self.app.get('/tag/{tag}')
async def tag_tag_get(request: Request, tag):
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
node_id = 'tag:{}'.format(tag)
syndicate = '?tag={}'.format(tag)
node_id = f'tag:{tag}'
syndicate = f'?tag={tag}'
path = 'tag'
# NOTE Perhaps it would be beneficial to retrieve "published" and
# tags ("category") of viewer to override the tags of Blasta
@ -1123,7 +1146,7 @@ class HttpInstance:
if jabber_id in accounts:
xmpp_instance = accounts[jabber_id]
#await xmpp_instance.plugin['xep_0060'].delete_node(jabber_id, node_id_public)
#await xmpp_instance.plugin['xep_0060'].delete_node(jabber_id, node_public_id)
for node_properties in nodes:
properties = nodes[node_properties]
@ -1132,22 +1155,22 @@ class HttpInstance:
xmpp_instance, jabber_id, properties['name'],
properties['title'], properties['subtitle'],
properties['access_model'])
await iq.send(timeout=15)
await XmppIq.send(iq, 15)
#await XmppPubsub.set_node_private(xmpp_instance, node_id_private)
#await XmppPubsub.set_node_private(xmpp_instance, node_id_read)
#await XmppPubsub.set_node_private(xmpp_instance, node_private_id)
#await XmppPubsub.set_node_private(xmpp_instance, node_read_id)
#configuration_form = await xmpp_instance['xep_0060'].get_node_config(jabber_id, properties['name'])
#print(configuration_form)
node_id = nodes['public']['name']
result, reason = await UtilitiesData.update_cache_and_database(
db_file, directory_cache, xmpp_instance, jabber_id, 'public', node_id)
if result == 'error':
message = 'XMPP system message » {}.'.format(reason)
message = f'XMPP system message » {reason}.'
description = 'IQ Error'
path = 'error'
return result_post(request, jabber_id, description, message, path)
else:
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'routine')
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, node_settings_id, 'routine')
routine = None
if isinstance(iq, Iq):
payload = iq['pubsub']['items']['item']['payload']
@ -1182,7 +1205,7 @@ class HttpInstance:
xmpp_instance = accounts[jabber_id]
XmppMessage.send(xmpp_instance, jid, body)
alias = jid.split('@')[0]
message = 'Your message has been sent to {}.'.format(alias)
message = f'Your message has been sent to {alias}.'
description = 'Message has been sent'
path = 'message'
else:
@ -1215,7 +1238,7 @@ class HttpInstance:
result, reason = await UtilitiesData.update_cache_and_database(
db_file, directory_cache, xmpp_instance, jabber_id, node_type, node_id)
if result == 'error':
message = 'Blasta system message » {}.'.format(reason)
message = f'Blasta system message » {reason}.'
description = 'Directory "private" appears to be empty'
path = 'error'
return result_post(request, jabber_id, description, message, path)
@ -1233,18 +1256,19 @@ class HttpInstance:
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
if jabber_id:
xmpp_instance = accounts[jabber_id]
if not await XmppPubsub.is_node_exist(xmpp_instance, 'xmpp:blasta:configuration:0'):
iq = XmppPubsub.create_node_config(xmpp_instance, jabber_id)
await iq.send(timeout=15)
if not await XmppPubsub.is_node_exist(xmpp_instance, node_settings_id):
iq = XmppPubsub.create_node_config(xmpp_instance, jabber_id, node_settings_id)
await XmppIq.send(iq, 15)
access_models = {}
for node_type in nodes:
node_id = nodes[node_type]['name']
iq = await XmppPubsub.get_node_configuration(xmpp_instance, jabber_id, node_id)
access_model = iq['pubsub_owner']['configure']['form']['values']['pubsub#access_model']
access_models[node_type] = access_model
if isinstance(iq, Iq):
access_model = iq['pubsub_owner']['configure']['form']['values']['pubsub#access_model']
access_models[node_type] = access_model
settings = {}
for setting in ['enrollment', 'routine']:
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', setting)
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, node_settings_id, setting)
if isinstance(iq, Iq):
payload = iq['pubsub']['items']['item']['payload']
if payload:
@ -1276,16 +1300,16 @@ class HttpInstance:
if jabber_id:
xmpp_instance = accounts[jabber_id]
if routine:
message = 'The routine directory has been set to {}'.format(routine)
message = f'The routine directory has been set to {routine}'
payload = DataForm.create_setting_entry(xmpp_instance, 'routine', routine)
iq = await XmppPubsub.publish_node_item( # NOTE Consider "configurations" as item ID (see Movim)
xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'routine', payload)
xmpp_instance, jabber_id, node_settings_id, 'routine', payload)
if enroll:
if enroll == '1': message = 'Your database is shared with the Blasta system'
else: message = 'Your database is excluded from the Blasta system'
payload = DataForm.create_setting_entry(xmpp_instance, 'enroll', enroll)
iq = await XmppPubsub.publish_node_item(
xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'enrollment', payload)
xmpp_instance, jabber_id, node_settings_id, 'enrollment', payload)
description = 'Setting has been saved'
template_file = 'result.xhtml'
template_dict = {
@ -1318,9 +1342,9 @@ class HttpInstance:
if entries:
filename = os.path.join(directory_cache, 'export', jabber_id + '_' + node_type + '.' + filetype)
#filename = 'export/' + jabber_id + '_' + node_type + '.' + filetype
#filename = 'export/{}_{}.{}'.format(jabber_id, node_type, filetype)
#filename = f'export/{jabber_id}_{node_type}.{filetype}'
#filename = 'export_' + node_type + '/' + jabber_id + '_' + '.' + filetype
#filename = 'export_{}/{}.{}'.format(node_type, jabber_id, filetype)
#filename = f'export_{node_type}/{jabber_id}.{filetype}'
match filetype:
case 'json':
UtilitiesData.save_to_json(filename, entries)
@ -1360,7 +1384,7 @@ class HttpInstance:
iq = XmppPubsub.create_node_atom(
xmpp_instance, jabber_id, node_id, node_title,
node_subtitle, node_access_model)
await iq.send(timeout=15)
await XmppIq.send(iq, 15)
#return {"filename": file.filename}
content = file.file.read().decode()
@ -1408,10 +1432,10 @@ class HttpInstance:
payload = UtilitiesSyndication.create_rfc4287_entry(entry_new)
iq = await XmppPubsub.publish_node_item(
xmpp_instance, jabber_id, node_id, item_id, payload)
#await iq.send(timeout=15)
#await XmppIq.send(iq, 15)
counter += 1
message = 'Blasta system message » Imported {} items.'.format(counter)
message = f'Blasta system message » Imported {counter} items.'
description = 'Import successful'
path = 'profile'
return result_post(request, jabber_id, description, message, path)
@ -1441,7 +1465,7 @@ class HttpInstance:
if (isinstance(iq, Iq) and
url_hash == iq['pubsub']['items']['item']['id']):
return RedirectResponse(url='/url/' + url_hash + '/edit')
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'routine')
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, node_settings_id, 'routine')
if isinstance(iq, Iq):
payload = iq['pubsub']['items']['item']['payload']
if payload:
@ -1541,7 +1565,7 @@ class HttpInstance:
result, reason = await UtilitiesData.update_cache_and_database(
db_file, directory_cache, xmpp_instance, jabber_id, node_type, node_id)
if result == 'error':
message = 'Blasta system message » {}.'.format(reason)
message = f'Blasta system message » {reason}.'
description = 'Directory "read" appears to be empty'
path = 'error'
return result_post(request, jabber_id, description, message, path)
@ -1626,7 +1650,7 @@ class HttpInstance:
description = 'Search your own bookmarks'
message = 'Search for bookmarks from your own directory.'
else:
description = 'Search bookmarks of {}'.format(jid)
description = f'Search bookmarks of {jid}'
message = 'Search for bookmarks of a given Jabber ID.'
form_action = '/jid/' + jid
input_id = input_name = label_for = 'q'
@ -1712,7 +1736,7 @@ class HttpInstance:
#if jabber_id == jid or node_type in ('private', 'read'):
tag_list = DatabaseSQLite.get_500_tags_by_jid_sorted_by_name(db_file, jid)
message = 'Common 500 tags sorted by name and sized by commonality.'
description = 'Common tags of {}'.format(jid)
description = f'Common tags of {jid}'
template_file = 'tag.xhtml'
template_dict = {
'request' : request,
@ -1734,7 +1758,7 @@ class HttpInstance:
@self.app.get('/url/{url_hash}')
async def url_hash_get(request: Request, url_hash):
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
node_id = 'hash:{}'.format(url_hash)
node_id = f'hash:{url_hash}'
param_hash = url_hash
syndicate = path = 'url'
entries = []
@ -1753,7 +1777,7 @@ class HttpInstance:
exist = True
break
else:
message = 'XMPP system message » Error: {}.'.format(iq)
message = f'XMPP system message » Error: {iq}.'
description = 'The requested bookmark could not be retrieved'
path = 'error'
return result_post(request, jabber_id, description, message, path)
@ -1810,7 +1834,7 @@ class HttpInstance:
'jid' : jid,
'name' : jid, # jid.split('@')[0] if '@' in jid else jid,
'instances' : instances})
# message = 'XMPP system message » {}.'.format(iq)
# message = f'XMPP system message » {iq}.'
# if iq == 'Node not found':
# description = 'An error has occurred'
# else:
@ -1853,7 +1877,7 @@ class HttpInstance:
description = 'The requested bookmark does not exist'
path = 'error'
return result_post(request, jabber_id, description, message, path)
message = 'Information for URI {}'.format(entries[0]['link']) # entry[2]
message = f'Information for URI {entries[0]["link"]}' # entry[2]
if not instances: instances = 0
if instances > 1:
description = 'Discover new resources and see who shares them'
@ -1904,7 +1928,7 @@ class HttpInstance:
tags_old: str = Form(''),
title: str = Form(...),
url: str = Form(...)):
node_id = 'hash:{}'.format(url_hash)
node_id = f'hash:{url_hash}'
param_hash = url_hash
syndicate = path = 'url'
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
@ -1923,7 +1947,7 @@ class HttpInstance:
'jid' : jabber_id,
'name' : name,
'instances' : instances or 1}
message = 'Information for URL {}'.format(url)
message = f'Information for URL {url}'
description = 'Bookmark properties'
xmpp_instance = accounts[jabber_id]
payload = UtilitiesSyndication.create_rfc4287_entry(entry)
@ -1938,29 +1962,29 @@ class HttpInstance:
case 'private':
print('Set item as private (XEP-0223)')
#iq = await XmppPubsub.publish_node_item_private(
# xmpp_instance, node_id_private, url_hash, iq)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_id_public, url_hash)
# xmpp_instance, node_private_id, url_hash, iq)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_public_id, url_hash)
UtilitiesData.remove_item_from_cache(directory_cache, jabber_id, 'public', url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_id_read, url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_read_id, url_hash)
UtilitiesData.remove_item_from_cache(directory_cache, jabber_id, 'read', url_hash)
case 'public':
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_id_private, url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_private_id, url_hash)
UtilitiesData.remove_item_from_cache(directory_cache, jabber_id, 'private', url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_id_read, url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_read_id, url_hash)
UtilitiesData.remove_item_from_cache(directory_cache, jabber_id, 'read', url_hash)
case 'read':
#iq = await XmppPubsub.publish_node_item_private(
# xmpp_instance, node_id_read, url_hash, iq)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_id_public, url_hash)
# xmpp_instance, node_read_id, url_hash, iq)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_public_id, url_hash)
UtilitiesData.remove_item_from_cache(directory_cache, jabber_id, 'public', url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_id_private, url_hash)
await XmppPubsub.del_node_item(xmpp_instance, jabber_id, node_private_id, url_hash)
UtilitiesData.remove_item_from_cache(directory_cache, jabber_id, 'private', url_hash)
if isinstance(iq, str):
description = 'Could not save bookmark'
message = 'XMPP system message » {}.'.format(iq)
message = f'XMPP system message » {iq}.'
path = 'error'
return result_post(request, jabber_id, description, message, path)
#await iq.send(timeout=15)
#await XmppIq.send(iq, 15)
# Save changes to cache file
entries_cache_filename = os.path.join(directory_cache, 'items', jabber_id + '.toml')
entries_cache = UtilitiesData.open_file_toml(entries_cache_filename)
@ -2055,7 +2079,7 @@ class HttpInstance:
@self.app.get('/url/{url_hash}/confirm')
async def url_hash_confirm_get(request: Request, url_hash):
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
node_id = 'hash:{}'.format(url_hash)
node_id = f'hash:{url_hash}'
param_hash = url_hash
syndicate = path = 'url'
if len(url_hash) == 32:
@ -2073,7 +2097,7 @@ class HttpInstance:
exist = True
break
else:
message = 'XMPP system message » {}.'.format(iq)
message = f'XMPP system message » {iq}.'
if iq == 'Node not found':
description = 'An error has occurred'
else:
@ -2093,7 +2117,7 @@ class HttpInstance:
entry['published_mod'] = UtilitiesDate.convert_iso8601_to_readable(entry['published'])
entries.append(entry)
description = 'Confirm deletion of a bookmark'
message = 'Details for bookmark {}'.format(entries[0]['link'])
message = f'Details for bookmark {entries[0]["link"]}'
template_file = 'browse.xhtml'
template_dict = {
'request' : request,
@ -2127,7 +2151,7 @@ class HttpInstance:
@self.app.get('/url/{url_hash}/delete')
async def url_hash_delete_get(request: Request, url_hash):
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
node_id = 'hash:{}'.format(url_hash)
node_id = f'hash:{url_hash}'
param_hash = url_hash
syndicate = path = 'url'
if len(url_hash) == 32:
@ -2145,7 +2169,7 @@ class HttpInstance:
exist = True
break
else:
message = 'XMPP system message » {}.'.format(iq)
message = f'XMPP system message » {iq}.'
if iq == 'Node not found':
description = 'An error has occurred'
else:
@ -2168,7 +2192,7 @@ class HttpInstance:
# Set a title
description = 'A bookmark has been deleted'
# Set a message
message = 'Details for bookmark {}'.format(entry['link'])
message = f'Details for bookmark {entry["link"]}'
# Create a link to restore bookmark
link_save = ('/save?url=' + urllib.parse.quote(entry['link']) +
@ -2222,7 +2246,7 @@ class HttpInstance:
@self.app.post('/url/{url_hash}/edit')
async def url_hash_edit_get(request: Request, url_hash):
jabber_id = UtilitiesHttp.is_jid_matches_to_session(accounts, sessions, request)
# node_id = 'hash:{}'.format(url_hash)
# node_id = f'hash:{url_hash}'
if len(url_hash) == 32:
if jabber_id:
xmpp_instance = accounts[jabber_id]
@ -2241,7 +2265,7 @@ class HttpInstance:
exist = True
break
else:
message = 'XMPP system message » {}.'.format(iq)
message = f'XMPP system message » {iq}.'
if iq == 'Node not found':
description = 'An error has occurred'
else:
@ -2281,7 +2305,7 @@ class HttpInstance:
entry['name'] = name
entry['url_hash'] = url_hash
else:
message = 'XMPP system message » {}.'.format(iq)
message = f'XMPP system message » {iq}.'
if iq == 'Node not found':
description = 'An error has occurred'
else:

View file

@ -6,8 +6,8 @@ from blasta.utilities.cryptography import UtilitiesCryptography
from blasta.utilities.syndication import UtilitiesSyndication
from blasta.xmpp.pubsub import XmppPubsub
from datetime import datetime
from lxml import etree
import os
import re
from slixmpp.stanza.iq import Iq
import time
import tomli_w
@ -116,8 +116,8 @@ class UtilitiesData:
url_hash = UtilitiesCryptography.hash_url_to_md5(entry['link'])
iq_item_id = iq_item['id']
if iq_item_id != url_hash:
logging.error('Item ID does not match MD5. id: {} hash: {}'.format(iq_item_id, url_hash))
logging.warn('Item ID does not match MD5. id: {} hash: {}'.format(iq_item_id, url_hash))
logging.error(f'Item ID does not match MD5. id: {iq_item_id} hash: {url_hash}')
logging.warn(f'Item ID does not match MD5. id: {iq_item_id} hash: {url_hash}')
instances = DatabaseSQLite.get_entry_instances_by_url_hash(db_file, url_hash)
if entry:
entry['instances'] = instances or 0
@ -136,44 +136,53 @@ class UtilitiesData:
def load_data_netscape(html: str) -> dict:
bookmarks = []
current_summary = ""
parser = etree.XMLParser(recover=True)
lines = html.splitlines()
for line in lines:
line = line.strip()
if line:
# Parse given line
root = etree.fromstring(line, parser)
# Check for <DT> tag
if line.startswith("<DT>"):
# Look for <A> tag within <DT>
a_match = re.search(r'<A HREF="(.*?)" ADD_DATE="(.*?)" LAST_MODIFIED="(.*?)" PRIVATE="(.*?)" TAGS="(.*?)">(.*?)</A>', line)
if a_match:
link, published, updated, private, tags, title = a_match.groups()
# Check for <DT> tag
if line.startswith("<DT>"):
# Look for <A> tag within <DT>
a_element = root.find('.//A')
if a_element is not None:
link = a_element.get('HREF')
add_date = a_element.get('ADD_DATE') or time.time()
last_modified = a_element.get('LAST_MODIFIED') or time.time()
tags = a_element.get('TAGS')
title = a_element.text or link
# Convert timestamps from seconds since epoch to ISO format
published_date = datetime.fromtimestamp(int(published)).isoformat()
updated_date = datetime.fromtimestamp(int(updated)).isoformat()
# Convert timestamps from seconds since epoch to ISO format
added_date = datetime.fromtimestamp(float(add_date)).isoformat()
modified_date = datetime.fromtimestamp(float(last_modified)).isoformat()
# Create bookmark dictionary
bookmark = {
'title': title,
'link': link,
'summary': current_summary,
'published': published_date,
'updated': updated_date,
'tags': [tag.strip() for tag in tags.split(',')] if tags else []
}
# Create bookmark dictionary
bookmark = {
'title': title,
'link': link,
'summary': current_summary,
'published': added_date,
'updated': modified_date,
'tags': [tag.strip() for tag in tags.split(',')] if tags else ['unclassified']
}
# Append bookmark to the list
bookmarks.append(bookmark)
# Append bookmark to the list
bookmarks.append(bookmark)
# Reset summary for the next bookmark
current_summary = ""
# Reset summary for the next bookmark
current_summary = ""
# Check for <DD> tag
elif line.startswith("<DD>"):
# Extract summary from <DD>
summary_match = re.search(r'<DD>(.*?)</DD>|<DD>(.*?)(?=s*<DT>|$)', line)
if summary_match:
bookmarks[len(bookmarks)-1]['summary'] = summary_match.group(2).strip()
# Check for <DD> tag
elif line.startswith("<DD>"):
# Extract summary from <DD>
bookmarks[len(bookmarks)-1]['summary'] = line[4:].strip()
#dd_element = root.find('.//DD')
#if dd_element:
# bookmarks[len(bookmarks)-1]['summary'] = dd_element.text.strip()
return {'entries': bookmarks}
@ -195,16 +204,18 @@ class UtilitiesData:
def remove_item_from_cache(directory_cache, jabber_id, node, url_hash):
filename_items = os.path.join(directory_cache, 'items', jabber_id + '.toml')
entries_cache = UtilitiesData.open_file_toml(filename_items)
if node in entries_cache:
entries_cache_node = entries_cache[node]
for entry_cache in entries_cache_node:
if entry_cache['url_hash'] == url_hash:
entry_cache_index = entries_cache_node.index(entry_cache)
del entries_cache_node[entry_cache_index]
break
data_items = entries_cache
UtilitiesData.save_to_toml(filename_items, data_items)
if os.path.exists(filename_items):
#if os.path.exists(filename_items) and os.path.getsize(filename_items):
entries_cache = UtilitiesData.open_file_toml(filename_items)
if node in entries_cache:
entries_cache_node = entries_cache[node]
for entry_cache in entries_cache_node:
if entry_cache['url_hash'] == url_hash:
entry_cache_index = entries_cache_node.index(entry_cache)
del entries_cache_node[entry_cache_index]
break
data_items = entries_cache
UtilitiesData.save_to_toml(filename_items, data_items)
def save_to_json(filename: str, data) -> None:
with open(filename, 'w') as f:
@ -244,7 +255,8 @@ class UtilitiesData:
return ['error', iq]
else:
entries_cache = UtilitiesData.open_file_toml(filename_items)
if not node_type in entries_cache: return ['error', 'Directory "{}" is empty'. format(node_type)]
if not node_type in entries_cache:
return ['error', f'Directory "{node_type}" is empty']
entries_cache_node = entries_cache[node_type]
# Check whether items still exist on node

View file

@ -1,2 +1,2 @@
__version__ = '0.2'
__version_info__ = (0, 2)
__version__ = '0.3'
__version_info__ = (0, 3)

19
blasta/xmpp/iq.py Normal file
View file

@ -0,0 +1,19 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from slixmpp.exceptions import IqError, IqTimeout
class XmppIq:
async def send(iq, timeout):
try:
await iq.send(timeout=timeout)
except IqError as e:
raise Exception('IQ Error!')
print(str(e))
except IqTimeout as e:
raise Exception('IQ Timeout!')
print(str(e))
except Exception as e:
raise Exception('Error!')
print(str(e))

View file

@ -29,7 +29,7 @@ class XmppPubsub:
value=subtitle)
form.addField('pubsub#max_items',
ftype='text-single',
value='255')
value=255)
form.addField('pubsub#notify_retract',
ftype='boolean',
value=1)
@ -47,12 +47,12 @@ class XmppPubsub:
value='http://www.w3.org/2005/Atom')
return iq
def create_node_config(xmpp_instance, jid):
def create_node_config(xmpp_instance, jid, node_settings_id):
jid_from = str(xmpp_instance.boundjid) if xmpp_instance.is_component else None
iq = xmpp_instance.Iq(stype='set',
sto=jid,
sfrom=jid_from)
iq['pubsub']['create']['node'] = 'xmpp:blasta:configuration:0'
iq['pubsub']['create']['node'] = node_settings_id
form = iq['pubsub']['configure']['form']
form['type'] = 'submit'
form.addField('pubsub#access_model',
@ -63,7 +63,7 @@ class XmppPubsub:
value=0)
form.addField('pubsub#description',
ftype='text-single',
value='Settings of the Blasta PubSub bookmarks system')
value='Settings of the Blasta PubSub annotation system')
form.addField('pubsub#max_items',
ftype='text-single',
value='30')

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "Blasta"
version = "0.2"
version = "0.3"
description = "A collaborative annotation management system for XMPP"
authors = [{name = "Schimon Zachary", email = "sch@fedora.email"}]
license = {text = "AGPL-3.0"}