Add Gajim to the list of recommended clients;

Improve PEP and PubSub publishing mechanism.
This commit is contained in:
Schimon Jehudah, Adv. 2024-09-25 11:56:46 +03:00
parent 640677437c
commit 263382ba8d
8 changed files with 123 additions and 79 deletions

View file

@ -123,10 +123,6 @@ def main():
# Setup logging. # Setup logging.
logging.basicConfig(level=args.loglevel, logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s') format='%(levelname)-8s %(message)s')
# # Setup logging.
# logging.basicConfig(level=args.loglevel,
# format='%(levelname)-8s %(message)s')
# # logging.basicConfig(format='[%(levelname)s] %(message)s') # # logging.basicConfig(format='[%(levelname)s] %(message)s')
# logger = logging.getLogger() # logger = logging.getLogger()
# logdbg = logger.debug # logdbg = logger.debug

View file

@ -653,10 +653,15 @@ or on your desktop.
url = "https://conversejs.org" url = "https://conversejs.org"
platform = "HTML (Web)" platform = "HTML (Web)"
# [[clients]] [[clients]]
# name = "Gajim" name = "Gajim"
# info = "XMPP client for desktop" info = "XMPP client for desktop"
# url = "https://gajim.org" info = ["""
Gajim aims to be an easy to use and fully-featured XMPP client. \
It is open source and released under the GNU General Public License (GPL).
"""]
url = "https://gajim.org"
platform = "Any"
# [[clients]] # [[clients]]
# name = "Monal IM" # name = "Monal IM"

View file

@ -19,6 +19,8 @@ import logging
class Logger: class Logger:
def set_logging_level(level):
logging.basicConfig(level)
def __init__(self, name): def __init__(self, name):
self.logger = logging.getLogger(name) self.logger = logging.getLogger(name)

View file

@ -1278,7 +1278,7 @@ class FeedTask:
db_file = config.get_pathname_to_database(jid_bare) db_file = config.get_pathname_to_database(jid_bare)
urls = sqlite.get_active_feeds_url_sorted_by_last_scanned(db_file) urls = sqlite.get_active_feeds_url_sorted_by_last_scanned(db_file)
for url in urls: for url in urls:
Message.printer('Scanning updates for URL {} ...'.format(url)) #Message.printer('Scanning updates for URL {} ...'.format(url))
url = url[0] url = url[0]
# print('STA',url) # print('STA',url)

View file

@ -161,7 +161,7 @@ class XmppChat:
# Adding one to the length because of # Adding one to the length because of
# assumption that a comma or a dot is added # assumption that a comma or a dot is added
alias_of_slixfeed_length = len(alias_of_slixfeed) + 1 alias_of_slixfeed_length = len(alias_of_slixfeed) + 1
command = (command[alias_of_slixfeed_length:]).lstrip() command = command[alias_of_slixfeed_length:].lstrip()
if isinstance(command, Message): command = command['body'] if isinstance(command, Message): command = command['body']
command_lowercase = command.lower() command_lowercase = command.lower()

View file

@ -26,6 +26,9 @@ NOTE
message = xmltodict.parse(str(message)) message = xmltodict.parse(str(message))
jid = message["message"]["x"]["@jid"] jid = message["message"]["x"]["@jid"]
2) It seems that XmppRoster.get_contacts(self) is being used excessively.
Use self.client_roster instead.
""" """
@ -383,9 +386,20 @@ class XmppClient(slixmpp.ClientXMPP):
print('POSIX sockets: Initiating IPC server...') print('POSIX sockets: Initiating IPC server...')
self.ipc = asyncio.create_task(XmppIpcServer.ipc(self)) self.ipc = asyncio.create_task(XmppIpcServer.ipc(self))
results = await XmppPubsub.get_pubsub_services(self) jids_of_pubsub = await XmppPubsub.get_pubsub_services(self)
for result in results + [{'jid' : self.boundjid.bare, for jid_bare in jids_of_pubsub:
'name' : self.alias}]: jid_bare['type'] = 'pubsub'
# NOTE Do you need 'name' too, or only 'jid'?
#jids_of_roster = []
#for jid_bare in self.client_roster:
# jids_of_roster.append({'jid' : jid_bare,
# 'name' : '',
# 'type' : 'pep'})
jid_of_slixfeed = [{'jid' : self.boundjid.bare,
'name' : self.alias,
'type' : 'pep'}]
#for result in jids_of_pubsub + jids_of_roster + jid_of_slixfeed:
for result in jids_of_pubsub + jid_of_slixfeed:
jid_bare = result['jid'] jid_bare = result['jid']
if jid_bare not in self.settings: if jid_bare not in self.settings:
db_file = config.get_pathname_to_database(jid_bare) db_file = config.get_pathname_to_database(jid_bare)
@ -399,8 +413,12 @@ class XmppClient(slixmpp.ClientXMPP):
# asyncio.create_task(FeedTask.loop_task(self, jid_bare)) # asyncio.create_task(FeedTask.loop_task(self, jid_bare))
#] #]
#await asyncio.gather(*tasks) #await asyncio.gather(*tasks)
print('feed task for {}'.format(jid_bare))
asyncio.create_task(FeedTask.loop_task(self, jid_bare)) asyncio.create_task(FeedTask.loop_task(self, jid_bare))
asyncio.create_task(XmppPubsubTask.loop_task(self, jid_bare)), #await asyncio.sleep(10)
print('publish task for {}'.format(jid_bare))
publish_type = result['type']
asyncio.create_task(XmppPubsubTask.loop_task(self, jid_bare, publish_type)),
print('End') print('End')
time_end = time.time() time_end = time.time()
@ -1802,7 +1820,7 @@ class XmppClient(slixmpp.ClientXMPP):
identifier = values['identifier'] if 'identifier' in values else None identifier = values['identifier'] if 'identifier' in values else None
url = values['subscription'] url = values['subscription']
jid_bare = session['from'].bare jid_bare = session['from'].bare
if 'jid' in values: custom_jid = values['jid'] custom_jid = values['jid'] if 'jid' in values else None
if XmppUtilities.is_operator(self, jid_bare) and custom_jid: if XmppUtilities.is_operator(self, jid_bare) and custom_jid:
if isinstance(custom_jid, list): custom_jid = custom_jid[0] if isinstance(custom_jid, list): custom_jid = custom_jid[0]
jid_bare = custom_jid or jid_bare jid_bare = custom_jid or jid_bare
@ -1929,14 +1947,16 @@ class XmppClient(slixmpp.ClientXMPP):
entries = sqlite.get_entries_of_feed(db_file, feed_id) entries = sqlite.get_entries_of_feed(db_file, feed_id)
renewed, scanned = sqlite.get_last_update_time_of_feed(db_file, renewed, scanned = sqlite.get_last_update_time_of_feed(db_file,
feed_id) feed_id)
last_updated_string = renewed or scanned
last_updated = DateAndTime.convert_seconds_to_yyyy_mm_dd( last_updated = DateAndTime.convert_seconds_to_yyyy_mm_dd(
float(renewed or scanned)) float(last_updated_string)) if last_updated_string else 'N/A'
form.add_field(desc='Recent titles from subscription', form.add_field(desc='Recent titles from subscription',
ftype='fixed', ftype='fixed',
value='Recent updates') value='Recent updates')
recent_updates = '' recent_updates = ''
for entry in entries: for entry in entries:
recent_updates += '* ' + entry[1] + '\n\n' recent_updates += '* ' + entry[1] + '\n\n'
if not recent_updates: recent_updates = 'N/A'
form.add_field(ftype='text-multi', form.add_field(ftype='text-multi',
value=recent_updates) value=recent_updates)
form.add_field(ftype='fixed', form.add_field(ftype='fixed',

View file

@ -10,10 +10,13 @@ class XmppIQ:
async def send(self, iq): async def send(self, iq):
try: try:
await iq.send(timeout=15) result = await iq.send(timeout=15)
except IqTimeout as e: except IqTimeout as e:
logger.error('Error Timeout') logger.error('Error Timeout')
logger.error(str(e)) logger.error(str(e))
result = e
except IqError as e: except IqError as e:
logger.error('Error XmppIQ') logger.error('Error XmppIQ')
logger.error(str(e)) logger.error(str(e))
result = e
return result

View file

@ -44,10 +44,10 @@ class XmppPubsub:
return results return results
async def get_node_properties(self, jid, node): async def get_node_properties(self, jid_bare, node):
config = await self.plugin['xep_0060'].get_node_config(jid, node) config = await self.plugin['xep_0060'].get_node_config(jid_bare, node)
subscriptions = await self.plugin['xep_0060'].get_node_subscriptions(jid, node) subscriptions = await self.plugin['xep_0060'].get_node_subscriptions(jid_bare, node)
affiliations = await self.plugin['xep_0060'].get_node_affiliations(jid, node) affiliations = await self.plugin['xep_0060'].get_node_affiliations(jid_bare, node)
properties = {'config': config, properties = {'config': config,
'subscriptions': subscriptions, 'subscriptions': subscriptions,
'affiliations': affiliations} 'affiliations': affiliations}
@ -55,49 +55,48 @@ class XmppPubsub:
return properties return properties
async def get_node_configuration(self, jid, node_id):
node = await self.plugin['xep_0060'].get_node_config(jid, node_id) async def get_node_configuration(self, jid_bare, node_id):
if not node: node = await self.plugin['xep_0060'].get_node_config(jid_bare, node_id)
print('NODE CONFIG', node_id, str(node))
return node return node
async def get_nodes(self, jid): async def get_nodes(self, jid_bare):
nodes = await self.plugin['xep_0060'].get_nodes(jid) nodes = await self.plugin['xep_0060'].get_nodes(jid_bare)
# 'self' would lead to slixmpp.jid.InvalidJID: idna validation failed: # 'self' would lead to slixmpp.jid.InvalidJID: idna validation failed:
return nodes return nodes
async def get_item(self, jid, node, item_id): async def get_item(self, jid_bare, node, item_id):
item = await self.plugin['xep_0060'].get_item(jid, node, item_id) item = await self.plugin['xep_0060'].get_item(jid_bare, node, item_id)
return item return item
async def get_items(self, jid, node): async def get_items(self, jid_bare, node):
items = await self.plugin['xep_0060'].get_items(jid, node) items = await self.plugin['xep_0060'].get_items(jid_bare, node)
return items return items
def delete_node(self, jid, node): def delete_node(self, jid_bare, node):
jid_from = str(self.boundjid) if self.is_component else None jid_from = self.boundjid.bare if self.is_component else None
self.plugin['xep_0060'].delete_node(jid, node, ifrom=jid_from) self.plugin['xep_0060'].delete_node(jid_bare, node, ifrom=jid_from)
def purge_node(self, jid, node): def purge_node(self, jid_bare, node):
jid_from = str(self.boundjid) if self.is_component else None jid_from = self.boundjid.bare if self.is_component else None
self.plugin['xep_0060'].purge(jid, node, ifrom=jid_from) self.plugin['xep_0060'].purge(jid_bare, node, ifrom=jid_from)
# iq = self.Iq(stype='set', # iq = self.Iq(stype='set',
# sto=jid, # sto=jid_bare,
# sfrom=jid_from) # sfrom=jid_from)
# iq['pubsub']['purge']['node'] = node # iq['pubsub']['purge']['node'] = node
# return iq # return iq
# TODO Make use of var "xep" with match/case (XEP-0060, XEP-0277, XEP-0472) # TODO Make use of var "xep" with match/case (XEP-0060, XEP-0277, XEP-0472)
def create_node(self, jid, node, xep ,title=None, subtitle=None): def create_node(self, jid_bare, node, xep=None ,title=None, subtitle=None):
jid_from = str(self.boundjid) if self.is_component else None jid_from = self.boundjid.bare if self.is_component else None
iq = self.Iq(stype='set', iq = self.Iq(stype='set',
sto=jid, sto=jid_bare,
sfrom=jid_from) sfrom=jid_from)
iq['pubsub']['create']['node'] = node iq['pubsub']['create']['node'] = node
form = iq['pubsub']['configure']['form'] form = iq['pubsub']['configure']['form']
@ -131,8 +130,8 @@ class XmppPubsub:
# TODO Consider to create a separate function called "create_atom_entry" # TODO Consider to create a separate function called "create_atom_entry"
# or "create_rfc4287_entry" for anything related to variable "node_entry". # or "create_rfc4287_entry" for anything related to variable "node_entry".
def create_entry(self, jid, node_id, item_id, node_item): def create_entry(self, jid_bare, node_id, item_id, node_item):
iq = self.Iq(stype="set", sto=jid) iq = self.Iq(stype="set", sto=jid_bare)
iq['pubsub']['publish']['node'] = node_id iq['pubsub']['publish']['node'] = node_id
item = pubsub.Item() item = pubsub.Item()
@ -153,8 +152,8 @@ class XmppPubsub:
return iq return iq
def _create_entry(self, jid, node, entry, version): def _create_entry(self, jid_bare, node, entry, version):
iq = self.Iq(stype="set", sto=jid) iq = self.Iq(stype="set", sto=jid_bare)
iq['pubsub']['publish']['node'] = node iq['pubsub']['publish']['node'] = node
item = pubsub.Item() item = pubsub.Item()
@ -294,13 +293,15 @@ class XmppPubsubAction:
return report return report
async def send_unread_items(self, jid_bare): async def send_unread_items(self, jid_bare, publish_type):
""" """
Parameters Parameters
---------- ----------
jid_bare : TYPE jid_bare : str
Bare Jabber ID. Bare Jabber ID.
publish_type : str
To which type of PubSub ('pep' or 'pubsub').
Returns Returns
------- -------
@ -324,37 +325,55 @@ class XmppPubsubAction:
# Publish to node 'urn:xmpp:microblog:0' for own JID # Publish to node 'urn:xmpp:microblog:0' for own JID
# Publish to node based on feed identifier for PubSub service. # Publish to node based on feed identifier for PubSub service.
if jid_bare == self.boundjid.bare: match publish_type:
node_id = 'urn:xmpp:microblog:0' # XEP-0163: Personal Eventing Protocol
node_subtitle = None # 2.2 One Publisher Per Node¶
node_title = None # The owner-publisher for every node is the bare JID of the account owner.
else: case 'pep':
# node_id = feed_properties[2] node_id = 'urn:xmpp:microblog:0'
# node_title = feed_properties[3] node_subtitle = None
# node_subtitle = feed_properties[5] node_title = None
node_id = sqlite.get_feed_identifier(db_file, feed_id) case 'pubsub':
node_id = node_id[0] # node_id = feed_properties[2]
if not node_id: # node_title = feed_properties[3]
counter = 0 # node_subtitle = feed_properties[5]
while True:
identifier = String.generate_identifier(url, counter)
if sqlite.check_identifier_exist(db_file, identifier):
counter += 1
else:
break
await sqlite.update_feed_identifier(db_file, feed_id, identifier)
node_id = sqlite.get_feed_identifier(db_file, feed_id) node_id = sqlite.get_feed_identifier(db_file, feed_id)
node_id = node_id[0] node_id = node_id[0]
node_title = sqlite.get_feed_title(db_file, feed_id) if not node_id:
node_title = node_title[0] counter = 0
node_subtitle = sqlite.get_feed_subtitle(db_file, feed_id) while True:
node_subtitle = node_subtitle[0] identifier = String.generate_identifier(url, counter)
if sqlite.check_identifier_exist(db_file, identifier):
counter += 1
else:
break
await sqlite.update_feed_identifier(db_file, feed_id, identifier)
node_id = sqlite.get_feed_identifier(db_file, feed_id)
node_id = node_id[0]
node_title = sqlite.get_feed_title(db_file, feed_id)
node_title = node_title[0]
node_subtitle = sqlite.get_feed_subtitle(db_file, feed_id)
node_subtitle = node_subtitle[0]
print ([jid_bare, publish_type, node_id])
xep = None xep = None
node_exist = await XmppPubsub.get_node_configuration(self, jid_bare, node_id) #node_exist = await XmppPubsub.get_node_configuration(self, jid_bare, node_id)
nodes = await XmppPubsub.get_nodes(self, jid_bare)
node_items = nodes['disco_items']['items']
node_exist = False
for node_item in node_items:
if node_item[1] == node_id:
node_exist = True
break
print(['node_exist', node_exist])
if not node_exist: if not node_exist:
iq_create_node = XmppPubsub.create_node( iq_create_node = XmppPubsub.create_node(
self, jid_bare, node_id, xep, node_title, node_subtitle) self, jid_bare, node_id, xep, node_title, node_subtitle)
await XmppIQ.send(self, iq_create_node) result = await XmppIQ.send(self, iq_create_node)
result_condition = result.iq['error']['condition']
if result_condition in ('forbidden', 'service-unavailable'):
reason = result.iq['error']['text']
print('Creation of node {} for JID {} has failed'.format(node_id, jid_bare, reason))
return
entries = sqlite.get_unread_entries_of_feed(db_file, feed_id) entries = sqlite.get_unread_entries_of_feed(db_file, feed_id)
report[url] = len(entries) report[url] = len(entries)
for entry in entries: for entry in entries:
@ -362,12 +381,11 @@ class XmppPubsubAction:
node_entry = Feed.create_rfc4287_entry(feed_entry) node_entry = Feed.create_rfc4287_entry(feed_entry)
entry_url = feed_entry['link'] entry_url = feed_entry['link']
item_id = Utilities.hash_url_to_md5(entry_url) item_id = Utilities.hash_url_to_md5(entry_url)
print('PubSub node item was sent to', jid_bare, node_id) print(['PubSub node item was sent to', jid_bare, node_id])
print(entry_url) print([entry_url, item_id])
print(item_id)
iq_create_entry = XmppPubsub.create_entry( iq_create_entry = XmppPubsub.create_entry(
self, jid_bare, node_id, item_id, node_entry) self, jid_bare, node_id, item_id, node_entry)
await XmppIQ.send(self, iq_create_entry) result = await XmppIQ.send(self, iq_create_entry)
ix = entry[0] ix = entry[0]
await sqlite.mark_as_read(db_file, ix) await sqlite.mark_as_read(db_file, ix)
print(report) print(report)
@ -377,7 +395,7 @@ class XmppPubsubAction:
class XmppPubsubTask: class XmppPubsubTask:
async def loop_task(self, jid_bare): async def loop_task(self, jid_bare, publish_type):
db_file = config.get_pathname_to_database(jid_bare) db_file = config.get_pathname_to_database(jid_bare)
if jid_bare not in self.settings: if jid_bare not in self.settings:
Config.add_settings_jid(self, jid_bare, db_file) Config.add_settings_jid(self, jid_bare, db_file)
@ -394,7 +412,7 @@ class XmppPubsubTask:
.format(jid_bare)) .format(jid_bare))
logger.info('Starting tasks "publish" for JID {}'.format(jid_bare)) logger.info('Starting tasks "publish" for JID {}'.format(jid_bare))
self.task_manager[jid_bare]['publish'] = asyncio.create_task( self.task_manager[jid_bare]['publish'] = asyncio.create_task(
XmppPubsubAction.send_unread_items(self, jid_bare)) XmppPubsubAction.send_unread_items(self, jid_bare, publish_type))
await asyncio.sleep(60 * 180) await asyncio.sleep(60 * 180)