From d6eec41a867ebd445f5ae5fd407786cbb404d9c9 Mon Sep 17 00:00:00 2001 From: Schimon Jehudah Date: Wed, 27 Mar 2024 16:20:32 +0000 Subject: [PATCH] Add commands for PubSub. Improve joining to MUC (WIP). Improve PubSub (WIP). --- slixfeed/action.py | 49 ++++++++++++++++++++++++---- slixfeed/task.py | 4 --- slixfeed/version.py | 4 +-- slixfeed/xmpp/client.py | 34 +++++++++++--------- slixfeed/xmpp/component.py | 19 ++++++----- slixfeed/xmpp/muc.py | 35 +++----------------- slixfeed/xmpp/process.py | 58 +++++++++++++++++++++++++++++++-- slixfeed/xmpp/publish.py | 66 ++++++++++++++++++++++++++++++++++++-- 8 files changed, 199 insertions(+), 70 deletions(-) diff --git a/slixfeed/action.py b/slixfeed/action.py index 7e9b625..e972c2c 100644 --- a/slixfeed/action.py +++ b/slixfeed/action.py @@ -49,6 +49,7 @@ from slixfeed.url import ( ) import slixfeed.task as task from slixfeed.xmpp.bookmark import XmppBookmark +from slixfeed.xmpp.muc import XmppGroupchat from slixfeed.xmpp.iq import XmppIQ from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.presence import XmppPresence @@ -116,6 +117,28 @@ def export_feeds(self, jid, jid_file, ext): return filename +async def xmpp_muc_autojoin(self, bookmarks): + for bookmark in bookmarks: + if bookmark["jid"] and bookmark["autojoin"]: + if not bookmark["nick"]: + bookmark["nick"] = self.alias + logger.error('Alias (i.e. Nicknname) is missing for ' + 'bookmark {}'.format(bookmark['name'])) + alias = bookmark["nick"] + muc_jid = bookmark["jid"] + await XmppGroupchat.join(self, muc_jid, alias) + logger.info('Autojoin groupchat\n' + 'Name : {}\n' + 'JID : {}\n' + 'Alias : {}\n' + .format(bookmark["name"], + bookmark["jid"], + bookmark["nick"])) + elif not bookmark["jid"]: + logger.error('JID is missing for bookmark {}' + .format(bookmark['name'])) + + """ TODO @@ -192,23 +215,30 @@ async def xmpp_send_status_message(self, jid): # ) -async def xmpp_send_pubsub(self, jid_bare): +async def xmpp_send_pubsub(self, jid_bare, num=None): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_bare: {}'.format(function_name, jid_bare)) jid_file = jid_bare.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled') if enabled: + if num: counter = 0 + report = {} subscriptions = sqlite.get_active_feeds_url(db_file) for url in subscriptions: url = url[0] feed_id = sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] - feed_title = sqlite.get_feed_title(db_file, feed_id) - feed_title = feed_title[0] + feed_title = None feed_summary = None - node = sqlite.get_node_name(db_file, feed_id) - node = node[0] + if jid_bare == self.boundjid.bare: + node = 'urn:xmpp:microblog:0' + else: + feed_title = sqlite.get_feed_title(db_file, feed_id) + feed_title = feed_title[0] + feed_summary = None + node = sqlite.get_node_name(db_file, feed_id) + node = node[0] xep = None iq_create_node = XmppPubsub.create_node( self, jid_bare, node, xep, feed_title, feed_summary) @@ -216,13 +246,17 @@ async def xmpp_send_pubsub(self, jid_bare): entries = sqlite.get_unread_entries_of_feed(db_file, feed_id) feed_properties = sqlite.get_feed_properties(db_file, feed_id) feed_version = feed_properties[2] + print('xmpp_send_pubsub',jid_bare) + print(node) + # if num and counter < num: + report[url] = len(entries) for entry in entries: feed_entry = {'author' : None, 'authors' : None, 'category' : None, 'content' : None, 'description' : entry[3], - 'href' : entry[2], + 'link' : entry[2], 'links' : entry[4], 'tags' : None, 'title' : entry[1], @@ -233,6 +267,9 @@ async def xmpp_send_pubsub(self, jid_bare): await XmppIQ.send(self, iq_create_entry) ix = entry[0] await sqlite.mark_as_read(db_file, ix) + # counter += 1 + # if num and counter > num: break + return report async def xmpp_send_message(self, jid, num=None): diff --git a/slixfeed/task.py b/slixfeed/task.py index facb8aa..1c992b5 100644 --- a/slixfeed/task.py +++ b/slixfeed/task.py @@ -152,10 +152,6 @@ async def start_tasks_xmpp_pubsub(self, jid_bare, tasks=None): .format(task, jid_bare)) logging.info('Starting tasks {} for JID {}'.format(tasks, jid_bare)) for task in tasks: - # print("task:", task) - # print("tasks:") - # print(tasks) - # breakpoint() match task: case 'publish': self.task_manager[jid_bare]['publish'] = asyncio.create_task( diff --git a/slixfeed/version.py b/slixfeed/version.py index cd05de4..58fad52 100644 --- a/slixfeed/version.py +++ b/slixfeed/version.py @@ -1,2 +1,2 @@ -__version__ = '0.1.50' -__version_info__ = (0, 1, 50) +__version__ = '0.1.51' +__version_info__ = (0, 1, 51) diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index b47c7f3..6ccdf1b 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -214,10 +214,9 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name message_log = '{}: jid_full: {}' logger.debug(message_log.format(function_name, jid_full)) - inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) - await XmppGroupchat.join(self, inviter, muc_jid) + await XmppGroupchat.join(self, muc_jid) message_body = ('Greetings! I am {}, the news anchor.\n' 'My job is to bring you the latest ' 'news from sources you provide me with.\n' @@ -237,10 +236,9 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name message_log = '{}: jid_full: {}' logger.debug(message_log.format(function_name, jid_full)) - inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) - await XmppGroupchat.join(self, inviter, muc_jid) + await XmppGroupchat.join(self, muc_jid) message_body = ('Greetings! I am {}, the news anchor.\n' 'My job is to bring you the latest ' 'news from sources you provide me with.\n' @@ -285,9 +283,10 @@ class Slixfeed(slixmpp.ClientXMPP): await self['xep_0115'].update_caps() # self.send_presence() await self.get_roster() - bookmarks = await self.plugin['xep_0048'].get_bookmarks() - await XmppGroupchat.autojoin(self, bookmarks) + bookmarks = await XmppBookmark.get(self) + await action.xmpp_muc_autojoin(self, bookmarks) jids = await XmppPubsub.get_pubsub_services(self) + print(jids) for jid_bare in jids: if jid_bare not in self.settings: jid_file = jid_bare @@ -311,8 +310,8 @@ class Slixfeed(slixmpp.ClientXMPP): # self.send_presence() profile.set_identity(self, 'client') self['xep_0115'].update_caps() - bookmarks = await self.plugin['xep_0048'].get_bookmarks() - await XmppGroupchat.autojoin(self, bookmarks) + bookmarks = await XmppBookmark.get(self) + await action.xmpp_muc_autojoin(self, bookmarks) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -782,7 +781,7 @@ class Slixfeed(slixmpp.ClientXMPP): form['instructions'] = ('Choose a PubSub Jabber ID and verify ' 'that Slixfeed has the necessary ' 'permissions to publish into it.') - form.add_field(var='subscription', + form.add_field(var='url', # TODO Make it possible to add several subscriptions at once; # Similarly to BitTorrent trackers list # ftype='text-multi', @@ -833,7 +832,7 @@ class Slixfeed(slixmpp.ClientXMPP): # options.addOption('XEP-0060: Publish-Subscribe', '0060') # options.addOption('XEP-0277: Microblogging over XMPP', '0277') # options.addOption('XEP-0472: Pubsub Social Feed', '0472') - session['next'] = self._handle_subscription_new + session['next'] = self._handle_preview session['payload'] = form case 'post': form = self['xep_0004'].make_form('form', 'Post') @@ -912,7 +911,7 @@ class Slixfeed(slixmpp.ClientXMPP): return session node = values['node'] url = values['url'] - xep = values['xep'] + # xep = values['xep'] if not node: if jid == self.boundjid.bare: node = 'urn:xmpp:microblog:0' @@ -958,6 +957,7 @@ class Slixfeed(slixmpp.ClientXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_post_complete + session['notes'] = None session['prev'] = self._handle_publish session['payload'] = form break @@ -981,6 +981,7 @@ class Slixfeed(slixmpp.ClientXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_preview + session['notes'] = None session['prev'] = self._handle_publish session['payload'] = form break @@ -1006,9 +1007,9 @@ class Slixfeed(slixmpp.ClientXMPP): form.add_field(var='url', ftype='hidden', value=url) - form.add_field(var='xep', - ftype='hidden', - value=xep) + # form.add_field(var='xep', + # ftype='hidden', + # value=xep) return session async def _handle_post_complete(self, payload, session): @@ -1021,7 +1022,8 @@ class Slixfeed(slixmpp.ClientXMPP): node = values['node'][0] jid = values['jid'][0] url = values['url'][0] - xep = values['xep'][0] + # xep = values['xep'][0] + xep = None result = await fetch.http(url) if 'content' in result: document = result['content'] @@ -1054,7 +1056,7 @@ class Slixfeed(slixmpp.ClientXMPP): # title = "*** No title ***" # if feed.entries[entry].has_key("summary"): # summary = feed.entries[entry].summary - iq_create_entry = XmppPubsub.create_entry( + iq_create_entry = XmppPubsub._create_entry( self, jid, node, feed_entry, feed_version) await XmppIQ.send(self, iq_create_entry) text_info = 'Posted {} entries.'.format(len(entries)) diff --git a/slixfeed/xmpp/component.py b/slixfeed/xmpp/component.py index 609fc8a..f2d0754 100644 --- a/slixfeed/xmpp/component.py +++ b/slixfeed/xmpp/component.py @@ -743,7 +743,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): form['instructions'] = ('Choose a PubSub Jabber ID and verify ' 'that Slixfeed has the necessary ' 'permissions to publish into it.') - form.add_field(var='subscription', + form.add_field(var='url', # TODO Make it possible to add several subscriptions at once; # Similarly to BitTorrent trackers list # ftype='text-multi', @@ -794,7 +794,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # options.addOption('XEP-0060: Publish-Subscribe', '0060') # options.addOption('XEP-0277: Microblogging over XMPP', '0277') # options.addOption('XEP-0472: Pubsub Social Feed', '0472') - session['next'] = self._handle_subscription_new + session['next'] = self._handle_preview session['payload'] = form case 'post': form = self['xep_0004'].make_form('form', 'Post') @@ -873,7 +873,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): return session node = values['node'] url = values['url'] - xep = values['xep'] + # xep = values['xep'] if not node: if jid == self.boundjid.bare: node = 'urn:xmpp:microblog:0' @@ -919,6 +919,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_post_complete + session['notes'] = None session['prev'] = self._handle_publish session['payload'] = form break @@ -942,6 +943,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_preview + session['notes'] = None session['prev'] = self._handle_publish session['payload'] = form break @@ -967,9 +969,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): form.add_field(var='url', ftype='hidden', value=url) - form.add_field(var='xep', - ftype='hidden', - value=xep) + # form.add_field(var='xep', + # ftype='hidden', + # value=xep) return session async def _handle_post_complete(self, payload, session): @@ -982,7 +984,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): node = values['node'][0] jid = values['jid'][0] url = values['url'][0] - xep = values['xep'][0] + # xep = values['xep'][0] + xep = None result = await fetch.http(url) if 'content' in result: document = result['content'] @@ -1015,7 +1018,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # title = "*** No title ***" # if feed.entries[entry].has_key("summary"): # summary = feed.entries[entry].summary - iq_create_entry = XmppPubsub.create_entry( + iq_create_entry = XmppPubsub._create_entry( self, jid, node, feed_entry, feed_version) await XmppIQ.send(self, iq_create_entry) text_info = 'Posted {} entries.'.format(len(entries)) diff --git a/slixfeed/xmpp/muc.py b/slixfeed/xmpp/muc.py index ae2208a..9582c95 100644 --- a/slixfeed/xmpp/muc.py +++ b/slixfeed/xmpp/muc.py @@ -34,32 +34,7 @@ class XmppGroupchat: self.join(self, inviter, jid) - async def autojoin(self, bookmarks): - jid_from = str(self.boundjid) if self.is_component else None - conferences = bookmarks["private"]["bookmarks"]["conferences"] - for conference in conferences: - if conference["jid"] and conference["autojoin"]: - if not conference["nick"]: - conference["nick"] = self.alias - logging.error('Alias (i.e. Nicknname) is missing for ' - 'bookmark {}'.format(conference['name'])) - await self.plugin['xep_0045'].join_muc_wait(conference["jid"], - conference["nick"], - presence_options = {"pfrom" : jid_from}, - password=None) - logging.info('Autojoin groupchat\n' - 'Name : {}\n' - 'JID : {}\n' - 'Alias : {}\n' - .format(conference["name"], - conference["jid"], - conference["nick"])) - elif not conference["jid"]: - logging.error('JID is missing for bookmark {}' - .format(conference['name'])) - - - async def join(self, inviter, jid): + async def join(self, jid, alias=None, password=None): # token = await initdb( # muc_jid, # sqlite.get_setting_value, @@ -81,13 +56,13 @@ class XmppGroupchat: # ) logging.info('Joining groupchat\n' 'JID : {}\n' - 'Inviter : {}\n' - .format(jid, inviter)) + .format(jid)) jid_from = str(self.boundjid) if self.is_component else None + if alias == None: self.alias await self.plugin['xep_0045'].join_muc_wait(jid, - self.alias, + alias, presence_options = {"pfrom" : jid_from}, - password=None) + password=password) def leave(self, jid): diff --git a/slixfeed/xmpp/process.py b/slixfeed/xmpp/process.py index 295833d..7a3b861 100644 --- a/slixfeed/xmpp/process.py +++ b/slixfeed/xmpp/process.py @@ -897,7 +897,7 @@ async def message(self, message): muc_jid = uri.check_xmpp_uri(message_text[5:]) if muc_jid: # TODO probe JID and confirm it's a groupchat - XmppGroupchat.join(self, jid_bare, muc_jid) + XmppGroupchat.join(self, muc_jid) # await XmppBookmark.add(self, jid=muc_jid) response = ('Joined groupchat {}' .format(message_text)) @@ -981,6 +981,60 @@ async def message(self, message): db_file, key, val) response = 'Only new items of newly added feeds be delivered.' XmppMessage.send_reply(self, message, response) + case _ if message_lowercase.startswith('pubsub delete '): + if is_operator(self, jid_bare): + info = message_text[14:] + info = info.split(' ') + jid = info[0] + nid = info[1] + if jid: + from slixfeed.xmpp.publish import XmppPubsub + XmppPubsub.delete_node(self, jid, nid) + response = 'Deleted node: ' + nid + else: + response = 'PubSub JID is missing. Enter PubSub JID.' + else: + response = ('This action is restricted. ' + 'Type: sending news to PubSub.') + XmppMessage.send_reply(self, message, response) + case _ if message_lowercase.startswith('pubsub purge '): + if is_operator(self, jid_bare): + info = message_text[13:] + info = info.split(' ') + jid = info[0] + nid = info[1] + if jid: + from slixfeed.xmpp.publish import XmppPubsub + XmppPubsub.purge_node(self, jid, nid) + response = 'Purged node: ' + nid + else: + response = 'PubSub JID is missing. Enter PubSub JID.' + else: + response = ('This action is restricted. ' + 'Type: sending news to PubSub.') + XmppMessage.send_reply(self, message, response) + case _ if message_lowercase.startswith('pubsub flash '): + if is_operator(self, jid_bare): + info = message_text[13:] + info = info.split(' ') + jid = info[0] + num = int(info[1]) + if jid: + if num: + report = await action.xmpp_send_pubsub(self, jid, + num) + else: + report = await action.xmpp_send_pubsub(self, jid) + response = '' + for url in report: + if report[url]: + response += url + ' : ' + str(report[url]) + '\n' + else: + response = 'PubSub JID is missing. Enter PubSub JID.' + else: + response = ('This action is restricted. ' + 'Type: sending news to PubSub.') + XmppMessage.send_reply(self, message, response) case _ if message_lowercase.startswith('next'): num = message_text[5:] if num: @@ -1398,7 +1452,7 @@ async def message(self, message): muc_jid = uri.check_xmpp_uri(message_text) if muc_jid: # TODO probe JID and confirm it's a groupchat - XmppGroupchat.join(self, jid_bare, muc_jid) + XmppGroupchat.join(self, muc_jid) # await XmppBookmark.add(self, jid=muc_jid) response = ('Joined groupchat {}' .format(message_text)) diff --git a/slixfeed/xmpp/publish.py b/slixfeed/xmpp/publish.py index e3f3b2b..1a8f61c 100644 --- a/slixfeed/xmpp/publish.py +++ b/slixfeed/xmpp/publish.py @@ -28,8 +28,23 @@ class XmppPubsub: return jids + 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 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) + # iq = self.Iq(stype='set', + # sto=jid, + # 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, summary=None): + def create_node(self, jid, node, xep ,title=None, summary=None): jid_from = str(self.boundjid) if self.is_component else None iq = self.Iq(stype='set', sto=jid, @@ -85,7 +100,7 @@ class XmppPubsub: # NOTE Warning: Entry might not have a link # TODO Handle situation error - url_encoded = entry.link.encode() + url_encoded = entry['link'].encode() url_hashed = hashlib.md5(url_encoded) url_digest = url_hashed.hexdigest() item['id'] = url_digest @@ -99,6 +114,53 @@ class XmppPubsub: updated = ET.SubElement(node_entry, "updated") updated.text = entry['updated'] + # Content + content = ET.SubElement(node_entry, "content") + content.set('type', 'text/html') + content.text = entry['description'] + + # Links + link = ET.SubElement(node_entry, "link") + link.set('href', entry['link']) + + item['payload'] = node_entry + + iq['pubsub']['publish'].append(item) + + return iq + + + def _create_entry(self, jid, node, entry, version): + iq = self.Iq(stype="set", sto=jid) + iq['pubsub']['publish']['node'] = node + + item = pubsub.Item() + + # From atomtopubsub: + # character / is causing a bug in movim. replacing : and , with - in id. + # It provides nicer urls. + + # Respond to atomtopubsub: + # I think it would be beneficial to use md5 checksum of Url as Id for + # cross reference, and namely - in another project to utilize PubSub as + # links sharing system (see del.icio.us) - to share node entries. + + # NOTE Warning: Entry might not have a link + # TODO Handle situation error + url_encoded = entry.link.encode() + url_hashed = hashlib.md5(url_encoded) + url_digest = url_hashed.hexdigest() + item['id'] = url_digest + '_html' + + node_entry = ET.Element("entry") + node_entry.set('xmlns', 'http://www.w3.org/2005/Atom') + + title = ET.SubElement(node_entry, "title") + title.text = entry.title + + updated = ET.SubElement(node_entry, "updated") + updated.text = entry.updated + # Content if version == 'atom3':