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.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
# # Setup logging.
# logging.basicConfig(level=args.loglevel,
# format='%(levelname)-8s %(message)s')
# # logging.basicConfig(format='[%(levelname)s] %(message)s')
# logger = logging.getLogger()
# logdbg = logger.debug

View file

@ -653,10 +653,15 @@ or on your desktop.
url = "https://conversejs.org"
platform = "HTML (Web)"
# [[clients]]
# name = "Gajim"
# info = "XMPP client for desktop"
# url = "https://gajim.org"
[[clients]]
name = "Gajim"
info = "XMPP client for desktop"
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]]
# name = "Monal IM"

View file

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

View file

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

View file

@ -161,7 +161,7 @@ class XmppChat:
# Adding one to the length because of
# assumption that a comma or a dot is added
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']
command_lowercase = command.lower()

View file

@ -26,6 +26,9 @@ NOTE
message = xmltodict.parse(str(message))
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...')
self.ipc = asyncio.create_task(XmppIpcServer.ipc(self))
results = await XmppPubsub.get_pubsub_services(self)
for result in results + [{'jid' : self.boundjid.bare,
'name' : self.alias}]:
jids_of_pubsub = await XmppPubsub.get_pubsub_services(self)
for jid_bare in jids_of_pubsub:
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']
if jid_bare not in self.settings:
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))
#]
#await asyncio.gather(*tasks)
print('feed task for {}'.format(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')
time_end = time.time()
@ -1802,7 +1820,7 @@ class XmppClient(slixmpp.ClientXMPP):
identifier = values['identifier'] if 'identifier' in values else None
url = values['subscription']
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 isinstance(custom_jid, list): custom_jid = custom_jid[0]
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)
renewed, scanned = sqlite.get_last_update_time_of_feed(db_file,
feed_id)
last_updated_string = renewed or scanned
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',
ftype='fixed',
value='Recent updates')
recent_updates = ''
for entry in entries:
recent_updates += '* ' + entry[1] + '\n\n'
if not recent_updates: recent_updates = 'N/A'
form.add_field(ftype='text-multi',
value=recent_updates)
form.add_field(ftype='fixed',

View file

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

View file

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