From 5507b161613cfae0d6d8895e10e6740e4630f9e6 Mon Sep 17 00:00:00 2001 From: Schimon Jehudah Date: Tue, 26 Mar 2024 16:23:22 +0000 Subject: [PATCH] Improve PubSub support (WIP). Improve MUC handling. Add command "send" to add feeds to other JIDs. Ad-Hoc: Add functionality to specify JId to commit actions on upon. Add subscription preview upon adding a subscription. Add project Jabber RSS Transport to bots list. Add projects Psi and Psi+ to clients list. --- slixfeed/action.py | 313 +++++++----- slixfeed/assets/about.toml | 26 +- slixfeed/assets/feeds.toml | 60 ++- slixfeed/sqlite.py | 264 +++++++++- slixfeed/task.py | 90 +++- slixfeed/url.py | 4 +- slixfeed/version.py | 4 +- slixfeed/xmpp/client.py | 942 +++++++++++++++++++----------------- slixfeed/xmpp/command.py | 8 +- slixfeed/xmpp/component.py | 962 +++++++++++++++++++------------------ slixfeed/xmpp/muc.py | 29 +- slixfeed/xmpp/process.py | 171 +++++-- slixfeed/xmpp/publish.py | 28 +- 13 files changed, 1767 insertions(+), 1134 deletions(-) diff --git a/slixfeed/action.py b/slixfeed/action.py index 36359c7..f14f26e 100644 --- a/slixfeed/action.py +++ b/slixfeed/action.py @@ -49,8 +49,10 @@ from slixfeed.url import ( ) import slixfeed.task as task from slixfeed.xmpp.bookmark import XmppBookmark +from slixfeed.xmpp.iq import XmppIQ from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.presence import XmppPresence +from slixfeed.xmpp.publish import XmppPubsub from slixfeed.xmpp.upload import XmppUpload from slixfeed.xmpp.utility import get_chat_type import sys @@ -134,7 +136,7 @@ if (await get_chat_type(self, jid_bare) == 'chat' and """ -async def xmpp_send_status(self, jid): +async def xmpp_send_status_message(self, jid): """ Send status message. @@ -190,7 +192,50 @@ async def xmpp_send_status(self, jid): # ) -async def xmpp_send_update(self, jid, num=None): +async def xmpp_send_pubsub(self, jid_bare): + 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: + 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_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) + await XmppIQ.send(self, iq_create_node) + 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] + for entry in entries: + feed_entry = {'author' : None, + 'authors' : None, + 'category' : None, + 'content' : None, + 'description' : entry[3], + 'href' : entry[2], + 'links' : entry[4], + 'tags' : None, + 'title' : entry[1], + 'type' : None, + 'updated' : entry[7]} + iq_create_entry = XmppPubsub.create_entry( + self, jid_bare, node, feed_entry, feed_version) + await XmppIQ.send(self, iq_create_entry) + ix = entry[0] + await sqlite.mark_as_read(db_file, ix) + + +async def xmpp_send_message(self, jid, num=None): """ Send news items as messages. @@ -272,7 +317,7 @@ async def xmpp_send_update(self, jid, num=None): # TODO Do not refresh task before # verifying that it was completed. - # await start_tasks_xmpp(self, jid, ['status']) + # await start_tasks_xmpp_chat(self, jid, ['status']) # await refresh_task(self, jid, send_update, 'interval') # interval = await initdb( @@ -767,145 +812,164 @@ async def import_opml(db_file, result): return difference -async def add_feed(self, jid_bare, db_file, url): +async def add_feed(self, jid_bare, db_file, url, node): function_name = sys._getframe().f_code.co_name logger.debug('{}: db_file: {} url: {}' .format(function_name, db_file, url)) while True: - exist = sqlite.get_feed_id_and_name(db_file, url) - if not exist: - result = await fetch.http(url) - message = result['message'] - status_code = result['status_code'] - if not result['error']: - document = result['content'] - feed = parse(document) - # if is_feed(url, feed): - if is_feed(feed): - if "title" in feed["feed"].keys(): - title = feed["feed"]["title"] - else: - title = urlsplit(url).netloc - if "language" in feed["feed"].keys(): - language = feed["feed"]["language"] - else: - language = '' - if "encoding" in feed.keys(): - encoding = feed["encoding"] - else: - encoding = '' - if "updated_parsed" in feed["feed"].keys(): - updated = feed["feed"]["updated_parsed"] - try: - updated = dt.convert_struct_time_to_iso8601(updated) - except: + exist_feed = sqlite.get_feed_id_and_name(db_file, url) + if not exist_feed: + exist_node = sqlite.check_node_exist(db_file, node) + if not exist_node: + result = await fetch.http(url) + message = result['message'] + status_code = result['status_code'] + if not result['error']: + document = result['content'] + feed = parse(document) + # if is_feed(url, feed): + if is_feed(feed): + if "title" in feed["feed"].keys(): + title = feed["feed"]["title"] + else: + title = urlsplit(url).netloc + if "language" in feed["feed"].keys(): + language = feed["feed"]["language"] + else: + language = '' + if "encoding" in feed.keys(): + encoding = feed["encoding"] + else: + encoding = '' + if "updated_parsed" in feed["feed"].keys(): + updated = feed["feed"]["updated_parsed"] + try: + updated = dt.convert_struct_time_to_iso8601(updated) + except: + updated = '' + else: updated = '' - else: - updated = '' - version = feed["version"] - entries = len(feed["entries"]) - await sqlite.insert_feed(db_file, url, - title=title, - entries=entries, - version=version, - encoding=encoding, - language=language, - status_code=status_code, - updated=updated) - await scan(self, jid_bare, db_file, url) - old = Config.get_setting_value(self.settings, jid_bare, 'old') - feed_id = sqlite.get_feed_id(db_file, url) - feed_id = feed_id[0] - if not old: - await sqlite.mark_feed_as_read(db_file, feed_id) - result_final = {'link' : url, - 'index' : feed_id, - 'name' : title, - 'code' : status_code, - 'error' : False, - 'exist' : False} - break - # NOTE This elif statement be unnecessary - # when feedparser be supporting json feed. - elif is_feed_json(document): - feed = json.loads(document) - if "title" in feed.keys(): - title = feed["title"] - else: - title = urlsplit(url).netloc - if "language" in feed.keys(): - language = feed["language"] - else: - language = '' - if "encoding" in feed.keys(): - encoding = feed["encoding"] - else: - encoding = '' - if "date_published" in feed.keys(): - updated = feed["date_published"] - try: - updated = dt.convert_struct_time_to_iso8601(updated) - except: - updated = '' - else: - updated = '' - version = 'json' + feed["version"].split('/').pop() - entries = len(feed["items"]) - await sqlite.insert_feed(db_file, url, - title=title, - entries=entries, - version=version, - encoding=encoding, - language=language, - status_code=status_code, - updated=updated) - await scan_json(self, jid_bare, db_file, url) - old = Config.get_setting_value(self.settings, jid_bare, 'old') - if not old: + version = feed["version"] + entries = len(feed["entries"]) + await sqlite.insert_feed(db_file, url, title, node, + entries=entries, + version=version, + encoding=encoding, + language=language, + status_code=status_code, + updated=updated) + await scan(self, jid_bare, db_file, url) + old = Config.get_setting_value(self.settings, jid_bare, 'old') feed_id = sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] - await sqlite.mark_feed_as_read(db_file, feed_id) - result_final = {'link' : url, - 'index' : feed_id, - 'name' : title, - 'code' : status_code, - 'error' : False, - 'exist' : False} - break - else: - # NOTE Do not be tempted to return a compact dictionary. - # That is, dictionary within dictionary - # Return multiple dictionaries in a list or tuple. - result = await crawl.probe_page(url, document) - if not result: - # Get out of the loop with dict indicating error. + if not old: + await sqlite.mark_feed_as_read(db_file, feed_id) result_final = {'link' : url, - 'index' : None, - 'name' : None, + 'index' : feed_id, + 'name' : title, 'code' : status_code, - 'error' : True, + 'error' : False, 'message': message, - 'exist' : False} + 'exist' : False, + 'node' : None} break - elif isinstance(result, list): - # Get out of the loop and deliver a list of dicts. - result_final = result + # NOTE This elif statement be unnecessary + # when feedparser be supporting json feed. + elif is_feed_json(document): + feed = json.loads(document) + if "title" in feed.keys(): + title = feed["title"] + else: + title = urlsplit(url).netloc + if "language" in feed.keys(): + language = feed["language"] + else: + language = '' + if "encoding" in feed.keys(): + encoding = feed["encoding"] + else: + encoding = '' + if "date_published" in feed.keys(): + updated = feed["date_published"] + try: + updated = dt.convert_struct_time_to_iso8601(updated) + except: + updated = '' + else: + updated = '' + version = 'json' + feed["version"].split('/').pop() + entries = len(feed["items"]) + await sqlite.insert_feed(db_file, url, title, node, + entries=entries, + version=version, + encoding=encoding, + language=language, + status_code=status_code, + updated=updated) + await scan_json(self, jid_bare, db_file, url) + old = Config.get_setting_value(self.settings, jid_bare, 'old') + if not old: + feed_id = sqlite.get_feed_id(db_file, url) + feed_id = feed_id[0] + await sqlite.mark_feed_as_read(db_file, feed_id) + result_final = {'link' : url, + 'index' : feed_id, + 'name' : title, + 'code' : status_code, + 'error' : False, + 'message': message, + 'exist' : False, + 'node' : None} break else: - # Go back up to the while loop and try again. - url = result['link'] + # NOTE Do not be tempted to return a compact dictionary. + # That is, dictionary within dictionary + # Return multiple dictionaries in a list or tuple. + result = await crawl.probe_page(url, document) + if not result: + # Get out of the loop with dict indicating error. + result_final = {'link' : url, + 'index' : None, + 'name' : None, + 'code' : status_code, + 'error' : True, + 'message': message, + 'exist' : False, + 'node' : None} + break + elif isinstance(result, list): + # Get out of the loop and deliver a list of dicts. + result_final = result + break + else: + # Go back up to the while loop and try again. + url = result['link'] + else: + result_final = {'link' : url, + 'index' : None, + 'name' : None, + 'code' : status_code, + 'error' : True, + 'message': message, + 'exist' : False, + 'node' : None} + break else: + ix = exist_node[1] + node = exist_node[2] + message = 'Node is already allocated.' result_final = {'link' : url, - 'index' : None, + 'index' : ix, 'name' : None, - 'code' : status_code, - 'error' : True, + 'code' : None, + 'error' : False, 'message': message, - 'exist' : False} + 'exist' : False, + 'node' : node} break else: - ix = exist[0] - name = exist[1] + ix = exist_feed[0] + name = exist_feed[1] message = 'URL already exist.' result_final = {'link' : url, 'index' : ix, @@ -913,7 +977,8 @@ async def add_feed(self, jid_bare, db_file, url): 'code' : None, 'error' : False, 'message': message, - 'exist' : True} + 'exist' : True, + 'node' : None} break return result_final diff --git a/slixfeed/assets/about.toml b/slixfeed/assets/about.toml index 818e2cb..86588ef 100644 --- a/slixfeed/assets/about.toml +++ b/slixfeed/assets/about.toml @@ -174,6 +174,16 @@ newsfeeds. interface = "Groupchat" url = "https://salsa.debian.org/mdosch/feed-to-muc" +[[friends]] +name = "Jabber RSS Transport" +info = [""" +Jabber RSS Transport is one of the possibilities to read news in Jabber \ +via RSS, which allows you not to use a separate program, but to receive \ +them directly in your favorite Jabber client. +"""] +interface = "Chat" +url = "https://jabberworld.info/Jabber_RSS_Transport" + [[friends]] name = "JabRSS by Christof" info = [""" @@ -648,15 +658,15 @@ name = "Poezio" info = "XMPP client for console" url = "https://poez.io" -# [[clients]] -# name = "Psi" -# info = "XMPP client for desktop" -# url = "https://psi-im.org" +[[clients]] +name = "Psi" +info = "XMPP client for desktop" +url = "https://psi-im.org" -# [[clients]] -# name = "Psi+" -# info = "XMPP client for desktop" -# url = "https://psi-plus.com" +[[clients]] +name = "Psi+" +info = "XMPP client for desktop" +url = "https://psi-plus.com" # [[clients]] # name = "Swift" diff --git a/slixfeed/assets/feeds.toml b/slixfeed/assets/feeds.toml index ff87098..3522f0d 100644 --- a/slixfeed/assets/feeds.toml +++ b/slixfeed/assets/feeds.toml @@ -58,6 +58,18 @@ name = "Berlin XMPP Meetup" link = "https://mov.im/?feed/pubsub.movim.eu/berlin-xmpp-meetup" tags = ["event", "germany", "xmpp"] +[[feeds]] +lang = "de-de" +name = "blog | hasecke" +link = "https://www.hasecke.eu/index.xml" +tags = ["linux", "p2p", "software", "technology"] + +[[feeds]] +lang = "de-de" +name = "heise online News" +link = "https://www.heise.de/rss/heise-atom.xml" +tags = ["computer", "industry", "electronics", "technology"] + [[feeds]] lang = "de-de" name = "CCC Event Blog" @@ -214,6 +226,12 @@ name = "La Quadrature du Net" link = "https://www.laquadrature.net/en/feed/" tags = ["news", "politics", "privacy", "surveillance"] +[[feeds]] +lang = "en-gb" +name = "op-co.de blog" +link = "https://op-co.de/blog/index.rss" +tags = ["code", "germany", "jabber", "mastodon", "telecommunication", "xmpp"] + [[feeds]] lang = "en-gb" name = "Pimux XMPP News" @@ -408,9 +426,9 @@ tags = ["health"] [[feeds]] lang = "en-us" -name = "Monal IM" -link = "https://monal-im.org/index.xml" -tags = ["iphone", "xmpp"] +name = "modernity" +link = "https://modernity.news/feed/" +tags = ["culture", "news", "politics", "usa"] [[feeds]] lang = "en-us" @@ -418,6 +436,12 @@ name = "Mom on a Mission" link = "https://www.mom-on-a-mission.blog/all-posts?format=rss" tags = ["family", "farming", "food", "gardening", "survival"] +[[feeds]] +lang = "en-us" +name = "Monal IM" +link = "https://monal-im.org/index.xml" +tags = ["iphone", "xmpp"] + [[feeds]] lang = "en-us" name = "NLnet News" @@ -778,18 +802,6 @@ name = "שיחה מקומית" link = "https://www.mekomit.co.il/feed/" tags = ["news", "politics"] -[[feeds]] -lang = "it-it" -name = "Diggita / Prima Pagina" -link = "https://diggita.com/rss.php" -tags = ["computer", "culture", "food", "technology"] - -[[feeds]] -lang = "it-it" -name = "Feddit.it" -link = "https://feddit.it/feeds/local.xml?sort=Active" -tags = ["fediverse", "forum"] - [[feeds]] lang = "it-it" name = "A sysadmin's (mis)adventures" @@ -802,12 +814,30 @@ name = "Avvocato a Roma" link = "https://www.studiosabatino.it/feed/" tags = ["law"] +[[feeds]] +lang = "it-it" +name = "Diggita / Prima Pagina" +link = "https://diggita.com/rss.php" +tags = ["computer", "culture", "food", "technology"] + [[feeds]] lang = "it-it" name = "Disroot Blog" link = "https://disroot.org/it/blog.atom" tags = ["decentralization", "privacy"] +[[feeds]] +lang = "it-it" +name = "Feddit.it" +link = "https://feddit.it/feeds/local.xml?sort=Active" +tags = ["fediverse", "forum"] + +[[feeds]] +lang = "it-it" +name = "Italian XMPP Happy Hour - Podcast" +link = "https://podcast.xmpp-it.net/api/v1/channels/xmpphappyhour/rss" +tags = ["decentralization", "privacy", "technology", "telecommunication", "xmpp"] + [[feeds]] lang = "it-it" name = "LinuxTrent" diff --git a/slixfeed/sqlite.py b/slixfeed/sqlite.py index d819f4c..e316a8a 100644 --- a/slixfeed/sqlite.py +++ b/slixfeed/sqlite.py @@ -167,6 +167,19 @@ def create_tables(db_file): ); """ ) + feeds_pubsub_table_sql = ( + """ + CREATE TABLE IF NOT EXISTS feeds_pubsub ( + id INTEGER NOT NULL, + feed_id INTEGER NOT NULL UNIQUE, + node TEXT NOT NULL UNIQUE, + FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id") + ON UPDATE CASCADE + ON DELETE CASCADE, + PRIMARY KEY (id) + ); + """ + ) feeds_rules_table_sql = ( """ CREATE TABLE IF NOT EXISTS feeds_rules ( @@ -287,6 +300,7 @@ def create_tables(db_file): cur.execute(feeds_table_sql) cur.execute(feeds_state_table_sql) cur.execute(feeds_properties_table_sql) + cur.execute(feeds_pubsub_table_sql) cur.execute(feeds_rules_table_sql) cur.execute(feeds_tags_table_sql) cur.execute(filters_table_sql) @@ -390,6 +404,7 @@ async def add_metadata(db_file): feed_id = ix[0] insert_feed_status(cur, feed_id) insert_feed_properties(cur, feed_id) + insert_feed_pubsub(cur, feed_id) def insert_feed_status(cur, feed_id): @@ -452,7 +467,37 @@ def insert_feed_properties(cur, feed_id): logger.error(e) -async def insert_feed(db_file, url, title=None, entries=None, version=None, +def insert_feed_pubsub(cur, feed_id): + """ + Set feed pubsub. + + Parameters + ---------- + cur : object + Cursor object. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: feed_id: {}' + .format(function_name, feed_id)) + sql = ( + """ + INSERT + INTO feeds_pubsub( + feed_id) + VALUES( + ?) + """ + ) + par = (feed_id,) + try: + cur.execute(sql, par) + except IntegrityError as e: + logger.warning( + "Skipping feed_id {} for table feeds_pubsub".format(feed_id)) + logger.error(e) + + +async def insert_feed(db_file, url, title, node, entries=None, version=None, encoding=None, language=None, status_code=None, updated=None): """ @@ -464,8 +509,10 @@ async def insert_feed(db_file, url, title=None, entries=None, version=None, Path to database file. url : str URL. - title : str, optional - Feed title. The default is None. + title : str + Feed title. + node : str + Feed Node. entries : int, optional Number of entries. The default is None. version : str, optional @@ -533,6 +580,19 @@ async def insert_feed(db_file, url, title=None, entries=None, version=None, feed_id, entries, version, encoding, language ) cur.execute(sql, par) + sql = ( + """ + INSERT + INTO feeds_pubsub( + feed_id, node) + VALUES( + ?, ?) + """ + ) + par = ( + feed_id, node + ) + cur.execute(sql, par) async def insert_feed_(db_file, url, title=None, entries=None, version=None, @@ -699,7 +759,8 @@ def get_feeds_by_tag_id(db_file, tag_id): FROM feeds INNER JOIN feeds_tags ON feeds.id = feeds_tags.feed_id INNER JOIN tags ON tags.id = feeds_tags.tag_id - WHERE tags.id = ?; + WHERE tags.id = ? + ORDER BY feeds.name; """ ) par = (tag_id,) @@ -734,7 +795,8 @@ def get_tags_by_feed_id(db_file, feed_id): FROM tags INNER JOIN feeds_tags ON tags.id = feeds_tags.tag_id INNER JOIN feeds ON feeds.id = feeds_tags.feed_id - WHERE feeds.id = ?; + WHERE feeds.id = ? + ORDER BY tags.tag; """ ) par = (feed_id,) @@ -777,6 +839,109 @@ async def set_feed_id_and_tag_id(db_file, feed_id, tag_id): cur.execute(sql, par) +def get_feed_properties(db_file, feed_id): + """ + Get properties of given feed. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed ID. + + Returns + ------- + node : str + Node name. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: db_file: {} feed_id: {}' + .format(function_name, db_file, feed_id)) + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT * + FROM feeds_properties + WHERE feed_id = ? + """ + ) + par = (feed_id,) + name = cur.execute(sql, par).fetchone() + return name + + +def get_node_name(db_file, feed_id): + """ + Get name of given node. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed ID. + + Returns + ------- + node : str + Node name. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: db_file: {} feed_id: {}' + .format(function_name, db_file, feed_id)) + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT node + FROM feeds_pubsub + WHERE feed_id = ? + """ + ) + par = (feed_id,) + name = cur.execute(sql, par).fetchone() + return name + + +def check_node_exist(db_file, node_name): + """ + Check whether node exist. + + Parameters + ---------- + db_file : str + Path to database file. + node_name : str + Node name. + + Returns + ------- + id : str + ID. + feed_id : str + Feed ID. + node : str + Node name. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: db_file: {} node_name: {}' + .format(function_name, db_file, node_name)) + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT id, feed_id, node + FROM feeds_pubsub + WHERE node = ? + """ + ) + par = (node_name,) + name = cur.execute(sql, par).fetchone() + return name + + def get_tag_id(db_file, tag_name): """ Get ID of given tag. Check whether tag exist. @@ -1327,6 +1492,62 @@ async def mark_entry_as_read(cur, ix): cur.execute(sql, par) +def get_status_information_of_feed(db_file, feed_id): + """ + Get status information of given feed. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed Id. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: db_file: {} feed_id: {}' + .format(function_name, db_file, feed_id)) + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT * + FROM feeds_state + WHERE feed_id = ? + """ + ) + par = (feed_id,) + count = cur.execute(sql, par).fetchone() + return count + + +def get_unread_entries_of_feed(db_file, feed_id): + """ + Get entries of given feed. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed Id. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: db_file: {} feed_id: {}' + .format(function_name, db_file, feed_id)) + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT * + FROM entries + WHERE read = 0 AND feed_id = ? + """ + ) + par = (feed_id,) + count = cur.execute(sql, par).fetchall() + return count + + def get_number_of_unread_entries_by_feed(db_file, feed_id): """ Count entries of given feed. @@ -2157,6 +2378,37 @@ def get_feeds_by_enabled_state(db_file, enabled_state): return result +def get_feeds_and_enabled_state(db_file): + """ + Select table feeds and join column enabled. + + Parameters + ---------- + db_file : str + Path to database file. + + Returns + ------- + result : tuple + List of URLs. + """ + function_name = sys._getframe().f_code.co_name + logger.debug('{}: db_file: {}' + .format(function_name, db_file)) + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT feeds.*, feeds_state.enabled + FROM feeds + INNER JOIN feeds_state ON feeds.id = feeds_state.feed_id + ORDER BY feeds.name ASC + """ + ) + result = cur.execute(sql).fetchall() + return result + + def get_active_feeds_url(db_file): """ Query table feeds for active URLs. @@ -2211,6 +2463,7 @@ def get_tags(db_file): """ SELECT tag, id FROM tags + ORDER BY tag """ ) result = cur.execute(sql).fetchall() @@ -2245,6 +2498,7 @@ def get_feeds(db_file): """ SELECT id, name, url FROM feeds + ORDER BY name """ ) result = cur.execute(sql).fetchall() diff --git a/slixfeed/task.py b/slixfeed/task.py index 3a77b27..facb8aa 100644 --- a/slixfeed/task.py +++ b/slixfeed/task.py @@ -3,6 +3,19 @@ """ +IMPORTANT CONSIDERATION + +This file appears to be redundant and may be replaced by a dict handler that +would match task keyword to functions. + +Or use it as a class Task + +tasks_xmpp_chat = {"check" : check_updates, + "status" : task_status_message, + "interval" : task_message} +tasks_xmpp_pubsub = {"check" : check_updates, + "pubsub" : task_pubsub} + TODO 1) Deprecate "add" (see above) and make it interactive. @@ -86,6 +99,17 @@ loop = asyncio.get_event_loop() # task_ping = asyncio.create_task(ping(self, jid=None)) +class Task: + + def start(self, jid_full, tasks=None): + asyncio.create_task() + + def cancel(self, jid_full, tasks=None): + pass + + + + def task_ping(self): # global task_ping_instance try: @@ -109,7 +133,49 @@ await taskhandler.start_tasks( ) """ -async def start_tasks_xmpp(self, jid_bare, tasks=None): +async def start_tasks_xmpp_pubsub(self, jid_bare, tasks=None): + try: + self.task_manager[jid_bare] + except KeyError as e: + self.task_manager[jid_bare] = {} + logging.debug('KeyError:', str(e)) + logging.info('Creating new task manager for JID {}'.format(jid_bare)) + if not tasks: + tasks = ['check', 'publish'] + logging.info('Stopping tasks {} for JID {}'.format(tasks, jid_bare)) + for task in tasks: + # if self.task_manager[jid][task]: + try: + self.task_manager[jid_bare][task].cancel() + except: + logging.info('No task {} for JID {} (start_tasks_xmpp_chat)' + .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( + task_publish(self, jid_bare)) + case 'check': + self.task_manager[jid_bare]['check'] = asyncio.create_task( + check_updates(self, jid_bare)) + + +async def task_publish(self, jid_bare): + jid_file = jid_bare.replace('/', '_') + db_file = config.get_pathname_to_database(jid_file) + if jid_bare not in self.settings: + Config.add_settings_jid(self.settings, jid_bare, db_file) + while True: + await action.xmpp_send_pubsub(self, jid_bare) + await asyncio.sleep(60 * 180) + + +async def start_tasks_xmpp_chat(self, jid_bare, tasks=None): """ NOTE @@ -133,7 +199,7 @@ async def start_tasks_xmpp(self, jid_bare, tasks=None): try: self.task_manager[jid_bare][task].cancel() except: - logging.info('No task {} for JID {} (start_tasks_xmpp)' + logging.info('No task {} for JID {} (start_tasks_xmpp_chat)' .format(task, jid_bare)) logging.info('Starting tasks {} for JID {}'.format(tasks, jid_bare)) for task in tasks: @@ -147,10 +213,10 @@ async def start_tasks_xmpp(self, jid_bare, tasks=None): check_updates(self, jid_bare)) case 'status': self.task_manager[jid_bare]['status'] = asyncio.create_task( - task_status(self, jid_bare)) + task_status_message(self, jid_bare)) case 'interval': self.task_manager[jid_bare]['interval'] = asyncio.create_task( - task_send(self, jid_bare)) + task_message(self, jid_bare)) # for task in self.task_manager[jid].values(): # print("task_manager[jid].values()") # print(self.task_manager[jid].values()) @@ -162,12 +228,12 @@ async def start_tasks_xmpp(self, jid_bare, tasks=None): # await task -async def task_status(self, jid): - await action.xmpp_send_status(self, jid) - refresh_task(self, jid, task_status, 'status', '90') +async def task_status_message(self, jid): + await action.xmpp_send_status_message(self, jid) + refresh_task(self, jid, task_status_message, 'status', '90') -async def task_send(self, jid_bare): +async def task_message(self, jid_bare): jid_file = jid_bare.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) if jid_bare not in self.settings: @@ -195,12 +261,12 @@ async def task_send(self, jid_bare): await sqlite.update_last_update_time(db_file) else: await sqlite.set_last_update_time(db_file) - await action.xmpp_send_update(self, jid_bare) - refresh_task(self, jid_bare, task_send, 'interval') - await start_tasks_xmpp(self, jid_bare, ['status']) + await action.xmpp_send_message(self, jid_bare) + refresh_task(self, jid_bare, task_message, 'interval') + await start_tasks_xmpp_chat(self, jid_bare, ['status']) -def clean_tasks_xmpp(self, jid, tasks=None): +def clean_tasks_xmpp_chat(self, jid, tasks=None): if not tasks: tasks = ['interval', 'status', 'check'] logging.info('Stopping tasks {} for JID {}'.format(tasks, jid)) diff --git a/slixfeed/url.py b/slixfeed/url.py index 03869eb..aa628d2 100644 --- a/slixfeed/url.py +++ b/slixfeed/url.py @@ -45,7 +45,9 @@ from urllib.parse import ( def get_hostname(url): parted_url = urlsplit(url) - return parted_url.netloc + hostname = parted_url.netloc + if hostname.startswith('www.'): hostname = hostname.replace('www.', '') + return hostname def replace_hostname(url, url_type): diff --git a/slixfeed/version.py b/slixfeed/version.py index df3a761..a912d6e 100644 --- a/slixfeed/version.py +++ b/slixfeed/version.py @@ -1,2 +1,2 @@ -__version__ = '0.1.47' -__version_info__ = (0, 1, 47) +__version__ = '0.1.48' +__version_info__ = (0, 1, 48) diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index 103f6ab..fe85b4e 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -217,7 +217,7 @@ class Slixfeed(slixmpp.ClientXMPP): inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) - XmppGroupchat.join(self, inviter, muc_jid) + await XmppGroupchat.join(self, inviter, 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' @@ -240,7 +240,7 @@ class Slixfeed(slixmpp.ClientXMPP): inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) - XmppGroupchat.join(self, inviter, muc_jid) + await XmppGroupchat.join(self, inviter, 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,7 +285,14 @@ class Slixfeed(slixmpp.ClientXMPP): # self.send_presence() await self.get_roster() bookmarks = await self.plugin['xep_0048'].get_bookmarks() - XmppGroupchat.autojoin(self, bookmarks) + await XmppGroupchat.autojoin(self, bookmarks) + jids = await XmppPubsub.get_pubsub_services(self) + for jid_bare in jids: + if jid_bare not in self.settings: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + Config.add_settings_jid(self.settings, jid_bare, db_file) + await task.start_tasks_xmpp_pubsub(self, jid_bare) # XmppCommand.adhoc_commands(self) self.adhoc_commands() # self.service_reactions() @@ -305,7 +312,7 @@ class Slixfeed(slixmpp.ClientXMPP): profile.set_identity(self, 'client') self['xep_0115'].update_caps() bookmarks = await self.plugin['xep_0048'].get_bookmarks() - XmppGroupchat.autojoin(self, bookmarks) + await XmppGroupchat.autojoin(self, bookmarks) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -386,9 +393,9 @@ class Slixfeed(slixmpp.ClientXMPP): return if presence['show'] in ('away', 'dnd', 'xa'): key_list = ['interval'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) key_list = ['status', 'check'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -455,7 +462,7 @@ class Slixfeed(slixmpp.ClientXMPP): # FIXME TODO Find out what is the source responsible for a couple presences with empty message # NOTE This is a temporary solution await asyncio.sleep(10) - await task.start_tasks_xmpp(self, jid_bare) + await task.start_tasks_xmpp_chat(self, jid_bare) self.add_event_handler("presence_unavailable", self.on_presence_unavailable) time_end = time.time() @@ -494,7 +501,7 @@ class Slixfeed(slixmpp.ClientXMPP): logger.debug(message_log.format(function_name, jid_full)) jid_bare = presence['from'].bare # await task.stop_tasks(self, jid) - task.clean_tasks_xmpp(self, jid_bare) + task.clean_tasks_xmpp_chat(self, jid_bare) # NOTE Albeit nice to ~have~ see, this would constantly # send presence messages to server to no end. @@ -521,7 +528,7 @@ class Slixfeed(slixmpp.ClientXMPP): message_log = '{}: jid_full: {}' logger.debug(message_log.format(function_name, jid_full)) jid_bare = presence["from"].bare - task.clean_tasks_xmpp(self, jid_bare) + task.clean_tasks_xmpp_chat(self, jid_bare) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -546,10 +553,10 @@ class Slixfeed(slixmpp.ClientXMPP): # NOTE: Required for Cheogram # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) await asyncio.sleep(5) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 10: logger.warning('{} (time: {})'.format(function_name, @@ -567,7 +574,7 @@ class Slixfeed(slixmpp.ClientXMPP): # NOTE: Required for Cheogram # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) await asyncio.sleep(5) status_message = ('💡 Send "help" for manual, or "info" for ' 'information.') @@ -588,9 +595,9 @@ class Slixfeed(slixmpp.ClientXMPP): if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -607,9 +614,9 @@ class Slixfeed(slixmpp.ClientXMPP): if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -626,9 +633,9 @@ class Slixfeed(slixmpp.ClientXMPP): if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -695,11 +702,8 @@ class Slixfeed(slixmpp.ClientXMPP): name='🪶️ Subscribe', handler=self._handle_subscription_add) self['xep_0050'].add_command(node='publish', - name='📻 Publish', - handler=self._handle_pubsub_add) - self['xep_0050'].add_command(node='post', - name='📣️ Post', - handler=self._handle_post) + name='📣️ Publish', + handler=self._handle_publish) self['xep_0050'].add_command(node='recent', name='📰️ Browse', handler=self._handle_recent) @@ -737,66 +741,155 @@ class Slixfeed(slixmpp.ClientXMPP): # Special interface # http://jabber.org/protocol/commands#actions - async def _handle_post(self, iq, session): + async def _handle_publish(self, iq, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.debug('{}: jid_full: {}' + .format(function_name, jid_full)) jid_bare = session['from'].bare - if not is_operator(self, jid_bare): - text_warn = 'PubSub is not available.' + if is_operator(self, jid_bare): + form = self['xep_0004'].make_form('form', 'PubSub') + form['instructions'] = 'Manage nodes and publish news into nodes.' + options = form.add_field(desc=('Send a set of selected posts or ' + 'set a new subscription.'), + ftype='list-single', + label='Choose', + required=True, + var='option') + # options.addOption('Manage nodes', 'nodes') + # options.addOption('Manage subscriptions', 'manage') + options.addOption('Post a selection', 'post') + options.addOption('Publish a subscription', 'publish') + session['allow_prev'] = False + session['has_next'] = True + session['next'] = self._handle_publish_select + session['prev'] = None + session['payload'] = form + else: + text_warn = 'This resource is restricted to operators.' session['notes'] = [['warn', text_warn]] - return session - form = self['xep_0004'].make_form('form', 'Post') - form['instructions'] = ('In order to post to PubSub, you will have to ' - 'choose a PubSub Jabber ID (e.g. {}) and ' - 'Slixfeed has to be allowed to publish into ' - 'it.' - .format(self.boundjid.bare)) - form.add_field(var='url', - ftype='text-single', - label='URL', - desc='Enter a subscription URL.', - value='http://', - required=True) - # options = form.add_field(var='jid', - # ftype='list-single', - # label='PubSub', - # desc='Select a PubSub Jabber ID.', - # value=self.boundjid.bare, - # required=True) - # iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) - # for item in iq['disco_items']['items']: - # jid = item[0] - # if item[1]: name = item[1] - # elif item[2]: name = item[2] - # else: name = jid - # options.addOption(jid, name) - form.add_field(var='jid', - ftype='text-single', - label='PubSub', - desc='Enter a PubSub Jabber ID.', - value=self.boundjid.bare, - # value='pubsub.' + self.boundjid.host, - required=True) - form.add_field(var='node', - ftype='text-single', - label='Node', - desc=('Enter a node to publish to (The default value ' - 'is "urn:xmpp:microblog:0" which is allocated to ' - 'each Jabber ID in the Movim system).'), - value='urn:xmpp:microblog:0', - required=False) - options = form.add_field(var='xep', - ftype='list-single', - label='Protocol', - desc='Select XMPP Extension Protocol.', - value='0060', - required=True) - options.addOption('XEP-0060: Publish-Subscribe', '0060') - options.addOption('XEP-0277: Microblogging over XMPP', '0277') - options.addOption('XEP-0472: Pubsub Social Feed', '0472') - session['allow_prev'] = False + return session + + async def _handle_publish_select(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.debug('{}: jid_full: {}' + .format(function_name, jid_full)) + values = payload['values'] + match values['option']: + case 'publish': + form = self['xep_0004'].make_form('form', 'Publish') + form['instructions'] = ('Choose a PubSub Jabber ID and verify ' + 'that Slixfeed has the necessary ' + 'permissions to publish into it.') + form.add_field(var='subscription', + # TODO Make it possible to add several subscriptions at once; + # Similarly to BitTorrent trackers list + # ftype='text-multi', + # label='Subscription URLs', + # desc=('Add subscriptions one time per ' + # 'subscription.'), + ftype='text-single', + label='URL', + desc='Enter a subscription URL.', + value='http://', + required=True) + options = form.add_field(var='jid', + ftype='list-single', + label='PubSub', + desc='Select a PubSub Service.', + value=self.boundjid.bare, + required=True) + options.addOption(self.boundjid.bare, self.boundjid.bare) + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid = item[0] + if item[1]: name = item[1] + elif item[2]: name = item[2] + else: name = jid + options.addOption(jid, name) + # form.add_field(var='jid', + # ftype='text-single', + # label='PubSub', + # desc='Enter a PubSub Jabber ID.', + # value=self.boundjid.bare, + # # value='pubsub.' + self.boundjid.host, + # required=True) + form.add_field(var='node', + ftype='text-single', + label='Node', + desc='Enter a node to publish to.') + # options = form.add_field(var='xep', + # ftype='list-single', + # label='Protocol', + # desc='Select XMPP Extension Protocol.', + # value='0060', + # required=True) + # 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['payload'] = form + case 'post': + form = self['xep_0004'].make_form('form', 'Post') + form['instructions'] = ('Choose a PubSub Jabber ID and verify ' + 'that Slixfeed has the necessary ' + 'permissions to publish into it.') + form.add_field(var='url', + ftype='text-single', + label='URL', + desc='Enter a subscription URL.', + value='http://', + required=True) + options = form.add_field(var='jid', + ftype='list-single', + label='PubSub', + desc='Select a PubSub Service.', + value=self.boundjid.bare, + required=True) + options.addOption(self.boundjid.bare, self.boundjid.bare) + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid = item[0] + if item[1]: name = item[1] + elif item[2]: name = item[2] + else: name = jid + options.addOption(jid, name) + # form.add_field(var='jid', + # ftype='text-single', + # label='PubSub', + # desc='Enter a PubSub Jabber ID.', + # value=self.boundjid.bare, + # # value='pubsub.' + self.boundjid.host, + # required=True) + form.add_field(var='node', + ftype='text-single', + label='Node', + desc='Enter a node to publish to.') + # options = form.add_field(var='xep', + # ftype='list-single', + # label='Protocol', + # desc='Select XMPP Extension Protocol.', + # value='0060', + # required=True) + # 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_preview + session['payload'] = form + session['allow_prev'] = True session['has_next'] = True - session['next'] = self._handle_preview - session['prev'] = None - session['payload'] = form + session['prev'] = self._handle_publish return session async def _handle_preview(self, payload, session): @@ -820,6 +913,11 @@ class Slixfeed(slixmpp.ClientXMPP): node = values['node'] url = values['url'] xep = values['xep'] + if not node: + if jid == self.boundjid.bare: + node = 'urn:xmpp:microblog:0' + else: + node = uri.get_hostname(url) form = self['xep_0004'].make_form('form', 'Publish') while True: result = await fetch.http(url) @@ -838,7 +936,7 @@ class Slixfeed(slixmpp.ClientXMPP): if "title" in feed["feed"].keys(): title = feed["feed"]["title"] else: - title = url + title = uri.get_hostname(url) entries = feed.entries entry_ix = 0 for entry in entries: @@ -860,22 +958,21 @@ class Slixfeed(slixmpp.ClientXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_post_complete - session['prev'] = self._handle_post + session['prev'] = self._handle_publish session['payload'] = form break else: result = await crawl.probe_page(url, document) if isinstance(result, list): + results = result form['instructions'] = ('Discovered {} subscriptions ' - 'from the given URL. Please ' - 'choose a subscription.' - .format(len(result))) + 'for {}' + .format(len(results), url)) options = form.add_field(var='url', ftype='list-single', label='Feeds', desc='Select a feed.', required=True) - results = result for result in results: title = result['name'] url = result['link'] @@ -884,7 +981,7 @@ class Slixfeed(slixmpp.ClientXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_preview - session['prev'] = self._handle_post + session['prev'] = self._handle_publish session['payload'] = form break else: @@ -985,7 +1082,7 @@ class Slixfeed(slixmpp.ClientXMPP): form['instructions'] = ('Displaying information\nJabber ID {}' .format(jid_bare)) form.add_field(ftype='fixed', - value='News') + label='News') feeds_all = str(sqlite.get_number_of_items(db_file, 'feeds')) form.add_field(label='Subscriptions', ftype='text-single', @@ -1005,7 +1102,7 @@ class Slixfeed(slixmpp.ClientXMPP): ftype='text-single', value=unread) form.add_field(ftype='fixed', - value='Options') + label='Options') key_archive = Config.get_setting_value(self.settings, jid_bare, 'archive') key_archive = str(key_archive) form.add_field(label='Archive', @@ -1059,7 +1156,7 @@ class Slixfeed(slixmpp.ClientXMPP): last_update = 'n/a' next_update = 'n/a' form.add_field(ftype='fixed', - value='Schedule') + label='Schedule') form.add_field(label='Last update', ftype='text-single', value=last_update) @@ -1167,80 +1264,6 @@ class Slixfeed(slixmpp.ClientXMPP): return session - async def _handle_pubsub_add(self, iq, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - jid_bare = session['from'].bare - if not is_operator(self, jid_bare): - text_warn = 'PubSub is not available.' - session['notes'] = [['warn', text_warn]] - return session - chat_type = await get_chat_type(self, jid_bare) - moderator = None - if chat_type == 'groupchat': - moderator = is_moderator(self, jid_bare, jid_full) - if chat_type == 'chat' or moderator: - form = self['xep_0004'].make_form('form', 'Publish') - form['instructions'] = ('In order to publish via PubSub, you will ' - 'have to choose a PubSub Jabber ID (e.g. ' - '{}) and Slixfeed has to be allowed to ' - 'publish into it.' - .format('pubsub.' + self.boundjid.host)) - form.add_field(var='subscription', - # TODO Make it possible to add several subscriptions at once; - # Similarly to BitTorrent trackers list - # ftype='text-multi', - # label='Subscription URLs', - # desc=('Add subscriptions one time per ' - # 'subscription.'), - ftype='text-single', - label='URL', - desc='Enter a subscription URL.', - value='http://', - required=True) - form.add_field(var='jid', - ftype='text-single', - label='PubSub', - desc='Enter a PubSub Jabber ID.', - value=self.boundjid.bare, - required=True) - form.add_field(var='node', - ftype='text-single', - label='Node', - desc=('Enter a node to publish to (The default ' - 'value is "urn:xmpp:microblog:0" which ' - 'is allocated to each Jabber ID in the ' - 'Movim system).'), - value='urn:xmpp:microblog:0', - required=False) - options = form.add_field(var='xep', - ftype='list-single', - label='Protocol', - desc='Select XMPP Extension Protocol.', - value='0060', - required=True) - options.addOption('XEP-0060: Publish-Subscribe', '0060') - options.addOption('XEP-0277: Microblogging over XMPP', '0277') - options.addOption('XEP-0472: Pubsub Social Feed', '0472') - # form.add_field(var='scan', - # ftype='boolean', - # label='Scan', - # desc='Scan URL for validity (recommended).', - # value=True) - session['allow_prev'] = False - session['has_next'] = True - session['next'] = self._handle_subscription_new - session['prev'] = None - session['payload'] = form - else: - text_warn = ('This resource is restricted to moderators of {}.' - .format(jid_bare)) - session['notes'] = [['warn', text_warn]] - return session - - async def _handle_subscription_add(self, iq, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -1269,12 +1292,12 @@ class Slixfeed(slixmpp.ClientXMPP): if is_operator(self, jid_bare): form.add_field(ftype='fixed', label='Subscriber') - form.add_field(var='jid', + form.add_field(desc=('Enter a Jabber ID to add the ' + 'subscription to (The default Jabber ID ' + 'is your own).'), ftype='text-single', label='Jabber ID', - desc=('Enter a Jabber ID to add the ' - 'subscription to (The default Jabber ID ' - 'is your own).')) + var='jid') # form.add_field(var='scan', # ftype='boolean', # label='Scan', @@ -1495,11 +1518,51 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) + form = self['xep_0004'].make_form('form', 'Subscription') + # scan = values['scan'] + values = payload['values'] + node = values['node'] if 'node' in values else None + url = values['subscription'] + jid = values['jid'] jid_bare = session['from'].bare - jid_file = jid_bare + if is_operator(self, jid_bare) and jid: + jid_file = jid[0] if isinstance(jid, list) else jid + form.add_field(var='jid', + ftype='hidden', + value=jid) + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) - # scan = payload['values']['scan'] - url = payload['values']['subscription'] + if node and sqlite.check_node_exist(db_file, node): + form['title'] = 'Conflict' + form['instructions'] = ('Name "{}" already exists. Choose a ' + 'different name.') + form.add_field(desc='Enter a node to publish to.', + ftype='text-single', + label='Node', + value=node, + var='node') + form.add_field(var='subscription', + ftype='hidden', + value=url) + form.add_field(var='node', + ftype='hidden', + value=node) + session['allow_prev'] = False + session['next'] = self._handle_subscription_new + # session['payload'] = None + session['prev'] = None + # elif not node: + # counter = 0 + # hostname = uri.get_hostname(url) + # node = hostname + ':' + str(counter) + # while True: + # if sqlite.check_node_exist(db_file, node): + # counter += 1 + # node = hostname + ':' + str(counter) + # else: + # break + # Several URLs to subscribe if isinstance(url, list) and len(url) > 1: url_count = len(url) urls = url @@ -1507,14 +1570,22 @@ class Slixfeed(slixmpp.ClientXMPP): error_count = 0 exist_count = 0 for url in urls: - result = await action.add_feed(self, jid_bare, db_file, url) + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break + result = await action.add_feed(self, jid_bare, db_file, url, node) if result['error']: error_count += 1 elif result['exist']: exist_count += 1 else: agree_count += 1 - form = self['xep_0004'].make_form('form', 'Subscription') if agree_count: response = ('Added {} new subscription(s) out of {}' .format(agree_count, url_count)) @@ -1531,16 +1602,25 @@ class Slixfeed(slixmpp.ClientXMPP): else: if isinstance(url, list): url = url[0] - result = await action.add_feed(self, jid_bare, db_file, url) + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break + result = await action.add_feed(self, jid_bare, db_file, url, node) + # URL is not a feed and URL has returned to feeds if isinstance(result, list): results = result - form = self['xep_0004'].make_form('form', 'Subscriptions') form['instructions'] = ('Discovered {} subscriptions for {}' .format(len(results), url)) options = form.add_field(var='subscription', ftype='list-multi', label='Subscribe', - desc=('Select subscriptions to add.'), + desc='Select subscriptions to add.', required=True) for result in results: options.addOption(result['name'], result['link']) @@ -1566,23 +1646,32 @@ class Slixfeed(slixmpp.ClientXMPP): session['payload'] = None session['prev'] = self._handle_subscription_add elif result['exist']: - # response = ('News source "{}" is already listed ' - # 'in the subscription list at index ' - # '{}.\n{}'.format(result['name'], result['index'], - # result['link'])) - # session['notes'] = [['warn', response]] # Not supported by Gajim - # session['notes'] = [['info', response]] - form = self['xep_0004'].make_form('form', 'Subscription') - form['instructions'] = ('Subscription is already assigned at index {}.' - '\n' - '{}' - '\n' - '\n' - 'Proceed to edit this subscription' - .format(result['index'], result['name'])) + name = result['name'] + form['instructions'] = ('Subscription "{}" already exist. ' + 'Proceed to edit this subscription.' + .format(name)) + url = result['link'] + feed_id = str(result['index']) + entries = sqlite.get_entries_of_feed(db_file, feed_id) + last_renewed = sqlite.get_status_information_of_feed(db_file, + feed_id) + last_renewed = str(last_renewed[5]) + options = form.add_field(desc='Recent titles from subscription', + ftype='list-multi', + label='Preview') + for entry in entries: + options.addOption(entry[1], entry[2]) + form.add_field(ftype='fixed', + label='Information') + form.add_field(ftype='text-single', + label='Renewed', + value=last_renewed) + form.add_field(ftype='text-single', + label='ID #', + value=feed_id) form.add_field(var='subscription', ftype='hidden', - value=result['link']) + value=url) # NOTE Should we allow "Complete"? # Do all clients provide button "Cancel". session['allow_complete'] = False @@ -1590,28 +1679,35 @@ class Slixfeed(slixmpp.ClientXMPP): session['next'] = self._handle_subscription_editor session['payload'] = form # session['has_next'] = False + # Single URL to subscribe else: - # response = ('News source "{}" has been ' - # 'added to subscription list.\n{}' - # .format(result['name'], result['link'])) - # session['notes'] = [['info', response]] - form = self['xep_0004'].make_form('form', 'Subscription') - # form['instructions'] = ('✅️ News source "{}" has been added to ' - # 'subscription list as index {}' - # '\n\n' - # 'Choose next to continue to subscription ' - # 'editor.' - # .format(result['name'], result['index'])) - form['instructions'] = ('New subscription' - '\n' - '"{}"' - '\n' - '\n' - 'Proceed to edit this subscription' - .format(result['name'])) + print(result) + name = result['name'] + form['instructions'] = ('Subscription "{}" has been added. ' + 'Proceed to edit this subscription.' + .format(name)) + url = result['link'] + feed_id = str(result['index']) + entries = sqlite.get_entries_of_feed(db_file, feed_id) + last_updated = sqlite.get_status_information_of_feed(db_file, + feed_id) + last_updated = str(last_updated[3]) + options = form.add_field(desc='Recent titles from subscription', + ftype='list-multi', + label='Preview') + for entry in entries: + options.addOption(entry[1], entry[2]) + form.add_field(ftype='fixed', + label='Information') + form.add_field(ftype='text-single', + label='Updated', + value=last_updated) + form.add_field(ftype='text-single', + label='ID #', + value=feed_id) form.add_field(var='subscription', ftype='hidden', - value=result['link']) + value=url) session['allow_complete'] = False session['has_next'] = True # session['allow_prev'] = False @@ -1623,71 +1719,27 @@ class Slixfeed(slixmpp.ClientXMPP): return session - async def _handle_subscription_enable(self, payload, session): + async def _handle_subscription_toggle(self, payload, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - form = payload jid_bare = session['from'].bare - jid_file = jid_bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + del values['jid'] + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) - ixs = payload['values']['subscriptions'] - form.add_field(ftype='fixed', - value='Modified subscriptions') - for ix in ixs: - name = sqlite.get_feed_title(db_file, ix) - url = sqlite.get_feed_url(db_file, ix) - await sqlite.set_enabled_status(db_file, ix, 1) - # text = (ix,) + name + url - # text = ' - '.join(text) - name = name[0] if name else 'Subscription #{}'.format(ix) - url = url[0] - text = '{} <{}>'.format(name, url) - form.add_field(var=ix, - ftype='text-single', - label=url, - value=text) - form['type'] = 'result' - form['title'] = 'Done' - form['instructions'] = ('Completed successfully!') - session["has_next"] = False + for key in values: + value = 1 if values[key] else 0 + await sqlite.set_enabled_status(db_file, key, value) + text_note = 'Done.' session['next'] = None - session['payload'] = form - return session - - - async def _handle_subscription_disable(self, payload, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - form = payload - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - ixs = payload['values']['subscriptions'] - form.add_field(ftype='fixed', - value='Modified subscriptions') - for ix in ixs: - name = sqlite.get_feed_title(db_file, ix) - url = sqlite.get_feed_url(db_file, ix) - await sqlite.set_enabled_status(db_file, ix, 0) - # text = (ix,) + name + url - # text = ' - '.join(text) - name = name[0] if name else 'Subscription #{}'.format(ix) - url = url[0] - text = '{} <{}>'.format(name, url) - form.add_field(var=ix, - ftype='text-single', - label=url, - value=text) - form['type'] = 'result' - form['title'] = 'Done' - form['instructions'] = ('Completed successfully!') - session["has_next"] = False - session['next'] = None - session['payload'] = form + session['notes'] = [['info', text_note]] + session['payload'] = None return session @@ -1696,32 +1748,29 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - form = payload jid_bare = session['from'].bare - jid_file = jid_bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + del values['jid'] + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) + subscriptions ='' ixs = payload['values']['subscriptions'] - form.add_field(ftype='fixed', - value='Deleted subscriptions') for ix in ixs: name = sqlite.get_feed_title(db_file, ix) url = sqlite.get_feed_url(db_file, ix) - await sqlite.remove_feed_by_index(db_file, ix) - # text = (ix,) + name + url - # text = ' - '.join(text) name = name[0] if name else 'Subscription #{}'.format(ix) url = url[0] - text = '{} <{}>'.format(name, url) - form.add_field(var=ix, - ftype='text-single', - label=url, - value=text) - form['type'] = 'result' - form['title'] = 'Done' - form['instructions'] = ('Completed successfully!') - session["has_next"] = False + await sqlite.remove_feed_by_index(db_file, ix) + subscriptions += '{}. {}\n{}\n\n'.format(ix, name, url) + text_note = ('The following subscriptions have been deleted:\n\n{}' + .format(subscriptions)) session['next'] = None - session['payload'] = form + session['notes'] = [['info', text_note]] + session['payload'] = None return session @@ -1878,10 +1927,18 @@ class Slixfeed(slixmpp.ClientXMPP): required=True, value='browse') options.addOption('Browse subscriptions', 'browse') - options.addOption('Browse subscriptions by tag', 'tag') - options.addOption('Enable subscriptions', 'enable') - options.addOption('Disable subscriptions', 'disable') + options.addOption('Browse tags', 'tag') options.addOption('Remove subscriptions', 'delete') + options.addOption('Toggle subscriptions', 'toggle') + if is_operator(self, jid_bare): + form.add_field(ftype='fixed', + label='Subscriber') + form.add_field(desc=('Enter a Jabber ID to add the ' + 'subscription to (The default Jabber ID ' + 'is your own).'), + ftype='text-single', + label='Jabber ID', + var='jid') session['payload'] = form session['next'] = self._handle_subscriptions_result session['has_next'] = True @@ -1897,11 +1954,19 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) + values = payload['values'] + jid = values['jid'] jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) form = self['xep_0004'].make_form('form', 'Subscriptions') - match payload['values']['action']: + if is_operator(self, jid_bare) and jid: + jid_file = jid + form.add_field(ftype='hidden', + value=jid, + var='jid') + else: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + match values['action']: case 'browse': form['instructions'] = 'Editing subscriptions' options = form.add_field(var='subscriptions', @@ -1911,7 +1976,7 @@ class Slixfeed(slixmpp.ClientXMPP): desc=('Select a subscription to edit.'), required=True) subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: title = subscription[1] url = subscription[2] @@ -1930,7 +1995,7 @@ class Slixfeed(slixmpp.ClientXMPP): desc=('Select subscriptions to remove.'), required=True) subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: title = subscription[1] ix = str(subscription[0]) @@ -1940,39 +2005,24 @@ class Slixfeed(slixmpp.ClientXMPP): # TODO Refer to confirmation dialog which would display feeds selected session['next'] = self._handle_subscription_del_complete session['allow_complete'] = True - case 'disable': - form['instructions'] = 'Disabling subscriptions' - options = form.add_field(var='subscriptions', - ftype='list-multi', - label='Subscriptions', - desc=('Select subscriptions to disable.'), - required=True) - subscriptions = sqlite.get_feeds_by_enabled_state(db_file, True) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + case 'toggle': + form['instructions'] = 'Toggling subscriptions' + subscriptions = sqlite.get_feeds_and_enabled_state(db_file) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[1] ix = str(subscription[0]) - options.addOption(title, ix) + title = subscription[1] + url = subscription[2] + enabled_state = True if subscription[3] else False + enabled_state = subscription[3] + form.add_field(ftype='boolean', + label=title, + desc=url, + value=enabled_state, + var=ix) session['cancel'] = self._handle_cancel session['has_next'] = False - session['next'] = self._handle_subscription_disable - session['allow_complete'] = True - case 'enable': - form['instructions'] = 'Enabling subscriptions' - options = form.add_field(var='subscriptions', - ftype='list-multi', - label='Subscriptions', - desc=('Select subscriptions to enable.'), - required=True) - subscriptions = sqlite.get_feeds_by_enabled_state(db_file, False) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) - for subscription in subscriptions: - title = subscription[1] - ix = str(subscription[0]) - options.addOption(title, ix) - session['cancel'] = self._handle_cancel - session['has_next'] = False - session['next'] = self._handle_subscription_enable + session['next'] = self._handle_subscription_toggle session['allow_complete'] = True case 'tag': form['instructions'] = 'Browsing tags' @@ -1982,7 +2032,7 @@ class Slixfeed(slixmpp.ClientXMPP): desc=('Select a tag to browse.'), required=True) tags = sqlite.get_tags(db_file) - tags = sorted(tags, key=lambda x: x[0]) + # tags = sorted(tags, key=lambda x: x[0]) for tag in tags: name = tag[0] ix = str(tag[1]) @@ -2001,12 +2051,20 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - tag_id = payload['values']['tag'] - tag_name = sqlite.get_tag_name(db_file, tag_id)[0] form = self['xep_0004'].make_form('form', 'Subscriptions') + jid_bare = session['from'].bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + form.add_field(ftype='hidden', + value=jid, + var='jid') + else: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + tag_id = values['tag'] + tag_name = sqlite.get_tag_name(db_file, tag_id)[0] form['instructions'] = 'Subscriptions tagged with "{}"'.format(tag_name) options = form.add_field(var='subscriptions', # ftype='list-multi', # TODO To be added soon @@ -2015,7 +2073,7 @@ class Slixfeed(slixmpp.ClientXMPP): desc=('Select a subscription to edit.'), required=True) subscriptions = sqlite.get_feeds_by_tag_id(db_file, tag_id) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: title = subscription[1] url = subscription[2] @@ -2029,68 +2087,28 @@ class Slixfeed(slixmpp.ClientXMPP): return session - # FIXME There are feeds that are missing (possibly because of sortings) - async def _handle_subscription(self, iq, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - jid_bare = session['from'].bare - form = self['xep_0004'].make_form('form', 'Subscription editor') - form['instructions'] = '📰️ Edit subscription preferences and properties' - # form.addField(var='interval', - # ftype='text-single', - # label='Interval period') - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - subscriptions = sqlite.get_feeds(db_file) - # subscriptions = set(subscriptions) - categorized_subscriptions = {} - for subscription in subscriptions: - title = subscription[1] - url = subscription[2] - try: - letter = title[0].capitalize() - if letter not in categorized_subscriptions: - categorized_subscriptions[letter] = [subscription] - # title[0].capitalize()] = [subscription] - else: - categorized_subscriptions[letter].append(subscription) - # title[0].capitalize()].append(subscription) - except Exception as e: - logger.warning('Title might be empty:', str(e)) - for category in sorted(categorized_subscriptions): - options = form.add_field(var=category, - ftype='list-single', - label=category.capitalize(), - desc='Select a subscription to view.') - subscriptions_ = categorized_subscriptions[category] - subscriptions_ = sorted(subscriptions_, key=lambda x: x[0]) - for subscription_ in subscriptions_: - # for subscription in categorized_subscriptions[category]: - title = subscription_[0] - url = subscription_[1] - options.addOption(title, url) - session['payload'] = form - session['next'] = self._handle_subscription_editor - session['has_next'] = True - return session - - async def _handle_subscription_editor(self, payload, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - if 'subscription' in payload['values']: - urls = payload['values']['subscription'] - elif 'subscriptions' in payload['values']: - urls = payload['values']['subscriptions'] - url_count = len(urls) form = self['xep_0004'].make_form('form', 'Subscription') + jid_bare = session['from'].bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + form.add_field(ftype='hidden', + value=jid, + var='jid') + else: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + if 'subscription' in values: + urls = values['subscription'] + elif 'subscriptions' in values: + urls = values['subscriptions'] + url_count = len(urls) if isinstance(urls, list) and url_count > 1: form['instructions'] = 'Editing {} subscriptions'.format(url_count) else: @@ -2105,15 +2123,17 @@ class Slixfeed(slixmpp.ClientXMPP): title = sqlite.get_feed_title(db_file, feed_id) title = title[0] tags_result = sqlite.get_tags_by_feed_id(db_file, feed_id) - tags_sorted = sorted(x[0] for x in tags_result) - tags = ', '.join(tags_sorted) + # tags_sorted = sorted(x[0] for x in tags_result) + # tags = ', '.join(tags_sorted) + tags = '' + for tag in tags_result: tags += tag[0] + ', ' form['instructions'] = 'Editing subscription #{}'.format(feed_id) else: form['instructions'] = 'Adding subscription' title = '' tags = '' # TODO Suggest tags by element "categories" form.add_field(ftype='fixed', - value='Properties') + label='Properties') form.add_field(var='name', ftype='text-single', label='Name', @@ -2121,7 +2141,7 @@ class Slixfeed(slixmpp.ClientXMPP): required=True) # NOTE This does not look good in Gajim # url = form.add_field(ftype='fixed', - # value=url) + # label=url) #url['validate']['datatype'] = 'xs:anyURI' options = form.add_field(var='url', ftype='list-single', @@ -2143,7 +2163,7 @@ class Slixfeed(slixmpp.ClientXMPP): ftype='hidden', value=tags) form.add_field(ftype='fixed', - value='Options') + label='Options') options = form.add_field(var='priority', ftype='list-single', label='Priority', @@ -2176,7 +2196,11 @@ class Slixfeed(slixmpp.ClientXMPP): .format(function_name, jid_full)) jid_bare = session['from'].bare values = payload['values'] - jid_file = jid_bare + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) # url = values['url'] # feed_id = sqlite.get_feed_id(db_file, url) @@ -2234,48 +2258,6 @@ class Slixfeed(slixmpp.ClientXMPP): return session - async def _handle_subscription_selector(self, payload, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - jid_bare = session['from'].bare - form = self['xep_0004'].make_form('form', 'Add Subscription') - form['instructions'] = ('📰️ Select a subscription to add\n' - 'Subsciptions discovered for {}' - .format(url)) - # form.addField(var='interval', - # ftype='text-single', - # label='Interval period') - options = form.add_field(var='subscriptions', - ftype='list-multi', - label='Subscriptions', - desc=('Select subscriptions to perform ' - 'actions upon.'), - required=True) - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) - for subscription in subscriptions: - title = subscription[1] - url = subscription[2] - options.addOption(title, url) - # options = form.add_field(var='action', - # ftype='list-single', - # label='Action', - # value='none') - # options.addOption('None', 'none') - # options.addOption('Reset', 'reset') - # options.addOption('Enable', 'enable') - # options.addOption('Disable', 'disable') - # options.addOption('Delete', 'delete') - session['payload'] = form - session['next'] = self._handle_subscription_editor - session['has_next'] = True - return session - - async def _handle_advanced(self, iq, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -2289,11 +2271,10 @@ class Slixfeed(slixmpp.ClientXMPP): if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Advanced') form['instructions'] = 'Extended options' - options = form.add_field(var='option', - ftype='list-single', + options = form.add_field(ftype='list-single', label='Choose', required=True, - value='export') + var='option') jid = session['from'].bare if is_operator(self, jid): options.addOption('Administration', 'admin') @@ -2351,6 +2332,7 @@ class Slixfeed(slixmpp.ClientXMPP): required=True) options.addOption('Bookmarks', 'bookmarks') options.addOption('Contacts', 'roster') + options.addOption('PubSub', 'pubsub') options.addOption('Subscribers', 'subscribers') session['payload'] = form session['next'] = self._handle_admin_action @@ -2478,10 +2460,10 @@ class Slixfeed(slixmpp.ClientXMPP): e_val = entry[e_key] e_key = e_key.capitalize() # form.add_field(ftype='fixed', - # value=e_val) + # label=e_val) if e_key == 'Name': form.add_field(ftype='fixed', - value=e_val) + label=e_val) continue if isinstance(e_val, list): form_type = 'text-multi' @@ -2544,7 +2526,7 @@ class Slixfeed(slixmpp.ClientXMPP): name = cmd.capitalize() form.add_field(var='title', ftype='fixed', - value=name) + label=name) elements = cmds[cmd] for key, value in elements.items(): key = key.replace('_', ' ') @@ -2701,16 +2683,10 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) - form = self['xep_0004'].make_form('form', 'Subscriptions') match payload['values']['action']: case 'bookmarks': form = self['xep_0004'].make_form('form', 'Bookmarks') - form['instructions'] = 'Bookmarks' + form['instructions'] = 'Managing bookmarks' options = form.add_field(var='jid', ftype='list-single', label='Jabber ID', @@ -2723,7 +2699,7 @@ class Slixfeed(slixmpp.ClientXMPP): session['next'] = self._handle_bookmarks_editor case 'roster': form = self['xep_0004'].make_form('form', 'Contacts') - form['instructions'] = 'Organize contacts' + form['instructions'] = 'Organizing contacts' options = form.add_field(var='jid', ftype='list-single', label='Contact', @@ -2774,12 +2750,72 @@ class Slixfeed(slixmpp.ClientXMPP): session['allow_complete'] = True session['has_next'] = False session['next'] = self._handle_subscribers_complete + case 'pubsub': + form = self['xep_0004'].make_form('form', 'PubSub') + form['instructions'] = ('Designate Publish-Subscribe services ' + 'for IoT updates, news publishing, ' + 'and even for microblogging on ' + 'platforms such as Libervia and Movim.') + form.add_field(desc='Select PubSub services to designate.', + ftype='fixed', + label='Jabber ID') + # jid_bare = self.boundjid.bare + # enabled_state = Config.get_setting_value(self.settings, jid_bare, 'enabled') + # form.add_field(ftype='boolean', + # label=jid_bare, + # value=enabled_state, + # var=jid_bare) + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid_bare = item[0] + if item[1]: name = item[1] + elif item[2]: name = item[2] + else: name = jid_bare + enabled_state = Config.get_setting_value(self.settings, jid_bare, 'enabled') + form.add_field(desc=jid_bare, + ftype='boolean', + label=name, + value=enabled_state, + var=jid_bare) + print(Config.get_setting_value(self.settings, jid_bare, 'enabled')) + session['allow_complete'] = True + session['has_next'] = False + session['next'] = self._handle_pubsubs_complete session['allow_prev'] = True session['payload'] = form session['prev'] = self._handle_advanced return session + async def _handle_pubsubs_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.debug('{}: jid_full: {}' + .format(function_name, jid_full)) + values = payload['values'] + print(self.settings) + for key in values: + if key: + jid_bare = key + value = values[key] + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + if jid_bare not in self.settings: + Config.add_settings_jid(self.settings, jid_bare, db_file) + await Config.set_setting_value(self.settings, jid_bare, db_file, 'enabled', value) + print(self.settings) + text_note = 'Done.' + session['next'] = None + session['notes'] = [['info', text_note]] + session['payload'] = None + return session + + async def _handle_subscribers_complete(self, payload, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -3139,7 +3175,6 @@ class Slixfeed(slixmpp.ClientXMPP): logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) jid_bare = session['from'].bare - form = payload jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) if jid_bare not in self.settings: @@ -3174,14 +3209,14 @@ class Slixfeed(slixmpp.ClientXMPP): status_type=status_type) await asyncio.sleep(5) key_list = ['check', 'status', 'interval'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) if (key == 'enabled' and val == 0 and str(is_enabled) == 1): logger.info('Slixfeed has been disabled for {}'.format(jid_bare)) key_list = ['interval', 'status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'xa' status_message = '📪️ Send "Start" to receive updates' XmppPresence.send(self, jid_bare, status_message, @@ -3197,10 +3232,10 @@ class Slixfeed(slixmpp.ClientXMPP): # XmppPresence.send(self, jid, status_message, # status_type=status_type) # await asyncio.sleep(5) - # await task.start_tasks_xmpp(self, jid, ['check', 'status', + # await task.start_tasks_xmpp_chat(self, jid, ['check', 'status', # 'interval']) # else: - # task.clean_tasks_xmpp(self, jid, ['interval', 'status']) + # task.clean_tasks_xmpp_chat(self, jid, ['interval', 'status']) # status_type = 'xa' # status_message = '📪️ Send "Start" to receive Jabber updates' # XmppPresence.send(self, jid, status_message, @@ -3228,7 +3263,8 @@ class Slixfeed(slixmpp.ClientXMPP): # form.add_field(var=key, # ftype='fixed', - # value=result) + # label=result) + form = payload form['title'] = 'Done' form['instructions'] = 'has been completed!' # session['allow_complete'] = True diff --git a/slixfeed/xmpp/command.py b/slixfeed/xmpp/command.py index 2f94950..c19f701 100644 --- a/slixfeed/xmpp/command.py +++ b/slixfeed/xmpp/command.py @@ -2189,14 +2189,14 @@ class XmppCommand: status_type=status_type) await asyncio.sleep(5) key_list = ['check', 'status', 'interval'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) if (key == 'enabled' and val == 0 and str(is_enabled) == 1): logger.info('Slixfeed has been disabled for {}'.format(jid_bare)) key_list = ['interval', 'status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'xa' status_message = '📪️ Send "Start" to receive updates' XmppPresence.send(self, jid_bare, status_message, @@ -2212,10 +2212,10 @@ class XmppCommand: # XmppPresence.send(self, jid, status_message, # status_type=status_type) # await asyncio.sleep(5) - # await task.start_tasks_xmpp(self, jid, ['check', 'status', + # await task.start_tasks_xmpp_chat(self, jid, ['check', 'status', # 'interval']) # else: - # task.clean_tasks_xmpp(self, jid, ['interval', 'status']) + # task.clean_tasks_xmpp_chat(self, jid, ['interval', 'status']) # status_type = 'xa' # status_message = '📪️ Send "Start" to receive Jabber updates' # XmppPresence.send(self, jid, status_message, diff --git a/slixfeed/xmpp/component.py b/slixfeed/xmpp/component.py index f755a44..b79f2ba 100644 --- a/slixfeed/xmpp/component.py +++ b/slixfeed/xmpp/component.py @@ -14,7 +14,9 @@ TODO """ + import asyncio +from feedparser import parse import logging # import os # from random import randrange @@ -22,6 +24,7 @@ import slixmpp import slixfeed.task as task # from time import sleep +from slixfeed.url import join_url, trim_url # from slixmpp.plugins.xep_0363.http_upload import FileTooBig, HTTPError, UploadServiceNotFound # from slixmpp.plugins.xep_0402 import BookmarkStorage, Conference # from slixmpp.plugins.xep_0048.stanza import Bookmarks @@ -235,20 +238,27 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): function_name = sys._getframe().f_code.co_name message_log = '{}' logger.debug(message_log.format(function_name)) - # self.send_presence() - profile.set_identity(self, 'service') - # XmppCommand.adhoc_commands(self) - self.adhoc_commands() - self.service_reactions() - await self['xep_0115'].update_caps() - # await XmppGroupchat.autojoin(self) - await profile.update(self) - task.task_ping(self) - # bookmarks = await self.plugin['xep_0048'].get_bookmarks() - # XmppGroupchat.autojoin(self, bookmarks) status_message = 'Slixfeed version {}'.format(__version__) for operator in self.operators: XmppPresence.send(self, operator['jid'], status_message) + await profile.update(self) + profile.set_identity(self, 'service') + await self['xep_0115'].update_caps() + # self.send_presence() + # bookmarks = await self.plugin['xep_0048'].get_bookmarks() + # XmppGroupchat.autojoin(self, bookmarks) + # await XmppGroupchat.autojoin(self, bookmarks) + jids = await XmppPubsub.get_pubsub_services(self) + for jid_bare in jids: + if jid_bare not in self.settings: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + Config.add_settings_jid(self.settings, jid_bare, db_file) + await task.start_tasks_xmpp_pubsub(self, jid_bare) + # XmppCommand.adhoc_commands(self) + self.adhoc_commands() + # self.service_reactions() + task.task_ping(self) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -263,7 +273,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # self.send_presence() profile.set_identity(self, 'service') self['xep_0115'].update_caps() - # await XmppGroupchat.autojoin(self) + # bookmarks = await self.plugin['xep_0048'].get_bookmarks() + # await XmppGroupchat.autojoin(self, bookmarks) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -342,9 +353,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): return if presence['show'] in ('away', 'dnd', 'xa'): key_list = ['interval'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) key_list = ['status', 'check'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -411,7 +422,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # FIXME TODO Find out what is the source responsible for a couple presences with empty message # NOTE This is a temporary solution await asyncio.sleep(10) - await task.start_tasks_xmpp(self, jid_bare) + await task.start_tasks_xmpp_chat(self, jid_bare) self.add_event_handler("presence_unavailable", self.on_presence_unavailable) time_end = time.time() @@ -451,7 +462,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): logger.debug(message_log.format(function_name, jid_full)) jid_bare = presence['from'].bare # await task.stop_tasks(self, jid) - task.clean_tasks_xmpp(self, jid_bare) + task.clean_tasks_xmpp_chat(self, jid_bare) # NOTE Albeit nice to ~have~ see, this would constantly # send presence messages to server to no end. @@ -478,7 +489,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): message_log = '{}: jid_full: {}' logger.debug(message_log.format(function_name, jid_full)) jid_bare = presence["from"].bare - task.clean_tasks_xmpp(self, jid_bare) + task.clean_tasks_xmpp_chat(self, jid_bare) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -503,10 +514,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # NOTE: Required for Cheogram # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) await asyncio.sleep(5) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 10: logger.warning('{} (time: {})'.format(function_name, @@ -524,7 +535,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # NOTE: Required for Cheogram # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) await asyncio.sleep(5) status_message = ('💡 Send "help" for manual, or "info" for ' 'information.') @@ -545,9 +556,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -564,9 +575,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -583,9 +594,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): - # task.clean_tasks_xmpp(self, jid, ['status']) + # task.clean_tasks_xmpp_chat(self, jid, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -652,11 +663,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): name='🪶️ Subscribe', handler=self._handle_subscription_add) self['xep_0050'].add_command(node='publish', - name='📻 Publish', - handler=self._handle_pubsub_add) - self['xep_0050'].add_command(node='post', - name='📣️ Post', - handler=self._handle_post) + name='📣️ Publish', + handler=self._handle_publish) self['xep_0050'].add_command(node='recent', name='📰️ Browse', handler=self._handle_recent) @@ -694,66 +702,155 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # Special interface # http://jabber.org/protocol/commands#actions - async def _handle_post(self, iq, session): + async def _handle_publish(self, iq, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.debug('{}: jid_full: {}' + .format(function_name, jid_full)) jid_bare = session['from'].bare - if not is_operator(self, jid_bare): - text_warn = 'PubSub is not available.' + if is_operator(self, jid_bare): + form = self['xep_0004'].make_form('form', 'PubSub') + form['instructions'] = 'Manage nodes and publish news into nodes.' + options = form.add_field(desc=('Send a set of selected posts or ' + 'set a new subscription.'), + ftype='list-single', + label='Choose', + required=True, + var='option') + # options.addOption('Manage nodes', 'nodes') + # options.addOption('Manage subscriptions', 'manage') + options.addOption('Post a selection', 'post') + options.addOption('Publish a subscription', 'publish') + session['allow_prev'] = False + session['has_next'] = True + session['next'] = self._handle_publish_select + session['prev'] = None + session['payload'] = form + else: + text_warn = 'This resource is restricted to operators.' session['notes'] = [['warn', text_warn]] - return session - form = self['xep_0004'].make_form('form', 'Post') - form['instructions'] = ('In order to post to PubSub, you will have to ' - 'choose a PubSub Jabber ID (e.g. {}) and ' - 'Slixfeed has to be allowed to publish into ' - 'it.' - .format(self.boundjid.bare)) - form.add_field(var='url', - ftype='text-single', - label='URL', - desc='Enter a subscription URL.', - value='http://', - required=True) - # options = form.add_field(var='jid', - # ftype='list-single', - # label='PubSub', - # desc='Select a PubSub Jabber ID.', - # value=self.boundjid.bare, - # required=True) - # iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) - # for item in iq['disco_items']['items']: - # jid = item[0] - # if item[1]: name = item[1] - # elif item[2]: name = item[2] - # else: name = jid - # options.addOption(jid, name) - form.add_field(var='jid', - ftype='text-single', - label='PubSub', - desc='Enter a PubSub Jabber ID.', - value=self.boundjid.bare, - # value='pubsub.' + self.boundjid.host, - required=True) - form.add_field(var='node', - ftype='text-single', - label='Node', - desc=('Enter a node to publish to (The default value ' - 'is "urn:xmpp:microblog:0" which is allocated to ' - 'each Jabber ID in the Movim system).'), - value='urn:xmpp:microblog:0', - required=False) - options = form.add_field(var='xep', - ftype='list-single', - label='Protocol', - desc='Select XMPP Extension Protocol.', - value='0060', - required=True) - options.addOption('XEP-0060: Publish-Subscribe', '0060') - options.addOption('XEP-0277: Microblogging over XMPP', '0277') - options.addOption('XEP-0472: Pubsub Social Feed', '0472') - session['allow_prev'] = False + return session + + async def _handle_publish_select(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.debug('{}: jid_full: {}' + .format(function_name, jid_full)) + values = payload['values'] + match values['option']: + case 'publish': + form = self['xep_0004'].make_form('form', 'Publish') + form['instructions'] = ('Choose a PubSub Jabber ID and verify ' + 'that Slixfeed has the necessary ' + 'permissions to publish into it.') + form.add_field(var='subscription', + # TODO Make it possible to add several subscriptions at once; + # Similarly to BitTorrent trackers list + # ftype='text-multi', + # label='Subscription URLs', + # desc=('Add subscriptions one time per ' + # 'subscription.'), + ftype='text-single', + label='URL', + desc='Enter a subscription URL.', + value='http://', + required=True) + options = form.add_field(var='jid', + ftype='list-single', + label='PubSub', + desc='Select a PubSub Service.', + value=self.boundjid.bare, + required=True) + options.addOption(self.boundjid.bare, self.boundjid.bare) + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid = item[0] + if item[1]: name = item[1] + elif item[2]: name = item[2] + else: name = jid + options.addOption(jid, name) + # form.add_field(var='jid', + # ftype='text-single', + # label='PubSub', + # desc='Enter a PubSub Jabber ID.', + # value=self.boundjid.bare, + # # value='pubsub.' + self.boundjid.host, + # required=True) + form.add_field(var='node', + ftype='text-single', + label='Node', + desc='Enter a node to publish to.') + # options = form.add_field(var='xep', + # ftype='list-single', + # label='Protocol', + # desc='Select XMPP Extension Protocol.', + # value='0060', + # required=True) + # 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['payload'] = form + case 'post': + form = self['xep_0004'].make_form('form', 'Post') + form['instructions'] = ('Choose a PubSub Jabber ID and verify ' + 'that Slixfeed has the necessary ' + 'permissions to publish into it.') + form.add_field(var='url', + ftype='text-single', + label='URL', + desc='Enter a subscription URL.', + value='http://', + required=True) + options = form.add_field(var='jid', + ftype='list-single', + label='PubSub', + desc='Select a PubSub Service.', + value=self.boundjid.bare, + required=True) + options.addOption(self.boundjid.bare, self.boundjid.bare) + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid = item[0] + if item[1]: name = item[1] + elif item[2]: name = item[2] + else: name = jid + options.addOption(jid, name) + # form.add_field(var='jid', + # ftype='text-single', + # label='PubSub', + # desc='Enter a PubSub Jabber ID.', + # value=self.boundjid.bare, + # # value='pubsub.' + self.boundjid.host, + # required=True) + form.add_field(var='node', + ftype='text-single', + label='Node', + desc='Enter a node to publish to.') + # options = form.add_field(var='xep', + # ftype='list-single', + # label='Protocol', + # desc='Select XMPP Extension Protocol.', + # value='0060', + # required=True) + # 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_preview + session['payload'] = form + session['allow_prev'] = True session['has_next'] = True - session['next'] = self._handle_preview - session['prev'] = None - session['payload'] = form + session['prev'] = self._handle_publish return session async def _handle_preview(self, payload, session): @@ -777,6 +874,11 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): node = values['node'] url = values['url'] xep = values['xep'] + if not node: + if jid == self.boundjid.bare: + node = 'urn:xmpp:microblog:0' + else: + node = uri.get_hostname(url) form = self['xep_0004'].make_form('form', 'Publish') while True: result = await fetch.http(url) @@ -795,7 +897,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if "title" in feed["feed"].keys(): title = feed["feed"]["title"] else: - title = url + title = uri.get_hostname(url) entries = feed.entries entry_ix = 0 for entry in entries: @@ -817,22 +919,21 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_post_complete - session['prev'] = self._handle_post + session['prev'] = self._handle_publish session['payload'] = form break else: result = await crawl.probe_page(url, document) if isinstance(result, list): + results = result form['instructions'] = ('Discovered {} subscriptions ' - 'from the given URL. Please ' - 'choose a subscription.' - .format(len(result))) + 'for {}' + .format(len(results), url)) options = form.add_field(var='url', ftype='list-single', label='Feeds', desc='Select a feed.', required=True) - results = result for result in results: title = result['name'] url = result['link'] @@ -841,7 +942,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['allow_prev'] = True session['has_next'] = True session['next'] = self._handle_preview - session['prev'] = self._handle_post + session['prev'] = self._handle_publish session['payload'] = form break else: @@ -942,7 +1043,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): form['instructions'] = ('Displaying information\nJabber ID {}' .format(jid_bare)) form.add_field(ftype='fixed', - value='News') + label='News') feeds_all = str(sqlite.get_number_of_items(db_file, 'feeds')) form.add_field(label='Subscriptions', ftype='text-single', @@ -962,7 +1063,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): ftype='text-single', value=unread) form.add_field(ftype='fixed', - value='Options') + label='Options') key_archive = Config.get_setting_value(self.settings, jid_bare, 'archive') key_archive = str(key_archive) form.add_field(label='Archive', @@ -1016,7 +1117,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): last_update = 'n/a' next_update = 'n/a' form.add_field(ftype='fixed', - value='Schedule') + label='Schedule') form.add_field(label='Last update', ftype='text-single', value=last_update) @@ -1124,80 +1225,6 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): return session - async def _handle_pubsub_add(self, iq, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - jid_bare = session['from'].bare - if not is_operator(self, jid_bare): - text_warn = 'PubSub is not available.' - session['notes'] = [['warn', text_warn]] - return session - chat_type = await get_chat_type(self, jid_bare) - moderator = None - if chat_type == 'groupchat': - moderator = is_moderator(self, jid_bare, jid_full) - if chat_type == 'chat' or moderator: - form = self['xep_0004'].make_form('form', 'Publish') - form['instructions'] = ('In order to publish via PubSub, you will ' - 'have to choose a PubSub Jabber ID (e.g. ' - '{}) and Slixfeed has to be allowed to ' - 'publish into it.' - .format('pubsub.' + self.boundjid.host)) - form.add_field(var='subscription', - # TODO Make it possible to add several subscriptions at once; - # Similarly to BitTorrent trackers list - # ftype='text-multi', - # label='Subscription URLs', - # desc=('Add subscriptions one time per ' - # 'subscription.'), - ftype='text-single', - label='URL', - desc='Enter a subscription URL.', - value='http://', - required=True) - form.add_field(var='jid', - ftype='text-single', - label='PubSub', - desc='Enter a PubSub Jabber ID.', - value=self.boundjid.bare, - required=True) - form.add_field(var='node', - ftype='text-single', - label='Node', - desc=('Enter a node to publish to (The default ' - 'value is "urn:xmpp:microblog:0" which ' - 'is allocated to each Jabber ID in the ' - 'Movim system).'), - value='urn:xmpp:microblog:0', - required=False) - options = form.add_field(var='xep', - ftype='list-single', - label='Protocol', - desc='Select XMPP Extension Protocol.', - value='0060', - required=True) - options.addOption('XEP-0060: Publish-Subscribe', '0060') - options.addOption('XEP-0277: Microblogging over XMPP', '0277') - options.addOption('XEP-0472: Pubsub Social Feed', '0472') - # form.add_field(var='scan', - # ftype='boolean', - # label='Scan', - # desc='Scan URL for validity (recommended).', - # value=True) - session['allow_prev'] = False - session['has_next'] = True - session['next'] = self._handle_subscription_new - session['prev'] = None - session['payload'] = form - else: - text_warn = ('This resource is restricted to moderators of {}.' - .format(jid_bare)) - session['notes'] = [['warn', text_warn]] - return session - - async def _handle_subscription_add(self, iq, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -1226,12 +1253,12 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if is_operator(self, jid_bare): form.add_field(ftype='fixed', label='Subscriber') - form.add_field(var='jid', + form.add_field(desc=('Enter a Jabber ID to add the ' + 'subscription to (The default Jabber ID ' + 'is your own).'), ftype='text-single', label='Jabber ID', - desc=('Enter a Jabber ID to add the ' - 'subscription to (The default Jabber ID ' - 'is your own).')) + var='jid') # form.add_field(var='scan', # ftype='boolean', # label='Scan', @@ -1452,11 +1479,51 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) + form = self['xep_0004'].make_form('form', 'Subscription') + # scan = values['scan'] + values = payload['values'] + node = values['node'] if 'node' in values else None + url = values['subscription'] + jid = values['jid'] jid_bare = session['from'].bare - jid_file = jid_bare + if is_operator(self, jid_bare) and jid: + jid_file = jid[0] if isinstance(jid, list) else jid + form.add_field(var='jid', + ftype='hidden', + value=jid) + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) - # scan = payload['values']['scan'] - url = payload['values']['subscription'] + if node and sqlite.check_node_exist(db_file, node): + form['title'] = 'Conflict' + form['instructions'] = ('Name "{}" already exists. Choose a ' + 'different name.') + form.add_field(desc='Enter a node to publish to.', + ftype='text-single', + label='Node', + value=node, + var='node') + form.add_field(var='subscription', + ftype='hidden', + value=url) + form.add_field(var='node', + ftype='hidden', + value=node) + session['allow_prev'] = False + session['next'] = self._handle_subscription_new + # session['payload'] = None + session['prev'] = None + # elif not node: + # counter = 0 + # hostname = uri.get_hostname(url) + # node = hostname + ':' + str(counter) + # while True: + # if sqlite.check_node_exist(db_file, node): + # counter += 1 + # node = hostname + ':' + str(counter) + # else: + # break + # Several URLs to subscribe if isinstance(url, list) and len(url) > 1: url_count = len(url) urls = url @@ -1464,14 +1531,22 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): error_count = 0 exist_count = 0 for url in urls: - result = await action.add_feed(self, jid_bare, db_file, url) + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break + result = await action.add_feed(self, jid_bare, db_file, url, node) if result['error']: error_count += 1 elif result['exist']: exist_count += 1 else: agree_count += 1 - form = self['xep_0004'].make_form('form', 'Subscription') if agree_count: response = ('Added {} new subscription(s) out of {}' .format(agree_count, url_count)) @@ -1488,16 +1563,25 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): else: if isinstance(url, list): url = url[0] - result = await action.add_feed(self, jid_bare, db_file, url) + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break + result = await action.add_feed(self, jid_bare, db_file, url, node) + # URL is not a feed and URL has returned to feeds if isinstance(result, list): results = result - form = self['xep_0004'].make_form('form', 'Subscriptions') form['instructions'] = ('Discovered {} subscriptions for {}' .format(len(results), url)) options = form.add_field(var='subscription', ftype='list-multi', label='Subscribe', - desc=('Select subscriptions to add.'), + desc='Select subscriptions to add.', required=True) for result in results: options.addOption(result['name'], result['link']) @@ -1523,23 +1607,32 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['payload'] = None session['prev'] = self._handle_subscription_add elif result['exist']: - # response = ('News source "{}" is already listed ' - # 'in the subscription list at index ' - # '{}.\n{}'.format(result['name'], result['index'], - # result['link'])) - # session['notes'] = [['warn', response]] # Not supported by Gajim - # session['notes'] = [['info', response]] - form = self['xep_0004'].make_form('form', 'Subscription') - form['instructions'] = ('Subscription is already assigned at index {}.' - '\n' - '{}' - '\n' - '\n' - 'Proceed to edit this subscription' - .format(result['index'], result['name'])) + name = result['name'] + form['instructions'] = ('Subscription "{}" already exist. ' + 'Proceed to edit this subscription.' + .format(name)) + url = result['link'] + feed_id = str(result['index']) + entries = sqlite.get_entries_of_feed(db_file, feed_id) + last_renewed = sqlite.get_status_information_of_feed(db_file, + feed_id) + last_renewed = str(last_renewed[5]) + options = form.add_field(desc='Recent titles from subscription', + ftype='list-multi', + label='Preview') + for entry in entries: + options.addOption(entry[1], entry[2]) + form.add_field(ftype='fixed', + label='Information') + form.add_field(ftype='text-single', + label='Renewed', + value=last_renewed) + form.add_field(ftype='text-single', + label='ID #', + value=feed_id) form.add_field(var='subscription', ftype='hidden', - value=result['link']) + value=url) # NOTE Should we allow "Complete"? # Do all clients provide button "Cancel". session['allow_complete'] = False @@ -1547,28 +1640,35 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['next'] = self._handle_subscription_editor session['payload'] = form # session['has_next'] = False + # Single URL to subscribe else: - # response = ('News source "{}" has been ' - # 'added to subscription list.\n{}' - # .format(result['name'], result['link'])) - # session['notes'] = [['info', response]] - form = self['xep_0004'].make_form('form', 'Subscription') - # form['instructions'] = ('✅️ News source "{}" has been added to ' - # 'subscription list as index {}' - # '\n\n' - # 'Choose next to continue to subscription ' - # 'editor.' - # .format(result['name'], result['index'])) - form['instructions'] = ('New subscription' - '\n' - '"{}"' - '\n' - '\n' - 'Proceed to edit this subscription' - .format(result['name'])) + print(result) + name = result['name'] + form['instructions'] = ('Subscription "{}" has been added. ' + 'Proceed to edit this subscription.' + .format(name)) + url = result['link'] + feed_id = str(result['index']) + entries = sqlite.get_entries_of_feed(db_file, feed_id) + last_updated = sqlite.get_status_information_of_feed(db_file, + feed_id) + last_updated = str(last_updated[3]) + options = form.add_field(desc='Recent titles from subscription', + ftype='list-multi', + label='Preview') + for entry in entries: + options.addOption(entry[1], entry[2]) + form.add_field(ftype='fixed', + label='Information') + form.add_field(ftype='text-single', + label='Updated', + value=last_updated) + form.add_field(ftype='text-single', + label='ID #', + value=feed_id) form.add_field(var='subscription', ftype='hidden', - value=result['link']) + value=url) session['allow_complete'] = False session['has_next'] = True # session['allow_prev'] = False @@ -1580,71 +1680,27 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): return session - async def _handle_subscription_enable(self, payload, session): + async def _handle_subscription_toggle(self, payload, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - form = payload jid_bare = session['from'].bare - jid_file = jid_bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + del values['jid'] + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) - ixs = payload['values']['subscriptions'] - form.add_field(ftype='fixed', - value='Modified subscriptions') - for ix in ixs: - name = sqlite.get_feed_title(db_file, ix) - url = sqlite.get_feed_url(db_file, ix) - await sqlite.set_enabled_status(db_file, ix, 1) - # text = (ix,) + name + url - # text = ' - '.join(text) - name = name[0] if name else 'Subscription #{}'.format(ix) - url = url[0] - text = '{} <{}>'.format(name, url) - form.add_field(var=ix, - ftype='text-single', - label=url, - value=text) - form['type'] = 'result' - form['title'] = 'Done' - form['instructions'] = ('Completed successfully!') - session["has_next"] = False + for key in values: + value = 1 if values[key] else 0 + await sqlite.set_enabled_status(db_file, key, value) + text_note = 'Done.' session['next'] = None - session['payload'] = form - return session - - - async def _handle_subscription_disable(self, payload, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - form = payload - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - ixs = payload['values']['subscriptions'] - form.add_field(ftype='fixed', - value='Modified subscriptions') - for ix in ixs: - name = sqlite.get_feed_title(db_file, ix) - url = sqlite.get_feed_url(db_file, ix) - await sqlite.set_enabled_status(db_file, ix, 0) - # text = (ix,) + name + url - # text = ' - '.join(text) - name = name[0] if name else 'Subscription #{}'.format(ix) - url = url[0] - text = '{} <{}>'.format(name, url) - form.add_field(var=ix, - ftype='text-single', - label=url, - value=text) - form['type'] = 'result' - form['title'] = 'Done' - form['instructions'] = ('Completed successfully!') - session["has_next"] = False - session['next'] = None - session['payload'] = form + session['notes'] = [['info', text_note]] + session['payload'] = None return session @@ -1653,32 +1709,29 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - form = payload jid_bare = session['from'].bare - jid_file = jid_bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + del values['jid'] + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) + subscriptions ='' ixs = payload['values']['subscriptions'] - form.add_field(ftype='fixed', - value='Deleted subscriptions') for ix in ixs: name = sqlite.get_feed_title(db_file, ix) url = sqlite.get_feed_url(db_file, ix) - await sqlite.remove_feed_by_index(db_file, ix) - # text = (ix,) + name + url - # text = ' - '.join(text) name = name[0] if name else 'Subscription #{}'.format(ix) url = url[0] - text = '{} <{}>'.format(name, url) - form.add_field(var=ix, - ftype='text-single', - label=url, - value=text) - form['type'] = 'result' - form['title'] = 'Done' - form['instructions'] = ('Completed successfully!') - session["has_next"] = False + await sqlite.remove_feed_by_index(db_file, ix) + subscriptions += '{}. {}\n{}\n\n'.format(ix, name, url) + text_note = ('The following subscriptions have been deleted:\n\n{}' + .format(subscriptions)) session['next'] = None - session['payload'] = form + session['notes'] = [['info', text_note]] + session['payload'] = None return session @@ -1835,10 +1888,18 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): required=True, value='browse') options.addOption('Browse subscriptions', 'browse') - options.addOption('Browse subscriptions by tag', 'tag') - options.addOption('Enable subscriptions', 'enable') - options.addOption('Disable subscriptions', 'disable') + options.addOption('Browse tags', 'tag') options.addOption('Remove subscriptions', 'delete') + options.addOption('Toggle subscriptions', 'toggle') + if is_operator(self, jid_bare): + form.add_field(ftype='fixed', + label='Subscriber') + form.add_field(desc=('Enter a Jabber ID to add the ' + 'subscription to (The default Jabber ID ' + 'is your own).'), + ftype='text-single', + label='Jabber ID', + var='jid') session['payload'] = form session['next'] = self._handle_subscriptions_result session['has_next'] = True @@ -1854,11 +1915,19 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) + values = payload['values'] + jid = values['jid'] jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) form = self['xep_0004'].make_form('form', 'Subscriptions') - match payload['values']['action']: + if is_operator(self, jid_bare) and jid: + jid_file = jid + form.add_field(ftype='hidden', + value=jid, + var='jid') + else: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + match values['action']: case 'browse': form['instructions'] = 'Editing subscriptions' options = form.add_field(var='subscriptions', @@ -1868,7 +1937,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): desc=('Select a subscription to edit.'), required=True) subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: title = subscription[1] url = subscription[2] @@ -1887,7 +1956,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): desc=('Select subscriptions to remove.'), required=True) subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: title = subscription[1] ix = str(subscription[0]) @@ -1897,39 +1966,24 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # TODO Refer to confirmation dialog which would display feeds selected session['next'] = self._handle_subscription_del_complete session['allow_complete'] = True - case 'disable': - form['instructions'] = 'Disabling subscriptions' - options = form.add_field(var='subscriptions', - ftype='list-multi', - label='Subscriptions', - desc=('Select subscriptions to disable.'), - required=True) - subscriptions = sqlite.get_feeds_by_enabled_state(db_file, True) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + case 'toggle': + form['instructions'] = 'Toggling subscriptions' + subscriptions = sqlite.get_feeds_and_enabled_state(db_file) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[1] ix = str(subscription[0]) - options.addOption(title, ix) + title = subscription[1] + url = subscription[2] + enabled_state = True if subscription[3] else False + enabled_state = subscription[3] + form.add_field(ftype='boolean', + label=title, + desc=url, + value=enabled_state, + var=ix) session['cancel'] = self._handle_cancel session['has_next'] = False - session['next'] = self._handle_subscription_disable - session['allow_complete'] = True - case 'enable': - form['instructions'] = 'Enabling subscriptions' - options = form.add_field(var='subscriptions', - ftype='list-multi', - label='Subscriptions', - desc=('Select subscriptions to enable.'), - required=True) - subscriptions = sqlite.get_feeds_by_enabled_state(db_file, False) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) - for subscription in subscriptions: - title = subscription[1] - ix = str(subscription[0]) - options.addOption(title, ix) - session['cancel'] = self._handle_cancel - session['has_next'] = False - session['next'] = self._handle_subscription_enable + session['next'] = self._handle_subscription_toggle session['allow_complete'] = True case 'tag': form['instructions'] = 'Browsing tags' @@ -1939,7 +1993,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): desc=('Select a tag to browse.'), required=True) tags = sqlite.get_tags(db_file) - tags = sorted(tags, key=lambda x: x[0]) + # tags = sorted(tags, key=lambda x: x[0]) for tag in tags: name = tag[0] ix = str(tag[1]) @@ -1958,12 +2012,20 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - tag_id = payload['values']['tag'] - tag_name = sqlite.get_tag_name(db_file, tag_id)[0] form = self['xep_0004'].make_form('form', 'Subscriptions') + jid_bare = session['from'].bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + form.add_field(ftype='hidden', + value=jid, + var='jid') + else: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + tag_id = values['tag'] + tag_name = sqlite.get_tag_name(db_file, tag_id)[0] form['instructions'] = 'Subscriptions tagged with "{}"'.format(tag_name) options = form.add_field(var='subscriptions', # ftype='list-multi', # TODO To be added soon @@ -1972,7 +2034,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): desc=('Select a subscription to edit.'), required=True) subscriptions = sqlite.get_feeds_by_tag_id(db_file, tag_id) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) + # subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: title = subscription[1] url = subscription[2] @@ -1986,68 +2048,28 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): return session - # FIXME There are feeds that are missing (possibly because of sortings) - async def _handle_subscription(self, iq, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - jid_bare = session['from'].bare - form = self['xep_0004'].make_form('form', 'Subscription editor') - form['instructions'] = '📰️ Edit subscription preferences and properties' - # form.addField(var='interval', - # ftype='text-single', - # label='Interval period') - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - subscriptions = sqlite.get_feeds(db_file) - # subscriptions = set(subscriptions) - categorized_subscriptions = {} - for subscription in subscriptions: - title = subscription[1] - url = subscription[2] - try: - letter = title[0].capitalize() - if letter not in categorized_subscriptions: - categorized_subscriptions[letter] = [subscription] - # title[0].capitalize()] = [subscription] - else: - categorized_subscriptions[letter].append(subscription) - # title[0].capitalize()].append(subscription) - except Exception as e: - logger.warning('Title might be empty:', str(e)) - for category in sorted(categorized_subscriptions): - options = form.add_field(var=category, - ftype='list-single', - label=category.capitalize(), - desc='Select a subscription to view.') - subscriptions_ = categorized_subscriptions[category] - subscriptions_ = sorted(subscriptions_, key=lambda x: x[0]) - for subscription_ in subscriptions_: - # for subscription in categorized_subscriptions[category]: - title = subscription_[0] - url = subscription_[1] - options.addOption(title, url) - session['payload'] = form - session['next'] = self._handle_subscription_editor - session['has_next'] = True - return session - - async def _handle_subscription_editor(self, payload, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - if 'subscription' in payload['values']: - urls = payload['values']['subscription'] - elif 'subscriptions' in payload['values']: - urls = payload['values']['subscriptions'] - url_count = len(urls) form = self['xep_0004'].make_form('form', 'Subscription') + jid_bare = session['from'].bare + values = payload['values'] + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + form.add_field(ftype='hidden', + value=jid, + var='jid') + else: + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + if 'subscription' in values: + urls = values['subscription'] + elif 'subscriptions' in values: + urls = values['subscriptions'] + url_count = len(urls) if isinstance(urls, list) and url_count > 1: form['instructions'] = 'Editing {} subscriptions'.format(url_count) else: @@ -2062,15 +2084,17 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): title = sqlite.get_feed_title(db_file, feed_id) title = title[0] tags_result = sqlite.get_tags_by_feed_id(db_file, feed_id) - tags_sorted = sorted(x[0] for x in tags_result) - tags = ', '.join(tags_sorted) + # tags_sorted = sorted(x[0] for x in tags_result) + # tags = ', '.join(tags_sorted) + tags = '' + for tag in tags_result: tags += tag[0] + ', ' form['instructions'] = 'Editing subscription #{}'.format(feed_id) else: form['instructions'] = 'Adding subscription' title = '' tags = '' # TODO Suggest tags by element "categories" form.add_field(ftype='fixed', - value='Properties') + label='Properties') form.add_field(var='name', ftype='text-single', label='Name', @@ -2078,7 +2102,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): required=True) # NOTE This does not look good in Gajim # url = form.add_field(ftype='fixed', - # value=url) + # label=url) #url['validate']['datatype'] = 'xs:anyURI' options = form.add_field(var='url', ftype='list-single', @@ -2100,7 +2124,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): ftype='hidden', value=tags) form.add_field(ftype='fixed', - value='Options') + label='Options') options = form.add_field(var='priority', ftype='list-single', label='Priority', @@ -2133,7 +2157,11 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): .format(function_name, jid_full)) jid_bare = session['from'].bare values = payload['values'] - jid_file = jid_bare + if is_operator(self, jid_bare) and 'jid' in values: + jid = values['jid'][0] + jid_file = jid + else: + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) # url = values['url'] # feed_id = sqlite.get_feed_id(db_file, url) @@ -2191,48 +2219,6 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): return session - async def _handle_subscription_selector(self, payload, session): - jid_full = str(session['from']) - function_name = sys._getframe().f_code.co_name - logger.debug('{}: jid_full: {}' - .format(function_name, jid_full)) - jid_bare = session['from'].bare - form = self['xep_0004'].make_form('form', 'Add Subscription') - form['instructions'] = ('📰️ Select a subscription to add\n' - 'Subsciptions discovered for {}' - .format(url)) - # form.addField(var='interval', - # ftype='text-single', - # label='Interval period') - options = form.add_field(var='subscriptions', - ftype='list-multi', - label='Subscriptions', - desc=('Select subscriptions to perform ' - 'actions upon.'), - required=True) - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) - for subscription in subscriptions: - title = subscription[1] - url = subscription[2] - options.addOption(title, url) - # options = form.add_field(var='action', - # ftype='list-single', - # label='Action', - # value='none') - # options.addOption('None', 'none') - # options.addOption('Reset', 'reset') - # options.addOption('Enable', 'enable') - # options.addOption('Disable', 'disable') - # options.addOption('Delete', 'delete') - session['payload'] = form - session['next'] = self._handle_subscription_editor - session['has_next'] = True - return session - - async def _handle_advanced(self, iq, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -2246,11 +2232,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Advanced') form['instructions'] = 'Extended options' - options = form.add_field(var='option', - ftype='list-single', + options = form.add_field(ftype='list-single', label='Choose', required=True, - value='export') + var='option') jid = session['from'].bare if is_operator(self, jid): options.addOption('Administration', 'admin') @@ -2308,6 +2293,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): required=True) options.addOption('Bookmarks', 'bookmarks') options.addOption('Contacts', 'roster') + options.addOption('PubSub', 'pubsub') options.addOption('Subscribers', 'subscribers') session['payload'] = form session['next'] = self._handle_admin_action @@ -2435,10 +2421,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): e_val = entry[e_key] e_key = e_key.capitalize() # form.add_field(ftype='fixed', - # value=e_val) + # label=e_val) if e_key == 'Name': form.add_field(ftype='fixed', - value=e_val) + label=e_val) continue if isinstance(e_val, list): form_type = 'text-multi' @@ -2501,7 +2487,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): name = cmd.capitalize() form.add_field(var='title', ftype='fixed', - value=name) + label=name) elements = cmds[cmd] for key, value in elements.items(): key = key.replace('_', ' ') @@ -2658,16 +2644,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): function_name = sys._getframe().f_code.co_name logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) - jid_bare = session['from'].bare - jid_file = jid_bare - db_file = config.get_pathname_to_database(jid_file) - subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[1]) - form = self['xep_0004'].make_form('form', 'Subscriptions') match payload['values']['action']: case 'bookmarks': form = self['xep_0004'].make_form('form', 'Bookmarks') - form['instructions'] = 'Bookmarks' + form['instructions'] = 'Managing bookmarks' options = form.add_field(var='jid', ftype='list-single', label='Jabber ID', @@ -2680,7 +2660,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['next'] = self._handle_bookmarks_editor case 'roster': form = self['xep_0004'].make_form('form', 'Contacts') - form['instructions'] = 'Organize contacts' + form['instructions'] = 'Organizing contacts' options = form.add_field(var='jid', ftype='list-single', label='Contact', @@ -2731,12 +2711,72 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): session['allow_complete'] = True session['has_next'] = False session['next'] = self._handle_subscribers_complete + case 'pubsub': + form = self['xep_0004'].make_form('form', 'PubSub') + form['instructions'] = ('Designate Publish-Subscribe services ' + 'for IoT updates, news publishing, ' + 'and even for microblogging on ' + 'platforms such as Libervia and Movim.') + form.add_field(desc='Select PubSub services to designate.', + ftype='fixed', + label='Jabber ID') + # jid_bare = self.boundjid.bare + # enabled_state = Config.get_setting_value(self.settings, jid_bare, 'enabled') + # form.add_field(ftype='boolean', + # label=jid_bare, + # value=enabled_state, + # var=jid_bare) + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid_bare = item[0] + if item[1]: name = item[1] + elif item[2]: name = item[2] + else: name = jid_bare + enabled_state = Config.get_setting_value(self.settings, jid_bare, 'enabled') + form.add_field(desc=jid_bare, + ftype='boolean', + label=name, + value=enabled_state, + var=jid_bare) + print(Config.get_setting_value(self.settings, jid_bare, 'enabled')) + session['allow_complete'] = True + session['has_next'] = False + session['next'] = self._handle_pubsubs_complete session['allow_prev'] = True session['payload'] = form session['prev'] = self._handle_advanced return session + async def _handle_pubsubs_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.debug('{}: jid_full: {}' + .format(function_name, jid_full)) + values = payload['values'] + print(self.settings) + for key in values: + if key: + jid_bare = key + value = values[key] + jid_file = jid_bare + db_file = config.get_pathname_to_database(jid_file) + if jid_bare not in self.settings: + Config.add_settings_jid(self.settings, jid_bare, db_file) + await Config.set_setting_value(self.settings, jid_bare, db_file, 'enabled', value) + print(self.settings) + text_note = 'Done.' + session['next'] = None + session['notes'] = [['info', text_note]] + session['payload'] = None + return session + + async def _handle_subscribers_complete(self, payload, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -3096,7 +3136,6 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) jid_bare = session['from'].bare - form = payload jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) if jid_bare not in self.settings: @@ -3131,14 +3170,14 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): status_type=status_type) await asyncio.sleep(5) key_list = ['check', 'status', 'interval'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) if (key == 'enabled' and val == 0 and str(is_enabled) == 1): logger.info('Slixfeed has been disabled for {}'.format(jid_bare)) key_list = ['interval', 'status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'xa' status_message = '📪️ Send "Start" to receive updates' XmppPresence.send(self, jid_bare, status_message, @@ -3154,10 +3193,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # XmppPresence.send(self, jid, status_message, # status_type=status_type) # await asyncio.sleep(5) - # await task.start_tasks_xmpp(self, jid, ['check', 'status', + # await task.start_tasks_xmpp_chat(self, jid, ['check', 'status', # 'interval']) # else: - # task.clean_tasks_xmpp(self, jid, ['interval', 'status']) + # task.clean_tasks_xmpp_chat(self, jid, ['interval', 'status']) # status_type = 'xa' # status_message = '📪️ Send "Start" to receive Jabber updates' # XmppPresence.send(self, jid, status_message, @@ -3185,7 +3224,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # form.add_field(var=key, # ftype='fixed', - # value=result) + # label=result) + form = payload form['title'] = 'Done' form['instructions'] = 'has been completed!' # session['allow_complete'] = True diff --git a/slixfeed/xmpp/muc.py b/slixfeed/xmpp/muc.py index 6a645dc..ae2208a 100644 --- a/slixfeed/xmpp/muc.py +++ b/slixfeed/xmpp/muc.py @@ -34,7 +34,8 @@ class XmppGroupchat: self.join(self, inviter, jid) - def autojoin(self, bookmarks): + 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"]: @@ -42,13 +43,10 @@ class XmppGroupchat: conference["nick"] = self.alias logging.error('Alias (i.e. Nicknname) is missing for ' 'bookmark {}'.format(conference['name'])) - # jid_from = str(self.boundjid) if self.is_component else None - self.plugin['xep_0045'].join_muc(conference["jid"], - conference["nick"], - # pfrom=jid_from, - # If a room password is needed, use: - # password=the_room_password, - ) + 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' @@ -61,7 +59,7 @@ class XmppGroupchat: .format(conference['name'])) - def join(self, inviter, jid): + async def join(self, inviter, jid): # token = await initdb( # muc_jid, # sqlite.get_setting_value, @@ -86,15 +84,14 @@ class XmppGroupchat: 'Inviter : {}\n' .format(jid, inviter)) jid_from = str(self.boundjid) if self.is_component else None - self.plugin['xep_0045'].join_muc(jid, - self.alias, - pfrom=jid_from - # If a room password is needed, use: - # password=the_room_password, - ) + await self.plugin['xep_0045'].join_muc_wait(jid, + self.alias, + presence_options = {"pfrom" : jid_from}, + password=None) def leave(self, jid): + jid_from = str(self.boundjid) if self.is_component else None message = ('This news bot will now leave this groupchat.\n' 'The JID of this news bot is xmpp:{}?message' .format(self.boundjid.bare)) @@ -108,4 +105,4 @@ class XmppGroupchat: self.plugin['xep_0045'].leave_muc(jid, self.alias, status_message, - self.boundjid) + jid_from) diff --git a/slixfeed/xmpp/process.py b/slixfeed/xmpp/process.py index 53554e6..b3c33dc 100644 --- a/slixfeed/xmpp/process.py +++ b/slixfeed/xmpp/process.py @@ -102,7 +102,7 @@ async def message(self, message): message_text.lower().endswith('.opml')): url = message_text key_list = ['status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'dnd' status_message = '📥️ Procesing request to import feeds...' # pending_tasks_num = len(self.pending_tasks[jid_bare]) @@ -122,7 +122,7 @@ async def message(self, message): del self.pending_tasks[jid_bare][pending_tasks_num] # del self.pending_tasks[jid_bare][self.pending_tasks_counter] key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) XmppMessage.send_reply(self, message, response) return @@ -321,11 +321,20 @@ async def message(self, message): title = ' '.join(message_text.split(' ')[1:]) if not title: title = uri.get_hostname(url) + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break if url.startswith('http'): db_file = config.get_pathname_to_database(jid_file) exist = sqlite.get_feed_id_and_name(db_file, url) if not exist: - await sqlite.insert_feed(db_file, url, title) + await sqlite.insert_feed(db_file, url, title, node) await action.scan(self, jid_bare, db_file, url) if jid_bare not in self.settings: Config.add_settings_jid(self.settings, jid_bare, @@ -333,10 +342,10 @@ async def message(self, message): old = Config.get_setting_value(self.settings, jid_bare, 'old') if old: - # task.clean_tasks_xmpp(self, jid_bare, ['status']) + # task.clean_tasks_xmpp_chat(self, jid_bare, ['status']) # await send_status(jid) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) else: feed_id = sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] @@ -541,7 +550,7 @@ async def message(self, message): del self.pending_tasks[jid_bare][pending_tasks_num] # del self.pending_tasks[jid_bare][self.pending_tasks_counter] key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) else: response = ('Unsupported filetype.\n' 'Try: md or opml') @@ -645,7 +654,7 @@ async def message(self, message): del self.pending_tasks[jid_bare][pending_tasks_num] # del self.pending_tasks[jid_bare][self.pending_tasks_counter] key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) if response: logging.warning('Error for URL {}: {}'.format(url, error)) XmppMessage.send_reply(self, message, response) @@ -653,7 +662,7 @@ async def message(self, message): message_lowercase.endswith('.opml')): url = message_text key_list = ['status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'dnd' status_message = '📥️ Procesing request to import feeds...' # pending_tasks_num = len(self.pending_tasks[jid_bare]) @@ -674,12 +683,109 @@ async def message(self, message): del self.pending_tasks[jid_bare][pending_tasks_num] # del self.pending_tasks[jid_bare][self.pending_tasks_counter] key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) + XmppMessage.send_reply(self, message, response) + # TODO Handle node error + # sqlite3.IntegrityError: UNIQUE constraint failed: feeds_pubsub.node + # ERROR:slixmpp.basexmpp:UNIQUE constraint failed: feeds_pubsub.node + case _ if message_lowercase.startswith('send '): + if is_operator(self, jid_bare): + info = message_text[5:].split(' ') + if len(info) > 1: + jid = info[0] + if '/' not in jid: + url = info[1] + db_file = config.get_pathname_to_database(jid) + if len(info) > 2: + node = info[2] + else: + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break + # task.clean_tasks_xmpp_chat(self, jid_bare, ['status']) + status_type = 'dnd' + status_message = ('📫️ Processing request to fetch data from {}' + .format(url)) + # pending_tasks_num = len(self.pending_tasks[jid_bare]) + pending_tasks_num = randrange(10000, 99999) + self.pending_tasks[jid_bare][pending_tasks_num] = status_message + # self.pending_tasks_counter += 1 + # self.pending_tasks[jid_bare][self.pending_tasks_counter] = status_message + XmppPresence.send(self, jid_bare, status_message, + status_type=status_type) + if url.startswith('feed:'): + url = uri.feed_to_http(url) + url = (uri.replace_hostname(url, 'feed')) or url + result = await action.add_feed(self, jid_bare, db_file, url, node) + if isinstance(result, list): + results = result + response = ("Web feeds found for {}\n\n```\n" + .format(url)) + for result in results: + response += ("Title : {}\n" + "Link : {}\n" + "\n" + .format(result['name'], result['link'])) + response += ('```\nTotal of {} feeds.' + .format(len(results))) + elif result['exist']: + response = ('> {}\nNews source "{}" is already ' + 'listed in the subscription list at ' + 'index {}' + .format(result['link'], + result['name'], + result['index'])) + elif result['node']: + response = ('> {}\nNode "{}" is already ' + 'allocated to index {}' + .format(result['link'], + result['node'], + result['index'])) + elif result['error']: + response = ('> {}\nFailed to find subscriptions. ' + 'Reason: {} (status code: {})' + .format(url, result['message'], + result['code'])) + else: + response = ('> {}\nNews source "{}" has been ' + 'added to subscription list.' + .format(result['link'], result['name'])) + # task.clean_tasks_xmpp_chat(self, jid_bare, ['status']) + del self.pending_tasks[jid_bare][pending_tasks_num] + # del self.pending_tasks[jid_bare][self.pending_tasks_counter] + print(self.pending_tasks) + key_list = ['status'] + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) + # except: + # response = ( + # '> {}\nNews source is in the process ' + # 'of being added to the subscription ' + # 'list.'.format(url) + # ) + else: + response = ('No action has been taken.' + '\n' + 'JID Must not include "/".') + else: + response = ('No action has been taken.' + '\n' + 'Missing argument. ' + 'Enter PubSub JID and subscription URL ' + '(and optionally: NodeName).') + else: + response = ('This action is restricted. ' + 'Type: adding node.') XmppMessage.send_reply(self, message, response) case _ if (message_lowercase.startswith('http') or message_lowercase.startswith('feed:')): url = message_text - # task.clean_tasks_xmpp(self, jid_bare, ['status']) + # task.clean_tasks_xmpp_chat(self, jid_bare, ['status']) status_type = 'dnd' status_message = ('📫️ Processing request to fetch data from {}' .format(url)) @@ -694,8 +800,17 @@ async def message(self, message): url = uri.feed_to_http(url) url = (uri.replace_hostname(url, 'feed')) or url db_file = config.get_pathname_to_database(jid_file) + counter = 0 + hostname = uri.get_hostname(url) + node = hostname + ':' + str(counter) + while True: + if sqlite.check_node_exist(db_file, node): + counter += 1 + node = hostname + ':' + str(counter) + else: + break # try: - result = await action.add_feed(self, jid_bare, db_file, url) + result = await action.add_feed(self, jid_bare, db_file, url, node) if isinstance(result, list): results = result response = ("Web feeds found for {}\n\n```\n" @@ -722,12 +837,12 @@ async def message(self, message): response = ('> {}\nNews source "{}" has been ' 'added to subscription list.' .format(result['link'], result['name'])) - # task.clean_tasks_xmpp(self, jid_bare, ['status']) + # task.clean_tasks_xmpp_chat(self, jid_bare, ['status']) del self.pending_tasks[jid_bare][pending_tasks_num] # del self.pending_tasks[jid_bare][self.pending_tasks_counter] print(self.pending_tasks) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) # except: # response = ( # '> {}\nNews source is in the process ' @@ -768,8 +883,8 @@ async def message(self, message): self.settings, jid_bare, db_file, key, val_new) # NOTE Perhaps this should be replaced by functions # clean and start - task.refresh_task(self, jid_bare, task.task_send, key, - val_new) + task.refresh_task(self, jid_bare, + task.task_message, key, val_new) response = ('Updates will be sent every {} minutes ' '(was: {}).'.format(val_new, val_old)) except: @@ -870,11 +985,11 @@ async def message(self, message): case _ if message_lowercase.startswith('next'): num = message_text[5:] if num: - await action.xmpp_send_update(self, jid_bare, num) + await action.xmpp_send_message(self, jid_bare, num) else: - await action.xmpp_send_update(self, jid_bare) + await action.xmpp_send_message(self, jid_bare) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) case 'old': db_file = config.get_pathname_to_database(jid_file) key = 'old' @@ -932,7 +1047,7 @@ async def message(self, message): url = data[0] if url: key_list = ['status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'dnd' status_message = ('📫️ Processing request to fetch data ' 'from {}'.format(url)) @@ -1028,7 +1143,7 @@ async def message(self, message): XmppMessage.send_reply(self, message, response) del self.pending_tasks[jid_bare][pending_tasks_num] key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) case _ if message_lowercase.startswith('recent '): num = message_text[7:] if num: @@ -1087,20 +1202,20 @@ async def message(self, message): 'News source does not exist. ' .format(url)) # refresh_task(self, jid_bare, send_status, 'status', 20) - # task.clean_tasks_xmpp(self, jid_bare, ['status']) + # task.clean_tasks_xmpp_chat(self, jid_bare, ['status']) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) XmppMessage.send_reply(self, message, response) else: response = ('No action has been taken.' '\n' 'Missing argument. ' - 'Enter feed URL or index number.') + 'Enter subscription URL or index number.') case _ if message_lowercase.startswith('reset'): # TODO Reset also by ID ix_url = message_text[6:] key_list = ['status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'dnd' status_message = '📫️ Marking entries as read...' # pending_tasks_num = len(self.pending_tasks[jid_bare]) @@ -1152,7 +1267,7 @@ async def message(self, message): del self.pending_tasks[jid_bare][pending_tasks_num] # del self.pending_tasks[jid_bare][self.pending_tasks_counter] key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) case _ if message_lowercase.startswith('search '): query = message_text[7:] if query: @@ -1179,7 +1294,7 @@ async def message(self, message): status_type=status_type) await asyncio.sleep(5) key_list = ['check', 'status', 'interval'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) response = 'Updates are enabled.' XmppMessage.send_reply(self, message, response) case 'stats': @@ -1204,7 +1319,7 @@ async def message(self, message): .format(feed_id)) XmppMessage.send_reply(self, message, response) key_list = ['status'] - await task.start_tasks_xmpp(self, jid_bare, key_list) + await task.start_tasks_xmpp_chat(self, jid_bare, key_list) case _ if message_lowercase.startswith('rename '): message_text = message_text[7:] feed_id = message_text.split(' ')[0] @@ -1264,7 +1379,7 @@ async def message(self, message): await Config.set_setting_value( self.settings, jid_bare, db_file, key, val) key_list = ['interval', 'status'] - task.clean_tasks_xmpp(self, jid_bare, key_list) + task.clean_tasks_xmpp_chat(self, jid_bare, key_list) status_type = 'xa' status_message = '📪️ Send "Start" to receive Jabber updates' XmppPresence.send(self, jid_bare, status_message, diff --git a/slixfeed/xmpp/publish.py b/slixfeed/xmpp/publish.py index ba3a5b9..e3f3b2b 100644 --- a/slixfeed/xmpp/publish.py +++ b/slixfeed/xmpp/publish.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# TODO Implement XEP-0472: Pubsub Social Feed +""" + +Functions create_node and create_entry are derived from project atomtopubsub. + +""" import hashlib import slixmpp.plugins.xep_0060.stanza.pubsub as pubsub @@ -10,7 +14,21 @@ from slixmpp.xmlstream import ET class XmppPubsub: - # TODO Make use of var "xep" with match/case + async def get_pubsub_services(self): + jids = [self.boundjid.bare] + iq = await self['xep_0030'].get_items(jid=self.boundjid.domain) + items = iq['disco_items']['items'] + for item in items: + iq = await self['xep_0030'].get_info(jid=item[0]) + identities = iq['disco_info']['identities'] + for identity in identities: + if identity[0] == 'pubsub' and identity[1] == 'service': + jid = item[0] + jids.extend([jid]) + return jids + + + # 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): jid_from = str(self.boundjid) if self.is_component else None iq = self.Iq(stype='set', @@ -61,7 +79,7 @@ class XmppPubsub: # It provides nicer urls. # Respond to atomtopubsub: - # I think it would be a good idea to use md5 checksum of Url as Id for + # 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. @@ -76,10 +94,10 @@ class XmppPubsub: node_entry.set('xmlns', 'http://www.w3.org/2005/Atom') title = ET.SubElement(node_entry, "title") - title.text = entry.title + title.text = entry['title'] updated = ET.SubElement(node_entry, "updated") - updated.text = entry.updated + updated.text = entry['updated'] # Content if version == 'atom3':