From 263382ba8db5b274fe651ef17da8b4704d5fb81a Mon Sep 17 00:00:00 2001 From: "Schimon Jehudah, Adv." Date: Wed, 25 Sep 2024 11:56:46 +0300 Subject: [PATCH] Add Gajim to the list of recommended clients; Improve PEP and PubSub publishing mechanism. --- slixfeed/__main__.py | 4 -- slixfeed/assets/about.toml | 13 ++-- slixfeed/log.py | 2 + slixfeed/syndication.py | 2 +- slixfeed/xmpp/chat.py | 2 +- slixfeed/xmpp/client.py | 32 +++++++-- slixfeed/xmpp/iq.py | 5 +- slixfeed/xmpp/publish.py | 142 +++++++++++++++++++++---------------- 8 files changed, 123 insertions(+), 79 deletions(-) diff --git a/slixfeed/__main__.py b/slixfeed/__main__.py index 44d8897..49107e8 100644 --- a/slixfeed/__main__.py +++ b/slixfeed/__main__.py @@ -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 diff --git a/slixfeed/assets/about.toml b/slixfeed/assets/about.toml index 9c63615..a11e6b7 100644 --- a/slixfeed/assets/about.toml +++ b/slixfeed/assets/about.toml @@ -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" diff --git a/slixfeed/log.py b/slixfeed/log.py index 62deb0e..c1ec773 100644 --- a/slixfeed/log.py +++ b/slixfeed/log.py @@ -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) diff --git a/slixfeed/syndication.py b/slixfeed/syndication.py index efc3fba..a167437 100644 --- a/slixfeed/syndication.py +++ b/slixfeed/syndication.py @@ -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) diff --git a/slixfeed/xmpp/chat.py b/slixfeed/xmpp/chat.py index cb0ff83..0e8e910 100644 --- a/slixfeed/xmpp/chat.py +++ b/slixfeed/xmpp/chat.py @@ -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() diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index 8c7590c..b6d7a6e 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -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', diff --git a/slixfeed/xmpp/iq.py b/slixfeed/xmpp/iq.py index 5081a2a..3eab2f3 100644 --- a/slixfeed/xmpp/iq.py +++ b/slixfeed/xmpp/iq.py @@ -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 diff --git a/slixfeed/xmpp/publish.py b/slixfeed/xmpp/publish.py index 4599329..9d5bf0d 100644 --- a/slixfeed/xmpp/publish.py +++ b/slixfeed/xmpp/publish.py @@ -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 ------- @@ -323,38 +324,56 @@ 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)