Compare commits

..

4 commits
main ... main

Author SHA1 Message Date
Schimon Jehudah, Adv.
0ab40eedec Python : Utilize XEP-0004: Data Forms to store configurations;
Python : Fix error in blasta_client.py (Thank you. yvo.);
XMPP   : Replace node xmpp:blasta:settings:0 by xmpp:blasta:configuration:0;
2024-09-11 07:44:18 +03:00
Schimon Jehudah, Adv.
ea255d84e0 Fix error (500) upon first connection of an account. 2024-09-05 16:27:53 +03:00
Schimon Jehudah, Adv.
c31278b576 Add functionality to search own directory;
Fix file check (Thank you roughnecks).
2024-09-05 14:58:14 +03:00
Schimon Jehudah, Adv.
21e3aa34aa Add HTTP error codes;
Correct count for filtering by tag.
2024-09-05 13:20:21 +03:00
3 changed files with 270 additions and 122 deletions

321
blasta.py
View file

@ -11,6 +11,7 @@ TODO
""" """
import argparse
import asyncio import asyncio
from asyncio import Lock from asyncio import Lock
from datetime import datetime from datetime import datetime
@ -51,7 +52,7 @@ DBLOCK = Lock()
class Data: class Data:
def cache_items_and_tags(entries, jid, tag=None): def cache_items_and_tags_search(entries, jid, query):
"""Create a cache file of node items and tags.""" """Create a cache file of node items and tags."""
item_ids = [] item_ids = []
tags = {} tags = {}
@ -59,13 +60,7 @@ class Data:
entry_tags = entry['tags'] entry_tags = entry['tags']
entry_url_hash = entry['url_hash'] entry_url_hash = entry['url_hash']
tags_to_include = [] tags_to_include = []
if tag: if query in ' '.join([entry['title'], entry['link'], entry['summary'], ' '.join(entry_tags)]):
if tag in entry_tags:
item_ids.append(entry_url_hash)
tags_to_include += entry_tags
for tag_to_include in tags_to_include:
tags[tag_to_include] = tags[tag_to_include]+1 if tag_to_include in tags else 1
else:
item_ids.append(entry_url_hash) item_ids.append(entry_url_hash)
tags_to_include += entry_tags tags_to_include += entry_tags
for tag_to_include in tags_to_include: for tag_to_include in tags_to_include:
@ -73,19 +68,59 @@ class Data:
if tags: if tags:
tags = dict(sorted(tags.items(), key=lambda item: (-item[1], item[0]))) tags = dict(sorted(tags.items(), key=lambda item: (-item[1], item[0])))
tags = dict(list(tags.items())[:30]) tags = dict(list(tags.items())[:30])
if tag: del tags[tag] if item_ids:
filename = 'data/{}_query.toml'.format(jid)
data = {
'item_ids' : item_ids,
'tags' : tags}
Data.save_to_toml(filename, data)
def cache_items_and_tags_filter(entries, jid, tag):
"""Create a cache file of node items and tags."""
item_ids = []
tags = {}
for entry in entries:
entry_tags = entry['tags']
entry_url_hash = entry['url_hash']
tags_to_include = []
if tag in entry_tags:
item_ids.append(entry_url_hash)
tags_to_include += entry_tags
for tag_to_include in tags_to_include:
tags[tag_to_include] = tags[tag_to_include]+1 if tag_to_include in tags else 1
if tags:
tags = dict(sorted(tags.items(), key=lambda item: (-item[1], item[0])))
tags = dict(list(tags.items())[:30])
del tags[tag]
if item_ids: if item_ids:
directory = 'data/{}/'.format(jid) directory = 'data/{}/'.format(jid)
if not exists(directory): if not exists(directory):
mkdir(directory) mkdir(directory)
if tag:
filename = 'data/{}/{}.toml'.format(jid, tag) filename = 'data/{}/{}.toml'.format(jid, tag)
# Add support for search query # Add support for search query
#if tag: #filename = 'data/{}/query:{}.toml'.format(jid, query)
# filename = 'data/{}/query:{}.toml'.format(jid, query) #filename = 'data/{}/tag:{}.toml'.format(jid, tag)
#if tag: data = {
# filename = 'data/{}/tag:{}.toml'.format(jid, tag) 'item_ids' : item_ids,
else: 'tags' : tags}
Data.save_to_toml(filename, data)
def cache_items_and_tags(entries, jid):
"""Create a cache file of node items and tags."""
item_ids = []
tags = {}
for entry in entries:
entry_tags = entry['tags']
entry_url_hash = entry['url_hash']
tags_to_include = []
item_ids.append(entry_url_hash)
tags_to_include += entry_tags
for tag_to_include in tags_to_include:
tags[tag_to_include] = tags[tag_to_include]+1 if tag_to_include in tags else 1
if tags:
tags = dict(sorted(tags.items(), key=lambda item: (-item[1], item[0])))
tags = dict(list(tags.items())[:30])
if item_ids:
filename = 'data/{}.toml'.format(jid) filename = 'data/{}.toml'.format(jid)
data = { data = {
'item_ids' : item_ids, 'item_ids' : item_ids,
@ -190,10 +225,9 @@ class Data:
entries_cache_node = Data.extract_iq_items_extra(iq, jabber_id) entries_cache_node = Data.extract_iq_items_extra(iq, jabber_id)
data_items = {node_type : entries_cache_node} data_items = {node_type : entries_cache_node}
Data.save_to_toml(filename_items, data_items) Data.save_to_toml(filename_items, data_items)
return ['fine', iq] # TODO Remove this line
else: else:
print('iq problem') return ['error', iq]
breakpoint()
print('iq problem')
else: else:
entries_cache = Data.open_file_toml(filename_items) entries_cache = Data.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', 'Directory "{}" is empty'. format(node_type)]
@ -229,10 +263,10 @@ class Data:
entries_iq = Data.extract_iq_items_extra(iq, jabber_id) entries_iq = Data.extract_iq_items_extra(iq, jabber_id)
entries_cache_node_new += entries_iq entries_cache_node_new += entries_iq
else: else:
print('iq problem') # TODO
breakpoint() # Handle this concern in a different fashion,
print('iq problem') # instead of stopping the whole operation.
return ['error', iq]
entries_cache_node += entries_cache_node_new entries_cache_node += entries_cache_node_new
if node_type == 'public': if node_type == 'public':
@ -397,6 +431,14 @@ class HttpInstance:
# httponly=False, # True # httponly=False, # True
# samesite='lax') # samesite='lax')
@self.app.exception_handler(403)
def not_found_exception_handler(request: Request, exc: HTTPException):
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
message = 'Blasta system message » Access denied.'
description = 'Access denied (403)'
path = 'error'
return result_post(request, jabber_id, description, message, path)
@self.app.exception_handler(404) @self.app.exception_handler(404)
def not_found_exception_handler(request: Request, exc: HTTPException): def not_found_exception_handler(request: Request, exc: HTTPException):
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request) jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
@ -421,6 +463,22 @@ class HttpInstance:
path = 'error' path = 'error'
return result_post(request, jabber_id, description, message, path) return result_post(request, jabber_id, description, message, path)
# TODO
@self.app.get('/admin')
def admin_get(request: Request):
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
authorized = None
if authorized:
template_file = 'connect.xhtml'
template_dict = {
'request' : request,
'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict)
response.headers['Content-Type'] = 'application/xhtml+xml'
return response
else:
raise HTTPException(status_code=403, detail='Access denied')
@self.app.get('/connect') @self.app.get('/connect')
def connect_get(request: Request): def connect_get(request: Request):
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request) jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
@ -432,7 +490,7 @@ class HttpInstance:
'request' : request, 'request' : request,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/contact') @self.app.get('/contact')
@ -450,7 +508,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/disconnect') @self.app.get('/disconnect')
@ -478,7 +536,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about') @self.app.get('/help/about')
@ -490,7 +548,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/folksonomy') @self.app.get('/help/about/folksonomy')
@ -502,7 +560,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/ideas') @self.app.get('/help/about/ideas')
@ -518,7 +576,7 @@ class HttpInstance:
'journal' : journal, 'journal' : journal,
'origin' : origin} 'origin' : origin}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/philosophy') @self.app.get('/help/about/philosophy')
@ -530,7 +588,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/projects') @self.app.get('/help/about/projects')
@ -542,7 +600,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/software') @self.app.get('/help/about/software')
@ -554,7 +612,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/thanks') @self.app.get('/help/about/thanks')
@ -566,7 +624,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/xmpp') @self.app.get('/help/about/xmpp')
@ -578,7 +636,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/xmpp/atomsub') @self.app.get('/help/about/xmpp/atomsub')
@ -590,7 +648,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/xmpp/libervia') @self.app.get('/help/about/xmpp/libervia')
@ -602,7 +660,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/xmpp/movim') @self.app.get('/help/about/xmpp/movim')
@ -614,7 +672,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/about/xmpp/pubsub') @self.app.get('/help/about/xmpp/pubsub')
@ -630,7 +688,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/feeds') @self.app.get('/help/feeds')
@ -642,7 +700,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/questions') @self.app.get('/help/questions')
@ -654,7 +712,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/syndication') @self.app.get('/help/syndication')
@ -671,7 +729,7 @@ class HttpInstance:
'origin' : origin, 'origin' : origin,
'pubsub_jid' : jabber_id_pubsub} 'pubsub_jid' : jabber_id_pubsub}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/help/utilities') @self.app.get('/help/utilities')
@ -689,7 +747,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/jid', response_class=HTMLResponse) @self.app.get('/jid', response_class=HTMLResponse)
@ -762,20 +820,48 @@ class HttpInstance:
# NOTE Does it work? # NOTE Does it work?
# It does not seem to actually filter tags. # It does not seem to actually filter tags.
# NOTE Yes. It does work. # NOTE Yes. It does work.
# See function "cache_items_and_tags". # See function "cache_items_and_tags_filter".
if param_query:
# TODO Search by query query = param_query
#if param_query: entries_cache = Data.open_file_toml(filename_items)
entries_cache_node = entries_cache[node_type]
if param_tags or param_tld or param_filetype or param_protocol: filename_cache = 'data/{}_query.toml'.format(jid)
Data.cache_items_and_tags_search(entries_cache_node, jid, query)
if exists(filename_cache) and getsize(filename_cache):
data = Data.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'] = Utilities.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('+') tags_list = param_tags.split('+')
if len(tags_list) == 1: if len(tags_list) == 1:
tag = param_tags tag = param_tags
entries_cache = Data.open_file_toml(filename_items) entries_cache = Data.open_file_toml(filename_items)
entries_cache_node = entries_cache[node_type] entries_cache_node = entries_cache[node_type]
filename_cache = 'data/{}/{}.toml'.format(jid, tag) filename_cache = 'data/{}/{}.toml'.format(jid, tag)
Data.cache_items_and_tags(entries_cache_node, jid, tag) Data.cache_items_and_tags_filter(entries_cache_node, jid, tag)
if exists(filename_cache) or getsize(filename_cache): if exists(filename_cache) and getsize(filename_cache):
data = Data.open_file_toml(filename_cache) data = Data.open_file_toml(filename_cache)
item_ids_all = data['item_ids'] item_ids_all = data['item_ids']
related_tags = data['tags'] related_tags = data['tags']
@ -811,7 +897,7 @@ class HttpInstance:
filename_cache = 'data/{}.toml'.format(jid) filename_cache = 'data/{}.toml'.format(jid)
#if len(entries_cache_node) and not exists(filename_cache): #if len(entries_cache_node) and not exists(filename_cache):
Data.cache_items_and_tags(entries_cache_node, jid) Data.cache_items_and_tags(entries_cache_node, jid)
if exists(filename_cache) or getsize(filename_cache): if exists(filename_cache) and getsize(filename_cache):
data = Data.open_file_toml(filename_cache) data = Data.open_file_toml(filename_cache)
item_ids_all = data['item_ids'] item_ids_all = data['item_ids']
related_tags = data['tags'] related_tags = data['tags']
@ -864,10 +950,11 @@ class HttpInstance:
for tag, instances in SQLite.get_30_tags_by_jid(db_file, jid, index_first): for tag, instances in SQLite.get_30_tags_by_jid(db_file, jid, index_first):
tags_dict[tag] = instances tags_dict[tag] = instances
if not entries_database: if not entries_database:
message = 'Blasta system message » Error: No entries were found.' #message = 'Blasta system message » Error: No entries were found.'
description = 'No results' #description = 'No results'
path = 'error' #path = 'error'
return result_post(request, jabber_id, description, message, path) #return result_post(request, jabber_id, description, message, path)
raise HTTPException(status_code=404, detail='No entries were found')
if entries_count: if entries_count:
entries = [] entries = []
for entry in entries_database: for entry in entries_database:
@ -991,13 +1078,14 @@ class HttpInstance:
'origin': origin, 'origin': origin,
'path': path} 'path': path}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
else: else:
description = 'An XMPP account is required' description = 'An XMPP account is required'
message = 'Blasta system message » Please connect with your XMPP account to view this directory.' message = 'Blasta system message » Please connect with your XMPP account to view this directory.'
path = 'error' path = 'error'
return result_post(request, jabber_id, description, message, path) return result_post(request, jabber_id, description, message, path)
if not entries: raise HTTPException(status_code=404, detail='No entries were found')
template_dict = { template_dict = {
'request': request, 'request': request,
'description': description, 'description': description,
@ -1025,7 +1113,7 @@ class HttpInstance:
else: else:
template_file = 'browse.xhtml' template_file = 'browse.xhtml'
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/blasta.svg') @self.app.get('/blasta.svg')
@ -1118,10 +1206,11 @@ class HttpInstance:
tags_of_entries = SQLite.get_30_tags_by_entries_recent(db_file, index_first) tags_of_entries = SQLite.get_30_tags_by_entries_recent(db_file, index_first)
entries_count = SQLite.get_entries_count(db_file) entries_count = SQLite.get_entries_count(db_file)
if not entries_database: if not entries_database:
message = 'Blasta system message » Error: No entries were found.' #message = 'Blasta system message » Error: No entries were found.'
description = 'No results' #description = 'No results'
path = 'error' #path = 'error'
return result_post(request, jabber_id, description, message, path) #return result_post(request, jabber_id, description, message, path)
raise HTTPException(status_code=404, detail='No entries were found')
tags_dict = {} tags_dict = {}
#for tag, instances in SQLite.get_tags_30(db_file): #for tag, instances in SQLite.get_tags_30(db_file):
for tag, instances in tags_of_entries: for tag, instances in tags_of_entries:
@ -1196,7 +1285,7 @@ class HttpInstance:
else: else:
template_file = 'browse.xhtml' template_file = 'browse.xhtml'
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
""" """
@ -1298,19 +1387,21 @@ class HttpInstance:
path = 'error' path = 'error'
return result_post(request, jabber_id, description, message, path) return result_post(request, jabber_id, description, message, path)
else: else:
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:settings:0', 'routine') iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'routine')
routine = None
if isinstance(iq, slixmpp.stanza.iq.Iq): if isinstance(iq, slixmpp.stanza.iq.Iq):
payload = iq['pubsub']['items']['item']['payload'] payload = iq['pubsub']['items']['item']['payload']
routine = payload.text if payload else None if payload:
else: xmlns = '{jabber:x:data}'
routine = None element_value = payload.find('.//' + xmlns + 'field[@var="routine"]/' + xmlns + 'value')
if isinstance(element_value, ET.Element): routine = element_value.text
match routine: match routine:
case 'private': case 'private':
response = RedirectResponse(url='/private') response = RedirectResponse(url='/private')
case 'read': case 'read':
response = RedirectResponse(url='/read') response = RedirectResponse(url='/read')
case _: case _:
response = RedirectResponse(url='/jid/' + jabber_id) response = RedirectResponse(url='/jid/')
else: else:
#del accounts[jabber_id] #del accounts[jabber_id]
@ -1349,7 +1440,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/private', response_class=HTMLResponse) @self.app.get('/private', response_class=HTMLResponse)
@ -1381,7 +1472,7 @@ class HttpInstance:
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request) jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
if jabber_id: if jabber_id:
xmpp_instance = accounts[jabber_id] xmpp_instance = accounts[jabber_id]
if not await XmppPubsub.is_node_exist(xmpp_instance, 'xmpp:blasta:settings:0'): if not await XmppPubsub.is_node_exist(xmpp_instance, 'xmpp:blasta:configuration:0'):
iq = XmppPubsub.create_node_config(xmpp_instance, jabber_id) iq = XmppPubsub.create_node_config(xmpp_instance, jabber_id)
await iq.send(timeout=15) await iq.send(timeout=15)
access_models = {} access_models = {}
@ -1392,10 +1483,13 @@ class HttpInstance:
access_models[node_type] = access_model access_models[node_type] = access_model
settings = {} settings = {}
for setting in ['enrollment', 'routine']: for setting in ['enrollment', 'routine']:
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:settings:0', setting) iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', setting)
if isinstance(iq, slixmpp.stanza.iq.Iq): if isinstance(iq, slixmpp.stanza.iq.Iq):
payload = iq['pubsub']['items']['item']['payload'] payload = iq['pubsub']['items']['item']['payload']
if payload: settings[setting] = payload.text if payload:
xmlns = '{jabber:x:data}'
element_value = payload.find('.//' + xmlns + 'field[@var="' + setting + '"]/' + xmlns + 'value')
if isinstance(element_value, ET.Element): settings[setting] = element_value.text
template_file = 'profile.xhtml' template_file = 'profile.xhtml'
template_dict = { template_dict = {
'access_models' : access_models, 'access_models' : access_models,
@ -1405,7 +1499,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
message = 'Blasta system message » Error: No active session.' message = 'Blasta system message » Error: No active session.'
description = 'You are not connected' description = 'You are not connected'
@ -1422,15 +1516,15 @@ class HttpInstance:
xmpp_instance = accounts[jabber_id] xmpp_instance = accounts[jabber_id]
if routine: if routine:
message = 'The routine directory has been set to {}'.format(routine) message = 'The routine directory has been set to {}'.format(routine)
payload = Xml.create_setting_entry(routine) payload = Xml.create_setting_entry(xmpp_instance, 'routine', routine)
iq = await XmppPubsub.publish_node_item( iq = await XmppPubsub.publish_node_item( # NOTE Consider "configurations" as item ID (see Movim)
xmpp_instance, jabber_id, 'xmpp:blasta:settings:0', 'routine', payload) xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'routine', payload)
if enroll: if enroll:
if enroll == '1': message = 'Your database is shared with the Blasta system' if enroll == '1': message = 'Your database is shared with the Blasta system'
else: message = 'Your database is excluded from the Blasta system' else: message = 'Your database is excluded from the Blasta system'
payload = Xml.create_setting_entry(enroll) payload = Xml.create_setting_entry(xmpp_instance, 'enroll', enroll)
iq = await XmppPubsub.publish_node_item( iq = await XmppPubsub.publish_node_item(
xmpp_instance, jabber_id, 'xmpp:blasta:settings:0', 'enrollment', payload) xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'enrollment', payload)
description = 'Setting has been saved' description = 'Setting has been saved'
template_file = 'result.xhtml' template_file = 'result.xhtml'
template_dict = { template_dict = {
@ -1442,7 +1536,7 @@ class HttpInstance:
'request' : request, 'request' : request,
'routine' : routine} 'routine' : routine}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
message = 'Blasta system message » Error: No active session.' message = 'Blasta system message » Error: No active session.'
description = 'You are not connected' description = 'You are not connected'
@ -1574,10 +1668,13 @@ class HttpInstance:
if (isinstance(iq, slixmpp.stanza.iq.Iq) and if (isinstance(iq, slixmpp.stanza.iq.Iq) and
url_hash == iq['pubsub']['items']['item']['id']): url_hash == iq['pubsub']['items']['item']['id']):
return RedirectResponse(url='/url/' + url_hash + '/edit') return RedirectResponse(url='/url/' + url_hash + '/edit')
iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:settings:0', 'routine') iq = await XmppPubsub.get_node_item(xmpp_instance, jabber_id, 'xmpp:blasta:configuration:0', 'routine')
if isinstance(iq, slixmpp.stanza.iq.Iq): if isinstance(iq, slixmpp.stanza.iq.Iq):
payload = iq['pubsub']['items']['item']['payload'] payload = iq['pubsub']['items']['item']['payload']
routine = payload.text if payload else None if payload:
xmlns = '{jabber:x:data}'
element_value = payload.find('.//' + xmlns + 'field[@var="routine"]/' + xmlns + 'value')
if isinstance(element_value, ET.Element): routine = element_value.text
else: else:
routine = None routine = None
# NOTE Is "message" missing? # NOTE Is "message" missing?
@ -1606,7 +1703,7 @@ class HttpInstance:
'title' : param_title, 'title' : param_title,
'url' : param_url} 'url' : param_url}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
message = 'Blasta system message » Error: No active session.' message = 'Blasta system message » Error: No active session.'
description = 'You are not connected' description = 'You are not connected'
@ -1651,7 +1748,7 @@ class HttpInstance:
'url' : url, 'url' : url,
'url_hash' : url_hash} 'url_hash' : url_hash}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
else: else:
message = 'Blasta system message » Error: No active session.' message = 'Blasta system message » Error: No active session.'
@ -1693,7 +1790,7 @@ class HttpInstance:
'path' : path, 'path' : path,
'request' : request} 'request' : request}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/register') @self.app.get('/register')
@ -1705,7 +1802,7 @@ class HttpInstance:
'jabber_id' : jabber_id, 'jabber_id' : jabber_id,
'journal' : journal} 'journal' : journal}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/rss') @self.app.get('/rss')
@ -1744,7 +1841,7 @@ class HttpInstance:
'message' : message, 'message' : message,
'path' : path} 'path' : path}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/search/jid/{jid}') @self.app.get('/search/jid/{jid}')
@ -1780,7 +1877,7 @@ class HttpInstance:
'message' : message, 'message' : message,
'path' : path} 'path' : path}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
response = RedirectResponse(url='/search/all') response = RedirectResponse(url='/search/all')
return response return response
@ -1812,7 +1909,7 @@ class HttpInstance:
'message' : message, 'message' : message,
'path' : path} 'path' : path}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/tag') @self.app.get('/tag')
@ -1831,7 +1928,7 @@ class HttpInstance:
'message' : message, 'message' : message,
'tag_list' : tag_list} 'tag_list' : tag_list}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/tag/{jid}') @self.app.get('/tag/{jid}')
@ -1854,7 +1951,7 @@ class HttpInstance:
'message' : message, 'message' : message,
'tag_list' : tag_list} 'tag_list' : tag_list}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
@self.app.get('/url') @self.app.get('/url')
@ -2016,7 +2113,7 @@ class HttpInstance:
'syndicate' : syndicate, 'syndicate' : syndicate,
'tags' : tags_list} 'tags' : tags_list}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
message = 'Blasta system message » Error: MD5 message-digest algorithm.' message = 'Blasta system message » Error: MD5 message-digest algorithm.'
description = 'The argument for URL does not appear to be a valid MD5 Checksum' description = 'The argument for URL does not appear to be a valid MD5 Checksum'
@ -2176,7 +2273,7 @@ class HttpInstance:
'syndicate': syndicate, 'syndicate': syndicate,
'tags' : tags_new} 'tags' : tags_new}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
return response return response
else: else:
message = 'Blasta system message » Error: No active session.' message = 'Blasta system message » Error: No active session.'
@ -2242,7 +2339,7 @@ class HttpInstance:
'pubsub_jid' : jabber_id_pubsub, 'pubsub_jid' : jabber_id_pubsub,
'syndicate' : syndicate} 'syndicate' : syndicate}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
response = RedirectResponse(url='/jid/' + jabber_id) response = RedirectResponse(url='/jid/' + jabber_id)
else: else:
@ -2337,7 +2434,7 @@ class HttpInstance:
'restore' : True, 'restore' : True,
'syndicate' : syndicate} 'syndicate' : syndicate}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
response = RedirectResponse(url='/jid/' + jabber_id) response = RedirectResponse(url='/jid/' + jabber_id)
else: else:
@ -2439,7 +2536,7 @@ class HttpInstance:
'url' : entry['link'], 'url' : entry['link'],
'url_hash' : url_hash} 'url_hash' : url_hash}
response = templates.TemplateResponse(template_file, template_dict) response = templates.TemplateResponse(template_file, template_dict)
response.headers["Content-Type"] = "application/xhtml+xml" response.headers['Content-Type'] = 'application/xhtml+xml'
else: else:
message = 'Blasta system message » Error: No active session.' message = 'Blasta system message » Error: No active session.'
description = 'You are not connected' description = 'You are not connected'
@ -3985,7 +4082,7 @@ class SQLite:
# .format(function_name, db_file, tag)) # .format(function_name, db_file, tag))
sql = ( sql = (
""" """
SELECT COUNT(entries.id) SELECT COUNT(DISTINCT entries.id)
FROM main_entries AS entries FROM main_entries AS entries
INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
INNER JOIN main_tags AS tags ON tags.id = co.tag_id INNER JOIN main_tags AS tags ON tags.id = co.tag_id
@ -5022,10 +5119,17 @@ class Utilities:
class Xml: class Xml:
def create_setting_entry(value : str): def create_setting_entry(xmpp_instance, key : str, value : str):
element = ET.Element('value') form = xmpp_instance['xep_0004'].make_form('form', 'Settings')
element.text = value form['type'] = 'result'
return element form.add_field(var=key,
value=value)
return form
# def create_setting_entry(value : str):
# element = ET.Element('value')
# element.text = value
# return element
class Configuration: class Configuration:
@ -5133,7 +5237,7 @@ class XmppPubsub:
iq = xmpp_instance.Iq(stype='set', iq = xmpp_instance.Iq(stype='set',
sto=jid, sto=jid,
sfrom=jid_from) sfrom=jid_from)
iq['pubsub']['create']['node'] = 'xmpp:blasta:settings:0' iq['pubsub']['create']['node'] = 'xmpp:blasta:configuration:0'
form = iq['pubsub']['configure']['form'] form = iq['pubsub']['configure']['form']
form['type'] = 'submit' form['type'] = 'submit'
form.addField('pubsub#access_model', form.addField('pubsub#access_model',
@ -5320,11 +5424,22 @@ def main():
return http_instance.app return http_instance.app
app = main() app = main()
webbrowser.open('http://localhost:8000/help/about')
# TODO Check first time
webbrowser.open_new_tab('http://localhost:8000')
# FIXME # FIXME
if __name__ == '__main__': if __name__ == '__main__':
uvicorn.run(app, host='localhost', port=8000, reload=True) parser = argparse.ArgumentParser(
prog='blasta',
description='Blasta - A collaborative annotation system.',
usage='%(prog)s [OPTION]...')
parser.add_argument('-v', '--version', help='print version',
action='version', version='0.1')
parser.add_argument('-p', '--port', help='port number', dest='port')
parser.add_argument('-o', '--open', help='open an html browser', action='store_const', const=True, dest='open')
args = parser.parse_args()
port = args.port if args.port else 8000
uvicorn.run(app, host='localhost', port=port, reload=True)
if args.open:
# TODO Check first time
webbrowser.open('http://localhost:{}/help/about'.format(port))
webbrowser.open_new_tab('http://localhost:{}'.format(port))

View file

@ -1,3 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import webview import webview
import subprocess import subprocess
import sys import sys
@ -13,7 +16,7 @@ class HtmlView:
# Open the link using xdg-open # Open the link using xdg-open
subprocess.run(['xdg-open', uri], check=True) subprocess.run(['xdg-open', uri], check=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"Failed to open URL: {uri}. Error: {e}") print('Failed to open URL: {}. Error: {}'.format(uri, e))
else: else:
# If it is from url_instance, just load it in the webview # If it is from url_instance, just load it in the webview
#webview.load_url(uri) #webview.load_url(uri)

View file

@ -64,14 +64,20 @@
&nbsp; &nbsp;
PubSub bookmarks PubSub bookmarks
</h2> </h2>
<p>» Information of your Jabber ID.</p> <p>
<h3>Your Profile</h3> » Information of your Jabber ID.
</p>
<h3>
Your profile
</h3>
<p> <p>
This page provides a general survey of your XMPP account and This page provides a general survey of your XMPP account and
stored bookmarks. stored bookmarks.
</p> </p>
<!-- <!--
<h4 id="enrollment">Enrollment</h4> <h4 id="enrollment">
Enrollment
</h4>
<p> <p>
Blasta does not automatically include your public bookmarks Blasta does not automatically include your public bookmarks
to its database. to its database.
@ -120,7 +126,9 @@
therefore. therefore.
</p> </p>
--> -->
<h4 id="export">Export</h4> <h4 id="export">
Export
</h4>
<p> <p>
Export bookmarks to a file. Export bookmarks to a file.
</p> </p>
@ -128,7 +136,9 @@
<!-- TODO Add XBEL, XHTML and XML --> <!-- TODO Add XBEL, XHTML and XML -->
<dl> <dl>
<dt> <dt>
<strong>Private</strong> <strong>
Private
</strong>
</dt> </dt>
<dd> <dd>
<a download="{{jabber_id}}_private.json" <a download="{{jabber_id}}_private.json"
@ -139,7 +149,9 @@
TOML</a>. TOML</a>.
</dd> </dd>
<dt> <dt>
<strong>Public</strong> <strong>
Public
</strong>
</dt> </dt>
<dd> <dd>
<a download="{{jabber_id}}_public.json" <a download="{{jabber_id}}_public.json"
@ -150,7 +162,9 @@
TOML</a>. TOML</a>.
</dd> </dd>
<dt> <dt>
<strong>Read</strong> <strong>
Read
</strong>
</dt> </dt>
<dd> <dd>
<a download="{{jabber_id}}_read.json" <a download="{{jabber_id}}_read.json"
@ -162,7 +176,9 @@
</dd> </dd>
</dl> </dl>
</p> </p>
<h4 id="import">Import</h4> <h4 id="import">
Import
</h4>
<p> <p>
Import bookmarks from a file, and choose a node to import Import bookmarks from a file, and choose a node to import
your bookmarks to. your bookmarks to.
@ -175,7 +191,9 @@
<tr> <tr>
<td> <td>
<strong> <strong>
<label for="file">File</label> <label for="file">
File
</label>
</strong> </strong>
</td> </td>
<td> <td>
@ -189,7 +207,9 @@
<tr> <tr>
<td> <td>
<strong> <strong>
<label for="node">Node</label> <label for="node">
Node
</label>
</strong> </strong>
</td> </td>
<td> <td>
@ -214,7 +234,9 @@
<tr> <tr>
<td> <td>
<strong> <strong>
<label for="node">Action</label> <label for="node">
Action
</label>
</strong> </strong>
</td> </td>
<td> <td>
@ -422,7 +444,9 @@ retrieve items only if on a whitelist managed by the node owner.">
proceeding. proceeding.
</p> </p>
<hr/> <hr/>
<h4 id="termination">Termination</h4> <h4 id="termination">
Termination
</h4>
<p> <p>
Due to security concerns, Blasta does not have a built-in Due to security concerns, Blasta does not have a built-in
mechanism to delete nodes. mechanism to delete nodes.
@ -438,7 +462,9 @@ retrieve items only if on a whitelist managed by the node owner.">
<a href="https://psi-im.org">Psi</a>, or <a href="https://psi-im.org">Psi</a>, or
<a href="https://psi-plus.com">Psi+</a>. <a href="https://psi-plus.com">Psi+</a>.
</p> </p>
<h4>Delete your public bookmarks</h4> <h4>
Delete your public bookmarks
</h4>
<pre> <pre>
&lt;iq type='set' &lt;iq type='set'
from='{{jabber_id}}' from='{{jabber_id}}'
@ -449,7 +475,9 @@ retrieve items only if on a whitelist managed by the node owner.">
&lt;/pubsub&gt; &lt;/pubsub&gt;
&lt;/iq&gt; &lt;/iq&gt;
</pre> </pre>
<h4>Delete your private bookmarks</h4> <h4>
Delete your private bookmarks
</h4>
<pre> <pre>
&lt;iq type='set' &lt;iq type='set'
from='{{jabber_id}}' from='{{jabber_id}}'
@ -460,7 +488,9 @@ retrieve items only if on a whitelist managed by the node owner.">
&lt;/pubsub&gt; &lt;/pubsub&gt;
&lt;/iq&gt; &lt;/iq&gt;
</pre> </pre>
<h4>Delete your reading list</h4> <h4>
Delete your reading list
</h4>
<pre> <pre>
&lt;iq type='set' &lt;iq type='set'
from='{{jabber_id}}' from='{{jabber_id}}'