From b1a1955545c3ea5a314f5d8db9b5112afb52bc56 Mon Sep 17 00:00:00 2001 From: Schimon Jehudah Date: Tue, 12 Mar 2024 17:13:01 +0000 Subject: [PATCH] Replace configuration file INI by TOML. Fix ping functionality when activated as component (thank you Guus and MattJ). Add initial code for XEP-0060: Publish-Subscribe. Fix case-sensitivity with setting keys sent in-chat-command (Thank you mirux) --- slixfeed/__main__.py | 38 +- slixfeed/action.py | 82 ++-- slixfeed/assets/accounts.ini | 98 ---- slixfeed/assets/accounts.toml | 108 +++++ slixfeed/assets/information.toml | 780 ++++++------------------------- slixfeed/assets/settings.ini | 77 --- slixfeed/assets/settings.toml | 47 ++ slixfeed/config.py | 105 +---- slixfeed/fetch.py | 5 +- slixfeed/sqlite.py | 2 +- slixfeed/task.py | 14 +- slixfeed/version.py | 4 +- slixfeed/xmpp/client.py | 157 ++++--- slixfeed/xmpp/component.py | 167 ++++--- slixfeed/xmpp/connect.py | 23 +- slixfeed/xmpp/iq.py | 14 + slixfeed/xmpp/message.py | 9 +- slixfeed/xmpp/muc.py | 4 + slixfeed/xmpp/process.py | 156 ++++--- slixfeed/xmpp/profile.py | 21 +- slixfeed/xmpp/publish.py | 44 ++ slixfeed/xmpp/utility.py | 15 +- 22 files changed, 802 insertions(+), 1168 deletions(-) delete mode 100644 slixfeed/assets/accounts.ini create mode 100644 slixfeed/assets/accounts.toml delete mode 100644 slixfeed/assets/settings.ini create mode 100644 slixfeed/assets/settings.toml create mode 100644 slixfeed/xmpp/iq.py create mode 100644 slixfeed/xmpp/publish.py diff --git a/slixfeed/__main__.py b/slixfeed/__main__.py index 5785392..fe6ef59 100644 --- a/slixfeed/__main__.py +++ b/slixfeed/__main__.py @@ -85,21 +85,24 @@ from slixfeed.version import __version__ # import socks # import socket -xmpp_type = config.get_value('accounts', 'XMPP', 'type') +account = config.get_values('accounts.toml', 'xmpp') +if not 'mode' in account['settings']: + account_mode = 'client' + print('Key "mode" was not found.\nSetting value to "client".') + # raise Exception('Key type is missing from accounts.toml.') +else: + account_mode = 'component' -if not xmpp_type: - raise Exception('Key type is missing from accounts.ini.') - -match xmpp_type: +match account_mode: case 'client': from slixfeed.xmpp.client import Slixfeed - from slixfeed.config import ConfigClient as ConfigAccount + # from slixfeed.config import ConfigClient as ConfigAccount case 'component': from slixfeed.xmpp.component import SlixfeedComponent - from slixfeed.config import ConfigComponent as ConfigAccount + # from slixfeed.config import ConfigComponent as ConfigAccount -account = ConfigAccount() # TODO Delete as soon as posible after is no longer needed +# account = ConfigAccount() # TODO Delete as soon as posible after is no longer needed class JabberComponent: def __init__(self, jid, secret, hostname, port, alias=None): @@ -175,9 +178,8 @@ class JabberClient: # xmpp.proxy = {'socks5': ('localhost', 9050)} # Connect to the XMPP server and start processing XMPP stanzas. - - if account.setting['hostname'] and account.setting['port']: - xmpp.connect((account.setting['hostname'], account.setting['port'])) + if hostname and port: + xmpp.connect((hostname, port)) else: xmpp.connect() xmpp.process() @@ -200,7 +202,7 @@ def main(): # # socket.socket = socks.socksocket # Setup the command line arguments. - match xmpp_type: + match account_mode: case 'client': parser = ArgumentParser(description=Slixfeed.__doc__) case 'component': @@ -242,11 +244,11 @@ def main(): # logwrn = logger.warning # Try configuration file - alias = account.setting['alias'] - jid = account.setting['jid'] - password = account.setting['password'] - hostname = account.setting['hostname'] - port = account.setting['port'] + jid = account[account_mode]['jid'] + password = account[account_mode]['password'] + alias = account[account_mode]['alias'] if 'alias' in account[account_mode] else None + hostname = account[account_mode]['hostname'] if 'hostname' in account[account_mode] else None + port = account[account_mode]['port'] if 'port' in account[account_mode] else None # Use arguments if were given if args.jid: @@ -268,7 +270,7 @@ def main(): if not alias: alias = (input('Alias: ')) or 'Slixfeed' - match xmpp_type: + match account_mode: case 'client': JabberClient(jid, password, hostname=hostname, port=port, alias=alias) case 'component': diff --git a/slixfeed/action.py b/slixfeed/action.py index 915b4db..1dbc094 100644 --- a/slixfeed/action.py +++ b/slixfeed/action.py @@ -113,6 +113,27 @@ async def export_feeds(self, jid, jid_file, ext): # response = 'Not yet implemented.' return filename + +""" +TODO + +Consider to append text to remind to share presence +'✒️ Share online status to receive updates' + +# TODO Request for subscription +if (await get_chat_type(self, jid_bare) == 'chat' and + not self.client_roster[jid_bare]['to']): + XmppPresence.subscription(self, jid_bare, 'subscribe') + await XmppRoster.add(self, jid_bare) + status_message = '✒️ Share online status to receive updates' + XmppPresence.send(self, jid_bare, status_message) + message_subject = 'RSS News Bot' + message_body = 'Share online status to receive updates.' + XmppMessage.send_headline(self, jid_bare, message_subject, + message_body, 'chat') + +""" + async def xmpp_send_status(self, jid): """ Send status message. @@ -127,7 +148,7 @@ async def xmpp_send_status(self, jid): status_text = '📜️ Slixfeed RSS News Bot' jid_file = jid.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) - enabled = self.settings[jid]['enabled'] or self.settings['default']['enabled'] + enabled = Config.get_setting_value(self.settings, jid, 'enabled') if not enabled: status_mode = 'xa' status_text = '📪️ Send "Start" to receive updates' @@ -179,11 +200,11 @@ async def xmpp_send_update(self, jid, num=None): logger.debug('{}: jid: {} num: {}'.format(function_name, jid, num)) jid_file = jid.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) - enabled = self.settings[jid]['enabled'] or self.settings['default']['enabled'] + enabled = Config.get_setting_value(self.settings, jid, 'enabled') if enabled: - show_media = self.settings[jid]['media'] or self.settings['default']['media'] + show_media = Config.get_setting_value(self.settings, jid, 'media') if not num: - num = self.settings[jid]['quantum'] or self.settings['default']['quantum'] + num = Config.get_setting_value(self.settings, jid, 'quantum') else: num = int(num) results = sqlite.get_unread_entries(db_file, num) @@ -465,7 +486,7 @@ def list_unread_entries(self, result, feed_title, jid): summary = summary.replace('\n', ' ') summary = summary.replace(' ', ' ') summary = summary.replace(' ', ' ') - length = self.settings[jid]['length'] or self.settings['default']['length'] + length = Config.get_setting_value(self.settings, jid, 'length') length = int(length) summary = summary[:length] + " […]" # summary = summary.strip().split('\n') @@ -476,13 +497,13 @@ def list_unread_entries(self, result, feed_title, jid): link = (replace_hostname(link, "link")) or link # news_item = ("\n{}\n{}\n{} [{}]\n").format(str(title), str(link), # str(feed_title), str(ix)) - formatting = self.settings[jid]['formatting'] or self.settings['default']['formatting'] + formatting = Config.get_setting_value(self.settings, jid, 'formatting') news_item = formatting.format(feed_title=feed_title, title=title, summary=summary, link=link, ix=ix) - news_item = news_item.replace('\\n', '\n') + # news_item = news_item.replace('\\n', '\n') return news_item @@ -502,11 +523,9 @@ def list_search_results(query, results): return message -def list_feeds_by_query(db_file, query): +def list_feeds_by_query(query, results): function_name = sys._getframe().f_code.co_name - logger.debug('{}: db_file: {} query: {}' - .format(function_name, db_file, query)) - results = sqlite.search_feeds(db_file, query) + logger.debug('{}'.format(function_name)) message = ('Feeds containing "{}":\n\n```' .format(query)) for result in results: @@ -549,10 +568,10 @@ async def list_options(self, jid_bare): # value = "Default" # values.extend([value]) - value_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] - value_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] - value_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] - value_enabled = self.settings[jid_bare]['archive'] or self.settings['default']['enabled'] + value_archive = Config.get_setting_value(self.settings, jid_bare, 'archive') + value_interval = Config.get_setting_value(self.settings, jid_bare, 'interval') + value_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum') + value_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled') message = ("Options:" "\n" @@ -647,13 +666,10 @@ def list_feeds(results): logger.debug('{}'.format(function_name)) message = "\nList of subscriptions:\n\n```\n" for result in results: - message += ("Name : {}\n" - "URL : {}\n" - # "Updated : {}\n" - # "Status : {}\n" - "ID : {}\n" - "\n" - .format(str(result[0]), str(result[1]), str(result[2]))) + message += ("{} [{}]\n" + "{}\n" + "\n\n" + .format(str(result[1]), str(result[0]), str(result[2]))) if len(results): message += ('```\nTotal of {} subscriptions.\n' .format(len(results))) @@ -793,7 +809,7 @@ async def add_feed(self, jid_bare, db_file, url): status_code=status_code, updated=updated) await scan(self, jid_bare, db_file, url) - old = self.settings[jid_bare]['old'] or self.settings['default']['old'] + 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: @@ -804,9 +820,6 @@ async def add_feed(self, jid_bare, db_file, url): 'code' : status_code, 'error' : False, 'exist' : False} - response = ('> {}\nNews source "{}" has been ' - 'added to subscription list.' - .format(url, title)) break # NOTE This elif statement be unnecessary # when feedparser be supporting json feed. @@ -843,7 +856,7 @@ async def add_feed(self, jid_bare, db_file, url): status_code=status_code, updated=updated) await scan_json(self, jid_bare, db_file, url) - old = self.settings[jid_bare]['old'] or self.settings['default']['old'] + 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] @@ -854,9 +867,6 @@ async def add_feed(self, jid_bare, db_file, url): 'code' : status_code, 'error' : False, 'exist' : False} - response = ('> {}\nNews source "{}" has been ' - 'added to subscription list.' - .format(url, title)) break else: # NOTE Do not be tempted to return a compact dictionary. @@ -886,8 +896,6 @@ async def add_feed(self, jid_bare, db_file, url): 'code' : status_code, 'error' : True, 'exist' : False} - response = ('> {}\nFailed to load URL. Reason: {}' - .format(url, status_code)) break else: ix = exist[0] @@ -898,9 +906,6 @@ async def add_feed(self, jid_bare, db_file, url): 'code' : None, 'error' : False, 'exist' : True} - response = ('> {}\nNews source "{}" is already ' - 'listed in the subscription list at ' - 'index {}'.format(url, name, ix)) break return result_final @@ -1254,7 +1259,7 @@ async def scan(self, jid_bare, db_file, url): return # new_entry = 0 for entry in entries: - logger.debug('{}: entry: {}'.format(function_name, entry.title)) + logger.debug('{}: entry: {}'.format(function_name, entry.link)) if entry.has_key("published"): date = entry.published date = dt.rfc2822_to_iso8601(date) @@ -1481,6 +1486,7 @@ async def extract_image_from_html(url): 'contains(@src, "emoji") or ' 'contains(@src, "icon") or ' 'contains(@src, "logo") or ' + 'contains(@src, "letture") or ' 'contains(@src, "search") or ' 'contains(@src, "share") or ' 'contains(@src, "smiley")' @@ -1614,7 +1620,7 @@ async def remove_nonexistent_entries(self, jid_bare, db_file, url, feed): feed_id = feed_id[0] items = sqlite.get_entries_of_feed(db_file, feed_id) entries = feed.entries - limit = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] + limit = Config.get_setting_value(self.settings, jid_bare, 'archive') for item in items: ix = item[0] entry_title = item[1] @@ -1723,7 +1729,7 @@ async def remove_nonexistent_entries_json(self, jid_bare, db_file, url, feed): feed_id = feed_id[0] items = sqlite.get_entries_of_feed(db_file, feed_id) entries = feed["items"] - limit = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] + limit = Config.get_setting_value(self.settings, jid_bare, 'archive') for item in items: ix = item[0] entry_title = item[1] diff --git a/slixfeed/assets/accounts.ini b/slixfeed/assets/accounts.ini deleted file mode 100644 index 7276e2d..0000000 --- a/slixfeed/assets/accounts.ini +++ /dev/null @@ -1,98 +0,0 @@ -# Settings to tell the bot to which accounts to connect -# and also from which accounts it receives instructions. - -[XMPP] -operator = -reconnect_timeout = 30 -type = client -#type = component - -[XMPP Client] -alias = Slixfeed -jid = -password = -#hostname = -#port = - -[XMPP Component] -alias = Slixfeed -jid = -password = -hostname = -port = - -[XMPP Profile] -name = Slixfeed -nickname = Slixfeed -role = Syndication News Bot -organization = RSS Task Force -url = https://gitgud.io/sjehuda/slixfeed -description = XMPP news bot (supports Atom, JSON, RDF and RSS). -note = This is a syndication news bot powered by Slixfeed. -birthday = 21 June 2022 - -[XMPP Proxy] -# NOTE You might want to consider support for socks4 too (this -# note was written when keys were proxy_host and proxy_port) - -# NOTE Consider not to use a version number as it might give an -# impression of an archaic feature in the future. - -#socks5_host = -#socks5_port = -#socks5_username = -#socks5_password = - -[ActivityPub] -username = -password = -operator = - -[Email] -recipient_emails = -sender_emails = - -[IMAP] -username = -password = -#port = 993 - -[SMTP] -host = -#port = 465 - -[IRC] -username = -password = -#port = 6667 -operator = - -[LXMF] -username = -password = -operator = - -[Matrix] -username = -password = -operator = - -[Nostr] -username = -password = -operator = - -[Session] -username = -password = -operator = - -[SIP] -username = -password = -operator = - -[TOX] -username = -password = -operator = diff --git a/slixfeed/assets/accounts.toml b/slixfeed/assets/accounts.toml new file mode 100644 index 0000000..d492778 --- /dev/null +++ b/slixfeed/assets/accounts.toml @@ -0,0 +1,108 @@ +# Settings to tell the bot to which accounts to connect +# and also from which accounts it receives instructions. + +[xmpp.settings] +#mode = "component" +reconnect_timeout = 30 + +[[xmpp.operators]] +name = "Mr. Operator" +jid = "" + +[[xmpp.operators]] +name = "Mrs. Operator" +jid = "" + +[xmpp.proxy.socks5] +#host = "127.0.0.1" +#port = 9050 +#username = "" +#password = "" + +[xmpp.profile] +FN = "Slixfeed" +NICKNAME = "Slixfeed" +ROLE = "Syndication News Bot" +ORG = "RSS Task Force" +URL = "https://gitgud.io/sjehuda/slixfeed" +NOTE = """ +This is a syndication news bot powered by Slixfeed. +This bot can read Atom, JSON, RDF and RSS feeds. +This bot can communicate to 1:1 chats and groupchats as one. +You are welcome to join our groupchat at: +xmpp:slixfeed@chat.woodpeckersnest.space?join +""" +BDAY = "21 June 2022" +TITLE = "XMPP News Bot" +DESC = "Syndication bot made for XMPP." + +[xmpp.client] +alias = "Slixfeed" +jid = "slixfeed@your.server/slixfeed" +password = "" +#hostname = +#port = + +[xmpp.component] +alias = "Slixfeed" +jid = "rss.your.server" +password = "" +hostname = "your.server" +#port = + +[activitypub] +username = "" +password = "" +operator = "" + +[activitypub.profile] +user_agent = "Slixfeed" + +[email] +recipient_emails = "" +sender_emails = "" + +[email.imap] +username = "" +password = "" +#port = 993 + +[email.smtp] +host = "" +#port = 465 + +[irc] +username = "" +password = "" +#port = 6667 +operator = "" + +[deltachat] +username = "" +password = "" +operator = "" + +[lxmf] +username = "" +password = "" +operator = "" + +[nostr] +username = "" +password = "" +operator = "" + +[session] +username = "" +password = "" +operator = "" + +[sip] +username = "" +password = "" +operator = "" + +[tox] +username = "" +password = "" +operator = "" diff --git a/slixfeed/assets/information.toml b/slixfeed/assets/information.toml index a740796..d96bae1 100644 --- a/slixfeed/assets/information.toml +++ b/slixfeed/assets/information.toml @@ -1,9 +1,5 @@ -[[about]] -title = "About" -subtitle = "Slixfeed news bot" - -[[about]] -info = [""" +[about] +info = """ Slixfeed is a news broker bot for syndicated news which aims to be \ an easy to use and fully-featured news aggregating bot. @@ -12,10 +8,13 @@ even Fediverse instances, along with filtering and other privacy \ driven functionalities. Slixfeed is designed primarily for the XMPP communication network \ -(aka Jabber). Visit https://xmpp.org/software/ for more information. -"""] +(aka Jabber). -note = [""" +https://gitgud.io/sjehuda/slixfeed +""" + +[note] +note = """ You can run your own Slixfeed instance as a client, from your own \ computer, server, and even from a Linux phone (i.e. Droidian, Kupfer, \ Mobian, NixOS, postmarketOS), as well as from Termux. @@ -24,107 +23,59 @@ All you need is one of the above and an XMPP account to connect \ Slixfeed with. Good luck! -"""] - -filetypes = "Atom, JSON, RDF, RSS, XML." -platforms = "XMPP" -# platforms = "ActivityPub, Briar, Email, IRC, LXMF, MQTT, Nostr, Session, Tox." -comment = "For ideal experience, we recommend using XMPP." -url = "https://gitgud.io/sjehuda/slixfeed" - -[[authors]] -title = "Authors" -subtitle = "The people who have made Slixfeed" - -[[authors]] -name = "Laura Lapina" -role = "Co-Author, Instructor and Mentor" -type = "AsyncIO, SQLite" - -[[authors]] -name = "Schimon Zackary" -role = "Creator and Author" - -[[contributors]] -title = "Contributors" -subtitle = "The people who have contributed to Slixfeed" - -[[contributors]] -name = "Stephen Paul Weber" -role = "Contributor and forms coordinator" -type = "XEP-0004, XEP-0050, XEP-0122" -project = "Cheogram" - -[[friends]] -title = "Similar Projects" -subtitle = """ -From Argentina to Germany. Syndication bots made by our counterparts. """ -[[friends]] -name = "err-rssreader" -info = ["A port of old Brutal's RSS Reader for Errbot"] -url = "https://github.com/errbotters/err-rssreader" +[authors] +info = """ +Schimon Zackary +Laura Lapina +""" -[[friends]] -name = "feed-to-muc" -info = [""" -An XMPP bot which posts to a MUC (groupchat) if there is an update in newsfeeds. -"""] -url = "https://salsa.debian.org/mdosch/feed-to-muc" +[contributors] +info = """ +Guus der Kinderen +grym (from #python IRC channel) +Stephen Paul Weber +""" -[[friends]] -name = "JabRSS (fork)" -info = [""" -Never miss a headline again! JabRSS is a simple RSS (RDF Site Summary) \ -headline notification service for Jabber. +[bots] +info = """ +Syndication bots made by our counterparts. -It is based on jabrss@cmeerw.net from Christof. +Morbot +https://codeberg.org/TheCoffeMaker/Morbot -It was restructured and offers additional features (see help, help filter and \ -show plugins). -"""] -url = "http://www.jotwewe.de/de/xmpp/jabrss/jabrss_en.htm" +feed-to-muc +https://salsa.debian.org/mdosch/feed-to-muc -[[friends]] -name = "JabRSS" -info = [""" -A simple RSS (RDF Site Summary) headline notification service for Jabber/XMPP. +JabRSS +http://www.jotwewe.de/de/xmpp/jabrss/jabrss_en.htm -A public instance of the bot is available via xmpp:jabrss@cmeerw.net -"""] -url = "https://dev.cmeerw.org/Projects/jabrss" +JabRSS +https://dev.cmeerw.org/Projects/jabrss -[[friends]] -name = "Morbot" -info = [""" -Morbo is a simple Slixmpp bot that will take new articles from listed RSS \ -feeds and send them to assigned XMPP MUCs (groupchats). -"""] -url = "https://codeberg.org/TheCoffeMaker/Morbot" +err-rssreader" +https://github.com/errbotters/err-rssreader -[[legal]] -title = "Legal" -subtitle = "Legal Notice" +XMPP Bot +https://github.com/nioc/xmpp-bot +""" -[[legal]] -info = [""" +[legal] +info = """ Slixfeed is free software; you can redistribute it and/or modify it under the \ terms of the MIT License. Slixfeed is distributed in the hope that it will be useful, but WITHOUT ANY \ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR \ A PARTICULAR PURPOSE. See the MIT License for more details. -"""] -link = "https://gitgud.io/sjehuda/slixfeed" -[[license]] -title = "License" -subtitle = "MIT License" +https://gitgud.io/sjehuda/slixfeed +""" -[[license]] -license = [""" -Copyright 2022 - 2024 Schimon Zackary Jehudah +[license] +info = """ +Copyright 2022 - 2024 Schimon Jehudah Zackary Permission is hereby granted, free of charge, to any person obtaining a copy \ of this software and associated documentation files (the “Software”), to deal \ @@ -143,558 +94,136 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS \ IN THE SOFTWARE. -"""] -owner = "Schimon Zackary" +""" -[[support]] -title = "Support" -subtitle = "Slixfeed Support Groupchat" +[support] +info = """ +Slixfeed Support Groupchat +xmpp:slixfeed@chat.woodpeckersnest.space?join +""" -[[support]] -jid = "xmpp:slixfeed@chat.woodpeckersnest.space?join" - -[[thanks]] -title = "Thanks" -subtitle = """ +[thanks] +info = """ From SalixOS to Gajim. A journey of 15 years. \ The people who have made all this possible. + +Alixander Court (Utah), Arne-Brün Vogelsang (Germany), Chris Farrell (Oregon) \ +Christian Dersch, Cyrille Pontvieux (France), Denis Fomin (Russia), Dimitris \ +Tzemos (Greece), Emmanuel Gil Peyrot (France), Florent Le Coz (France), George \ +Vlahavas (Greece), Guus der Kinderen (Netherlands), habnabit_ (#python), Imar \ +van Erven Dorens (Netherlands), imattau (atomtopubsub), Jaussoin Timothée \ +(France), Justin Karneges (California), Kevin Smith (Wales), Lars Windolf \ +(Germany), Luis Henrique Mello (Brazil), magicfelix, Markus Muttilainen \ +(SalixOS), Martin (Germany), Mathieu Pasquet (France), Maxime Buquet (France), \ +mirux (Germany), Phillip Watkins (England), Pierrick Le Brun (France), Raphael \ +Groner (Germany), Remko Tronçon (Belgium), Richard Lapointe (Connecticut), \ +Simone "roughnecks" Canaletti (Italy), Strix from Loqi, Thibaud Guerin \ +(SalixOS), Thorsten Fröhlich (France), Thorsten Mühlfelder (Germany), Tim \ +Beech (Brazil), Tomoki Tsuchiya (SalixOS), Yann Leboulanger (France) + +Thanks also to the friends of #python at irc.libera.chat """ -[[thanks]] -name = "Alixander Court" -country = "Utah" -url = "https://alixandercourt.com" +[operators] +info = """ +No operator was specified for this instance. +""" -[[thanks]] -name = "Arne-Brün Vogelsang" -country = "Germany" -project = "monocles" -url = "https://monocles.eu/more" - -[[thanks]] -name = "Chris Farrell" -alias = "timcowchip" -country = "Oregon" -project = "SalixOS" - -[[thanks]] -name = "Christian Dersch" -alias = "christian" -project = "SalixOS" - -[[thanks]] -name = "Cyrille Pontvieux" -alias = "JRD" -country = "France" -project = "SalixOS" -url = "http://animeka.com http://enialis.net" -jabber = "xmpp:jrd@jabber.cz?message" - -[[thanks]] -name = "Denis Fomin" -alias = "Dicson" -country = "Russia" -project = "Gajim" -url = "https://juick.com/dicson" - -[[thanks]] -name = "Dimitris Tzemos" -alias = "djemos" -country = "Greece" -projects = "SalixOS, Slackel" -url = "http://slackel.gr" -jabber = "xmpp:djemos@jabber.org?message" - -[[thanks]] -name = "Emmanuel Gil Peyrot" -alias = "Link mauve" -country = "France" -projects = "Poezio, slixmpp" -jabber = "xmpp:linkmauve@linkmauve.fr?message" -url = "https://linkmauve.fr" - -[[thanks]] -name = "Florent Le Coz" -alias = "louiz" -country = "France" -projects = "Poezio, slixmpp" -jabber = "xmpp:louiz@louiz.org?message" -url = "https://louiz.org" - -[[thanks]] -name = "George Vlahavas" -alias = "gapan" -country = "Greece" -project = "SalixOS" -url = "https://salixos.org https://vlahavas.com" - -[[thanks]] -name = "Guus der Kinderen" -country = "Netherlands" -project = "Openfire" -url = "https://igniterealtime.org" - -[[thanks]] -name = "habnabit_" -alias = "habnabit_" -irc = "irc://irc.libera.chat/#python" - -[[thanks]] -name = "Imar van Erven Dorens" -country = "Netherlands" -project = "SalixOS" -url = "https://simplicit.nl" - -[[thanks]] -name = "imattau" -alias = "imattau" -project = "atomtopubsub" - -[[thanks]] -name = "Jaussoin Timothée" -alias = "edhelas" -country = "France" -projects = "atomtopubsub, Movim" -url = "https://mov.im" - -[[thanks]] -name = "Justin Karneges" -country = "California" -project = "Psi" -url = "https://jblog.andbit.net https://psi-im.org" - -[[thanks]] -name = "Kevin Smith" -alias = "Kev" -country = "Wales" -projects = "Psi, SleekXMPP, Swift IM" -url = "http://kismith.co.uk https://isode.com https://swift.im" - -[[thanks]] -name = "Lars Windolf" -alias = "lwindolf" -country = "Germany" -project = "Liferea" -url = "https://lzone.de" - -[[thanks]] -name = "Luis Henrique Mello" -alias = "lmello" -country = "Brazil" -project = "SalixOS" - -[[thanks]] -name = "magicfelix" -alias = "magicfelix" - -[[thanks]] -name = "Markus Muttilainen" -alias = "stillborn" -project = "SalixOS" - -[[thanks]] -name = "Martin" -alias = "debacle" -country = "Germany" -projects = "Debian, sms4you" -email = "mailto:debacle@debian.org" - -[[thanks]] -name = "Mathieu Pasquet" -alias = "mathieui" -country = "France" -project = "slixmpp" -jabber = "xmpp:mathieui@mathieui.net?message" -url = "https://blog.mathieui.net" - -[[thanks]] -name = "Maxime Buquet" -alias = "pep" -country = "France" -project = "slixmpp" -jabber = "xmpp:pep@bouah.net?message" -url = "https://bouah.net" - -[[thanks]] -name = "mirux" -alias = "mirux" -country = "Germany" - -[[thanks]] -name = "Phillip Watkins" -alias = "pwatk" -country = "England" -project = "SalixOS" - -[[thanks]] -name = "Pierrick Le Brun" -alias = "akuna" -country = "France" -project = "SalixOS" -url = "https://mossieur-ballon.com" - -[[thanks]] -name = "Raphael Groner" -alias = "rapgro" -country = "Germany" -project = "Fedora" - -[[thanks]] -name = "Remko Tronçon" -country = "Belgium" -projects = "Psi, SleekXMPP, Swift IM" -url = "http://el-tramo.be https://mko.re https://psi-im.org" - -[[thanks]] -name = "Richard Lapointe" -alias = "laprjns" -country = "Connecticut" -projects = "SalixOS, Zenwalk" - -[[thanks]] -name = "Simone Canaletti" -alias = "roughnecks" -country = "Italy" -url = "https://woodpeckersnest.space" - -[[thanks]] -name = "Stephen Paul Weber" -alias = "singpolyma" -projects = "Cheogram, JMP, Sopranica" -url = "https://singpolyma.net" - -[[thanks]] -name = "Strix from Loqi" -alias = "Strix" - -[[thanks]] -name = "Thibaud Guerin" -alias = "guth" -project = "SalixOS" - -[[thanks]] -name = "Thorsten Fröhlich" -country = "France" - -[[thanks]] -name = "Thorsten Mühlfelder" -alias = "thenktor" -country = "Germany" -project = "SalixOS" - -[[thanks]] -name = "Tim Beech" -alias = "mimosa" -country = "Brazil" -project = "SalixOS" -url = "https://apipucos.wordpress.com" - -[[thanks]] -name = "Tomoki Tsuchiya" -alias = "tsuren" -project = "SalixOS" - -[[thanks]] -name = "Yann Leboulanger" -alias = "asterix" -country = "France" -project = "Gajim" -jabber = "xmpp:asterix@jabber.lagaule.org?message" -url = "https://gajim.org" - -[[thanks]] -name = "#python (IRC Channel)" -irc = "irc://irc.libera.chat/#python" - -[[thanks]] -name = "The Salix Team" -about = [""" -Previously part of the Zenwalk team. - -The stubbornness of the Salix OS team members, and their determination to the \ -cause, no matter whether popular or else, you are the people who have lead \ -the creator of this software to the XMPP network. - -It may well be said, that without you, gentlemen, and without your kind \ -honesty, sincerity and even the arguments however difficult these arguments \ -were, Slixfeed would have never been existed today. - -All this from an XMPP groupchat that started out from 5 to 8 participants, \ -fifteen years ago (2009). - -Thank you. -"""] -irc = "irc://irc.libera.chat/#salix" -jabber = "xmpp:salix@chat.meticul.eu?join" -url = "https://docs.salixos.org/wiki/Salix_OS:Team" - -[[thanks]] -name = "The XMPP Community" -about = [""" -For over a couple of decades, the people of XMPP form a strong community \ -which strives to provide you and your loved ones, private, secure and \ -stable communication experience. - -While we are for private property and high standard of living, in the XMPP \ -realm we cooperate and we compete together to provide you with the best \ -communication platform in the world. - -With governments and intelligence agencies around the world making an \ -extensive - and sometimes exclusive - use of XMPP, you can be rest assured \ -that you can never be wrong by making XMPP your prime and premier choice \ -for communications. - -We are XMPP. -Join us! -"""] - -[[operators]] -title = "Operators" -subtitle = "Slixfeed Operators" - -[[operators]] -name = "Mr. Operator" -jid = "No operator was specified for this instance." - -[[policies]] -title = "Policies" -subtitle = "Terms of service" - -[[policies]] -name = "Terms and Conditions" -info = [""" +[terms] +info = """ You are bound to these terms. -"""] - -[[policies]] -name = "Privacy Policy" -info = [""" -All your data belongs to us. -"""] - -[[clients]] -title = "Recommended Clients" -subtitle = """ -As a chat bot, Slixfeed works with any XMPP messenger, yet we have deemed it \ -appropriate to list the software that work best with Slixfeed, namely those \ -that provide support for XEP-0050: Ad-Hoc Commands. """ -[[clients]] -name = "Cheogram" -info = "XMPP client for mobile" -url = "https://cheogram.com" +[privacy] +info = """ +All your data belongs to us. +""" -# [[clients]] -# name = "Conversations" -# info = "XMPP client for mobile" -# url = "https://conversations.im" +[clients] +info = """ +Recommended Clients: -[[clients]] -name = "Converse" -info = "XMPP client for desktop and mobile" -url = "https://conversejs.org" +Cheogram +https://cheogram.com -# [[clients]] -# name = "Gajim" -# info = "XMPP client for desktop" -# url = "https://gajim.org" +Converse +https://conversejs.org -# [[clients]] -# name = "Monal IM" -# info = "XMPP client for desktop and mobile" -# url = "https://monal-im.org" +Gajim +https://gajim.org -[[clients]] -name = "monocles chat" -info = "XMPP client for mobile" -url = "https://monocles.chat" +monocles chat +https://monocles.chat -[[clients]] -name = "Movim" -info = "XMPP client for desktop and mobile" -url = "https://mov.im" +Movim +https://mov.im -# [[clients]] -# name = "Moxxy" -# info = "XMPP client for mobile" -# url = "https://moxxy.org" +Poezio +https://poez.io +""" -[[clients]] -name = "Psi" -info = "XMPP client for desktop" -url = "https://psi-im.org" +[services] +info = """ +Recommended Syndication Services -[[clients]] -name = "Psi+" -info = "XMPP client for desktop" -url = "https://psi-plus.com" +Feed Creator +https://www.fivefilters.org/feed-creator/" -# [[clients]] -# name = "Swift" -# info = "XMPP client for desktop" -# url = "https://swift.im" +Kill the Newsletter +https://kill-the-newsletter.com -# [[clients]] -# name = "yaxim" -# info = "XMPP client for mobile" -# url = "https://yaxim.org" +Open RSS +https://openrss.org -[[services]] -title = "Recommended News Services" -subtitle = [""" -Below are online services that extend the syndication experience by means \ -of bookmarking and multimedia, and also enhance it by restoring access to \ -news web feeds. -"""] +RSS-Bridge +https://rss-bridge.org/bridge01/ -[[services]] -name = "Feed Creator" -info = [""" -Feed Creator is a service that creates feeds from HTML pages. \ -It generates RSS and JSON feeds from a set of links or other HTML elements. -"""] -link = "https://www.fivefilters.org/feed-creator/" +RSSHub +https://docs.rsshub.app +""" -[[services]] -name = "Kill the Newsletter" -info = "Kill the Newsletter converts email newsletters into Web feeds." -link = "https://kill-the-newsletter.com" +[software] +info = """ +Recommended News Software -[[services]] -name = "Open RSS" -info = [""" -Open RSS is a nonprofit organization that provides free RSS feeds for \ -websites and applications that don't already provide them, so RSS feeds can \ -continue to be a reliable way for people to stay up-to-date with content \ -anywhere on the internet. -"""] -link = "https://openrss.org" +CommaFeed +https://commafeed.com -[[services]] -name = "RSS-Bridge" -info = [""" -RSS-Bridge is free and open source software for generating Atom or RSS \ -feeds from websites which don’t have one. It is written in PHP and intended \ -to run on a Web server. -"""] -link = "https://rss-bridge.org/bridge01/" +FreshRSS +https://freshrss.org -[[services]] -name = "RSSHub" -info = [""" -RSSHub is an open source, easy to use, and extensible RSS feed generator. \ -It's capable of generating RSS feeds from pretty much everything. -"""] -link = "https://docs.rsshub.app" +Liferea +https://lzone.de/liferea/ -[[software]] -title = "Recommended News Software" -subtitle = [""" -Take back control of your news. With free, quality, software for your \ -desktop, home and mobile devices. -"""] +NetNewsWire +https://netnewswire.com -[[software]] -name = "CommaFeed" -info = [""" -A self-hosted RSS reader, based on Dropwizard and React/TypeScript. -"""] -link = "https://commafeed.com" -os = "Any (HTML)" +Newsboat +https://newsboat.org -[[software]] -name = "FreshRSS" -info = [""" -FreshRSS is a self-hosted RSS and Atom feed aggregator. -It is lightweight, easy to work with, powerful, and customizable. -"""] -link = "https://freshrss.org" -os = "Any (HTML)" +Spot-On +https://textbrowser.github.io/spot-on/ -[[software]] -name = "Liferea" -info = [""" -Liferea is a feed reader/news aggregator that brings together all of the \ -content from your favorite subscriptions into a simple interface that makes \ -it easy to organize and browse feeds. Its GUI is similar to a desktop \ -mail/news client, with an embedded web browser. -"""] -link = "https://lzone.de/liferea/" -os = "FreeBSD and Linux" +Vienna RSS +https://vienna-rss.com +""" -[[software]] -name = "NetNewsWire" -info = [""" -NetNewsWire shows you articles from your favorite blogs and news sites and \ -keeps track of what you’ve read. +[resources] +info = """ +Useful Resources: -This means you can stop going from page to page in your browser looking for \ -new articles to read. Do it the easy way instead: let NetNewsWire bring you \ -the news. +feedparser +https://pythonhosted.org/feedparser -And, if you’ve been getting your news via the commercial Social Networks — \ -with their ads, algorithms, user tracking, outrage, and misinformation — you \ -can switch to NetNewsWire to get news directly and more reliably from the \ -sites you trust. -"""] -link = "https://netnewswire.com" -os = "MacOS" +Slixmpp +https://slixmpp.readthedocs.io -[[software]] -name = "Newsboat" -info = [""" -Newsboat is an RSS/Atom feed reader for the text console. It’s an actively \ -maintained fork of Newsbeuter -"""] -link = "https://newsboat.org" -os = "Any" +XMPP +https://xmpp.org/about +""" -[[software]] -name = "Spot-On" -info = [""" -Spot-On is a software carnival which brings chat, email, news, newsgroups, \ -search and other forms of communications into a single communications \ -orchestra. -"""] -link = "https://textbrowser.github.io/spot-on/" -os = "Any" - -[[software]] -name = "Vienna RSS" -info = [""" -Vienna is an RSS/Atom reader for macOS, packed with powerful features that \ -help you make sense of the flood of information that is distributed via \ -these formats today. -"""] -link = "https://vienna-rss.com" -os = "MacOS" - -[[resources]] -title = "Useful Resources" -subtitle = "Technologies which Slixfeed is based upon" - -[[resources]] -name = "feedparser" -info = "Syndication Library" -url = "https://pythonhosted.org/feedparser" - -[[resources]] -name = "Slixmpp" -info = "XMPP Library" -url = "https://slixmpp.readthedocs.io" - -[[resources]] -name = "XMPP" -info = "Messaging Protocol" -url = "https://xmpp.org/about" - -[[rss_task_force]] -title = "About RSS Task Force" -subtitle = "Swiss Organization" - -[[rss_task_force]] -info = [""" +[rss-task-force] +info = """ The RSS Task Force (previously known as The Syndication Society) is an \ international organization headquartered in Switzerland. @@ -706,44 +235,35 @@ Thanks to a joint effort of transport and travel companies, in 2021 we have \ expanded our cause towards all entities of all types and sorts. The RSS Task Force was founded by two taxicab drivers in 2018. -"""] +""" -[[sleekxmpp]] -title = "About Project SleekXMPP" -subtitle = "SleekXMPP XMPP Library" - -[[sleekxmpp]] -info = [""" +[sleekxmpp] +info = """ SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+, and is \ featured in examples in the book XMPP: The Definitive Guide by Kevin Smith, \ Remko Tronçon, and Peter Saint-Andre. -"""] -url = "https://codeberg.org/fritzy/SleekXMPP" -[[slixmpp]] -title = "About Project Slixmpp" -subtitle = "Slixmpp XMPP Library" +https://codeberg.org/fritzy/SleekXMPP +""" -[[slixmpp]] -info = [""" +[slixmpp] +info = """ Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of \ SleekXMPP. Slixmpp's goals is to only rewrite the core of the SleekXMPP library \ (the low level socket handling, the timers, the events dispatching) \ in order to remove all threads. -"""] -url = "https://codeberg.org/poezio/slixmpp" -[[xmpp]] -title = "About XMPP" -subtitle = "Previously known as Jabber" +https://codeberg.org/poezio/slixmpp +""" -[[xmpp]] -info = [""" +[xmpp] +info = """ XMPP is the Extensible Messaging and Presence Protocol, a set of open \ technologies for instant messaging, presence, multi-party chat, voice and \ video calls, collaboration, lightweight middleware, content syndication, and \ generalized routing of XML data. -"""] -link = "https://xmpp.org/about" + +https://xmpp.org/about +""" diff --git a/slixfeed/assets/settings.ini b/slixfeed/assets/settings.ini deleted file mode 100644 index 7d82a80..0000000 --- a/slixfeed/assets/settings.ini +++ /dev/null @@ -1,77 +0,0 @@ -# This file lists default settings per database. -# See file /usr/share/slixfeed/settings.ini - -#[Bot Settings] - -# Check interval -#interval = 90 - -[Settings] -#[Contact Settings] - -# Maximum items to archive (0 - 500) -archive = 50 - -# Source check interval (recommended 90; minimum 10) -check = 120 - -# Work status (Value 0 to disable) -enabled = 1 - -# Enable filters (Value 1 to enable) -filter = 0 - -# Update interval in minutes (Minimum value 10) -interval = 300 - -# Maximum length of summary (Value 0 to disable) -length = 300 - -# Display media (audio, image, video) when available -media = 0 - -# Mark entries of newly added entries as unread -old = 0 - -# Amount of entries per update -quantum = 3 - -# Pick random item from database -random = 0 - -# Set message formatting -formatting = {title}\n> {summary}\n{link}\n{feed_title} [{ix}]\n\n - -# Utilized in case of missing protocol support. -[Bridge] -gopher = - -i2p = - -nostr = - -tor = - -yggdrasil = - -[Network] -# Example http://localhost:8118 (privoxy) -http_proxy = - -# User Agent -user_agent = Slixfeed/0.1 - -# Enable policed DNS system (not recommended) -# clearnet = 1 - -# Enable I2P mixnet system (safer) -i2p = 1 - -# Enable Loki mixnet system (safer) -loki = 1 - -# Enable Tor semi-mixnet system (semi-safer) -tor = 1 - -# Enable Yggdrasil mixnet system (safer) -yggdrasil = 1 diff --git a/slixfeed/assets/settings.toml b/slixfeed/assets/settings.toml new file mode 100644 index 0000000..6925e63 --- /dev/null +++ b/slixfeed/assets/settings.toml @@ -0,0 +1,47 @@ +# This file lists default settings per database. +# See file /usr/share/slixfeed/settings.toml + +[default] +archive = 50 # Maximum items to archive (0 - 500) +check = 120 # Source check interval (recommended 90; minimum 10) +enabled = 1 # Work status (Value 0 to disable) +interval = 300 # Update interval (Minimum value 10) +length = 300 # Maximum length of summary (Value 0 to disable) +media = 0 # Display media (audio, image, video) when available +old = 0 # Mark entries of newly added entries as unread +quantum = 3 # Amount of entries per update +random = 0 # Pick random item from database + +# Message styling is not to be modified from bot +# * title = Title of item +# * summary = Summary of item +# * link = Link of item +# * feed_title = Title of news source +# * ix = Index of item +formatting = """ +{title} +> {summary} +{link} +{feed_title} [{ix}] + + +""" + +# Utilized in case of missing protocol support. +[bridge] +gopher = "" +i2p = "" +ipfs = "" +nostr = "" +tor = "" +yggdrasil = "" + +[network] +http_proxy = "http://localhost:8118" +user_agent = "Slixfeed/0.1" +clearnet = 0 # Enable policed DNS system (not recommended) +i2p = 1 # Enable I2P mixnet system (safer) +ipfs = 1 # Enable IPFS DHT system (safer) +loki = 1 # Enable Loki mixnet system (safer) +tor = 1 # Enable Tor semi-mixnet system (semi-safer) +yggdrasil = 1 # Enable Yggdrasil mixnet system (safer) diff --git a/slixfeed/config.py b/slixfeed/config.py index 3d79a99..1a58344 100644 --- a/slixfeed/config.py +++ b/slixfeed/config.py @@ -50,26 +50,21 @@ except: class Config: def add_settings_default(settings): - settings['default'] = {} - for key in ('archive', 'check', 'enabled', 'filter', 'formatting', - 'interval', 'length', 'media', 'old', 'quantum'): - value = get_value('settings', 'Settings', key) - settings['default'][key] = value + settings_default = get_values('settings.toml', 'settings') + settings['default'] = settings_default + # TODO Open SQLite file once def add_settings_jid(settings, jid_bare, db_file): settings[jid_bare] = {} for key in ('archive', 'enabled', 'filter', 'formatting', 'interval', 'length', 'media', 'old', 'quantum'): value = sqlite.get_setting_value(db_file, key) - if value: value = value[0] - settings[jid_bare][key] = value - - def add_settings_xmpp(settings): - settings['xmpp'] = {} - for key in ('operator', 'reconnect_timeout', 'type'): - value = get_value('accounts', 'XMPP', key) - settings['xmpp'][key] = value + if value: settings[jid_bare][key] = value[0] + def get_settings_xmpp(key=None): + result = get_values('accounts.toml', 'xmpp') + result = result[key] if key else result + return result async def set_setting_value(settings, jid_bare, db_file, key, val): key = key.lower() @@ -81,73 +76,12 @@ class Config: await sqlite.set_setting_value(db_file, key_val) def get_setting_value(settings, jid_bare, key): - if key in settings[jid_bare]: + if jid_bare in settings and key in settings[jid_bare]: value = settings[jid_bare][key] else: value = settings['default'][key] return value - - # self.settings = {} - # initiate an empty dict and the rest would be: - # settings['account'] = {} - # settings['default'] = {} - # settings['jabber@id'] = {} - # def __init__(self, db_file): - # self.archive = get_setting_value(db_file, 'archive') - # self.enabled = get_setting_value(db_file, 'enabled') - # self.formatting = get_setting_value(db_file, 'formatting') - # self.interval = get_setting_value(db_file, 'interval') - # self.length = get_setting_value(db_file, 'length') - # self.media = get_setting_value(db_file, 'media') - # self.old = get_setting_value(db_file, 'old') - # self.quantum = get_setting_value(db_file, 'quantum') - - # def default(): - # archive = get_value('settings', 'Settings', 'archive') - # enabled = get_value('settings', 'Settings', 'enabled') - # formatting = get_value('settings', 'Settings', 'formatting') - # interval = get_value('settings', 'Settings', 'interval') - # length = get_value('settings', 'Settings', 'length') - # media = get_value('settings', 'Settings', 'media') - # old = get_value('settings', 'Settings', 'old') - # quantum = get_value('settings', 'Settings', 'quantum') - - # def jid(db_file): - # archive = sqlite.get_setting_value(db_file, 'archive') - # enabled = sqlite.get_setting_value(db_file, 'enabled') - # formatting = sqlite.get_setting_value(db_file, 'formatting') - # interval = sqlite.get_setting_value(db_file, 'interval') - # length = sqlite.get_setting_value(db_file, 'length') - # media = sqlite.get_setting_value(db_file, 'media') - # old = sqlite.get_setting_value(db_file, 'old') - # quantum = sqlite.get_setting_value(db_file, 'quantum') - - -class ConfigXMPP: - def __init__(self): - self.setting = {} - for key in ('operator', 'reconnect_timeout', 'type'): - value = get_value('accounts', 'XMPP', key) - self.setting[key] = value - - -class ConfigClient: - def __init__(self): - self.setting = {} - for key in ('alias', 'jid', 'operator', 'password', 'hostname', 'port'): - value = get_value('accounts', 'XMPP Client', key) - self.setting[key] = value - - -class ConfigDefault: - def __init__(self, settings): - settings['default'] = {} - for key in ('archive', 'check', 'enabled', 'filter', 'formatting', - 'interval', 'length', 'media', 'old', 'quantum'): - value = get_value('settings', 'Settings', key) - settings['default'][key] = value - class ConfigNetwork: def __init__(self, settings): settings['network'] = {} @@ -167,6 +101,19 @@ class ConfigJabberID: settings[jid_bare][key] = value +def get_values(filename, key=None): + config_dir = get_default_config_directory() + if not os.path.isdir(config_dir): + config_dir = '/usr/share/slixfeed/' + if not os.path.isdir(config_dir): + config_dir = os.path.dirname(__file__) + "/assets" + config_file = os.path.join(config_dir, filename) + with open(config_file, mode="rb") as defaults: + result = tomllib.load(defaults) + values = result[key] if key else result + return values + + def get_setting_value(db_file, key): value = sqlite.get_setting_value(db_file, key) if value: @@ -299,9 +246,7 @@ def get_value(filename, section, keys): for key in keys: if key in section_res: value = section_res[key] - logging.debug( - "Found value {} for key {}".format(value, key) - ) + logging.debug("Found value {} for key {}".format(value, key)) else: value = '' logging.debug("Missing key:", key) @@ -310,9 +255,7 @@ def get_value(filename, section, keys): key = keys if key in section_res: result = section_res[key] - logging.debug( - "Found value {} for key {}".format(result, key) - ) + logging.debug("Found value {} for key {}".format(result, key)) else: result = '' # logging.error("Missing key:", key) diff --git a/slixfeed/fetch.py b/slixfeed/fetch.py index d24b54d..9e07319 100644 --- a/slixfeed/fetch.py +++ b/slixfeed/fetch.py @@ -119,10 +119,11 @@ async def http(url): msg: list or str Document or error message. """ - user_agent = (config.get_value("settings", "Network", "user_agent") + user_agent = (config.get_values('settings.toml', 'network')['user_agent'] or 'Slixfeed/0.1') headers = {'User-Agent': user_agent} - proxy = (config.get_value("settings", "Network", "http_proxy") or '') + proxy = (config.get_values('settings.toml', 'network')['http_proxy'] + or '') timeout = ClientTimeout(total=10) async with ClientSession(headers=headers) as session: # async with ClientSession(trust_env=True) as session: diff --git a/slixfeed/sqlite.py b/slixfeed/sqlite.py index d9e31b0..50f7bbd 100644 --- a/slixfeed/sqlite.py +++ b/slixfeed/sqlite.py @@ -2243,7 +2243,7 @@ def get_feeds(db_file): cur = conn.cursor() sql = ( """ - SELECT name, url, id + SELECT id, name, url FROM feeds """ ) diff --git a/slixfeed/task.py b/slixfeed/task.py index 6ebfd75..3a77b27 100644 --- a/slixfeed/task.py +++ b/slixfeed/task.py @@ -172,7 +172,7 @@ async def task_send(self, 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) - update_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + update_interval = Config.get_setting_value(self.settings, jid_bare, 'interval') update_interval = 60 * int(update_interval) last_update_time = sqlite.get_last_update_time(db_file) if last_update_time: @@ -232,7 +232,7 @@ def refresh_task(self, jid_bare, callback, key, val=None): 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) - val = self.settings[jid_bare][key] or self.settings['default'][key] + val = Config.get_setting_value(self.settings, jid_bare, key) # if self.task_manager[jid][key]: if jid_bare in self.task_manager: try: @@ -268,7 +268,7 @@ async def wait_and_run(self, callback, jid_bare, val): # TODO Take this function out of # -async def check_updates(self, jid): +async def check_updates(self, jid_bare): """ Start calling for update check up. @@ -277,15 +277,15 @@ async def check_updates(self, jid): jid : str Jabber ID. """ - logging.info('Scanning for updates for JID {}'.format(jid)) + logging.info('Scanning for updates for JID {}'.format(jid_bare)) while True: - jid_file = jid.replace('/', '_') + jid_file = jid_bare.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) urls = sqlite.get_active_feeds_url(db_file) for url in urls: - await action.scan(self, jid, db_file, url) + await action.scan(self, jid_bare, db_file, url) await asyncio.sleep(50) - val = self.settings['default']['check'] + val = Config.get_setting_value(self.settings, jid_bare, 'check') await asyncio.sleep(60 * float(val)) # Schedule to call this function again in 90 minutes # loop.call_at( diff --git a/slixfeed/version.py b/slixfeed/version.py index 4da4703..2f1666a 100644 --- a/slixfeed/version.py +++ b/slixfeed/version.py @@ -1,2 +1,2 @@ -__version__ = '0.1.36' -__version_info__ = (0, 1, 36) +__version__ = '0.1.37' +__version_info__ = (0, 1, 37) diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index 1095960..0bd575f 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -55,7 +55,7 @@ import slixfeed.xmpp.profile as profile from slixfeed.xmpp.roster import XmppRoster # import slixfeed.xmpp.service as service from slixfeed.xmpp.presence import XmppPresence -from slixfeed.xmpp.utility import get_chat_type +from slixfeed.xmpp.utility import get_chat_type, is_operator import sys import time @@ -70,6 +70,7 @@ import logging import os import slixfeed.action as action import slixfeed.config as config +from slixfeed.config import Config import slixfeed.crawl as crawl import slixfeed.dt as dt import slixfeed.fetch as fetch @@ -123,14 +124,18 @@ class Slixfeed(slixmpp.ClientXMPP): self.task_ping_instance = {} # Handler for configuration - self.settings = {} - # Populate handler - Config.add_settings_default(self.settings) - Config.add_settings_xmpp(self.settings) + self.settings = config.get_values('settings.toml') + # Handler for operators + self.operators = config.get_values('accounts.toml', 'xmpp')['operators'] + + # self.settings = {} + # # Populate dict handler + # Config.add_settings_default(self.settings) # Handlers for connection events self.connection_attempts = 0 self.max_connection_attempts = 10 + self.reconnect_timeout = config.get_values('accounts.toml', 'xmpp')['settings']['reconnect_timeout'] self.add_event_handler("session_start", self.on_session_start) @@ -261,16 +266,14 @@ class Slixfeed(slixmpp.ClientXMPP): function_name = sys._getframe().f_code.co_name message_log = '{}' logger.debug(message_log.format(function_name)) - jid_operator = config.get_value('accounts', 'XMPP', 'operator') - if jid_operator: - status_message = ('Wait while Slixfeed {} is being loaded...' - .format(__version__)) - XmppPresence.send(self, jid_operator, status_message) + 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, 'client') await self['xep_0115'].update_caps() # self.send_presence() - # await self.get_roster() + await self.get_roster() bookmarks = await self.plugin['xep_0048'].get_bookmarks() XmppGroupchat.autojoin(self, bookmarks) # XmppCommand.adhoc_commands(self) @@ -335,16 +338,16 @@ class Slixfeed(slixmpp.ClientXMPP): XmppPresence.send(self, jid_bare, status_message) else: # TODO Request for subscription - if (await get_chat_type(self, jid_bare) == 'chat' and - not self.client_roster[jid_bare]['to']): - XmppPresence.subscription(self, jid_bare, 'subscribe') - await XmppRoster.add(self, jid_bare) - status_message = '✒️ Share online status to receive updates' - XmppPresence.send(self, jid_bare, status_message) - message_subject = 'RSS News Bot' - message_body = 'Share online status to receive updates.' - XmppMessage.send_headline(self, jid_bare, message_subject, - message_body, 'chat') + # if (await get_chat_type(self, jid_bare) == 'chat' and + # not self.client_roster[jid_bare]['to']): + # XmppPresence.subscription(self, jid_bare, 'subscribe') + # await XmppRoster.add(self, jid_bare) + # status_message = '✒️ Share online status to receive updates' + # XmppPresence.send(self, jid_bare, status_message) + # message_subject = 'RSS News Bot' + # message_body = 'Share online status to receive updates.' + # XmppMessage.send_headline(self, jid_bare, message_subject, + # message_body, 'chat') await process.message(self, message) # chat_type = message["type"] # message_body = message["body"] @@ -671,13 +674,16 @@ class Slixfeed(slixmpp.ClientXMPP): # ) # NOTE https://codeberg.org/poezio/slixmpp/issues/3515 - # if jid == self.settings['xmpp']['operator']: + # if is_operator(self, jid_bare): self['xep_0050'].add_command(node='recent', name='📰️ Browse', handler=self._handle_recent) self['xep_0050'].add_command(node='subscription', name='🪶️ Subscribe', handler=self._handle_subscription_add) + self['xep_0050'].add_command(node='publish', + name='📣️ Publish', + handler=self._handle_publish) self['xep_0050'].add_command(node='subscriptions', name='🎫️ Subscriptions', handler=self._handle_subscriptions) @@ -712,6 +718,42 @@ class Slixfeed(slixmpp.ClientXMPP): # Special interface # http://jabber.org/protocol/commands#actions + async def _handle_publish(self, iq, session): + form = self['xep_0004'].make_form('form', 'Publish') + form['instructions'] = ('In order to publish via Pubsub Social Feed ' + '(XEP-0472), you will have to choose a ' + 'Publish-Subscribe (XEP-0060) hostname and ' + 'be permitted to publish into it.') + # TODO Select from list-multi + form.add_field(var='subscription', + ftype='text-single', + label='URL', + desc='Enter subscription URL.', + value='http://', + required=True) + form.add_field(var='subscription', + ftype='text-single', + label='PubSub', + desc='Enter a PubSub URL.', + value='pubsub.' + self.boundjid.host, + required=True) + session['allow_prev'] = False + session['has_next'] = True + session['next'] = self._handle_preview + session['prev'] = None + session['payload'] = form + return session + + def _handle_preview(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)) + text_note = ('XEP-0472: Pubsub Social Feed will be available soon.') + session['notes'] = [['info', text_note]] + session['payload'] = None + return session + async def _handle_profile(self, iq, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -747,42 +789,42 @@ class Slixfeed(slixmpp.ClientXMPP): value=unread) form.add_field(ftype='fixed', value='Options') - key_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] + key_archive = Config.get_setting_value(self.settings, jid_bare, 'archive') key_archive = str(key_archive) form.add_field(label='Archive', ftype='text-single', value=key_archive) - key_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] + key_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled') key_enabled = str(key_enabled) form.add_field(label='Enabled', ftype='text-single', value=key_enabled) - key_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + key_interval = Config.get_setting_value(self.settings, jid_bare, 'interval') key_interval = str(key_interval) form.add_field(label='Interval', ftype='text-single', value=key_interval) - key_length = self.settings[jid_bare]['length'] or self.settings['default']['length'] + key_length = Config.get_setting_value(self.settings, jid_bare, 'length') key_length = str(key_length) form.add_field(label='Length', ftype='text-single', value=key_length) - key_media = self.settings[jid_bare]['media'] or self.settings['default']['media'] + key_media = Config.get_setting_value(self.settings, jid_bare, 'media') key_media = str(key_media) form.add_field(label='Media', ftype='text-single', value=key_media) - key_old = self.settings[jid_bare]['old'] or self.settings['default']['old'] + key_old = Config.get_setting_value(self.settings, jid_bare, 'old') key_old = str(key_old) form.add_field(label='Old', ftype='text-single', value=key_old) - key_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] + key_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum') key_quantum = str(key_quantum) form.add_field(label='Quantum', ftype='text-single', value=key_quantum) - update_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + update_interval = Config.get_setting_value(self.settings, jid_bare, 'interval') update_interval = str(update_interval) update_interval = 60 * int(update_interval) last_update_time = sqlite.get_last_update_time(db_file) @@ -797,7 +839,7 @@ class Slixfeed(slixmpp.ClientXMPP): else: next_update = 'n/a' else: - last_update_time = 'n/a' + last_update = 'n/a' next_update = 'n/a' form.add_field(ftype='fixed', value='Schedule') @@ -1394,8 +1436,10 @@ class Slixfeed(slixmpp.ClientXMPP): .format(function_name, jid_full)) jid_bare = session['from'].bare chat_type = await get_chat_type(self, jid_bare) + moderator = None if chat_type == 'groupchat': moderator = is_moderator(self, jid_bare, jid_full) + # moderator = moderator if moderator else None if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Discover & Search') form['instructions'] = 'Discover news subscriptions of all kinds' @@ -1557,10 +1601,10 @@ 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[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[0] - url = subscription[1] + title = subscription[1] + url = subscription[2] options.addOption(title, url) session['has_next'] = True session['next'] = self._handle_subscription_editor @@ -1576,10 +1620,10 @@ class Slixfeed(slixmpp.ClientXMPP): desc=('Select subscriptions to remove.'), required=True) subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[0] - ix = str(subscription[2]) + title = subscription[1] + ix = str(subscription[0]) options.addOption(title, ix) session['cancel'] = self._handle_cancel session['has_next'] = False @@ -1693,8 +1737,8 @@ class Slixfeed(slixmpp.ClientXMPP): # subscriptions = set(subscriptions) categorized_subscriptions = {} for subscription in subscriptions: - title = subscription[0] - url = subscription[1] + title = subscription[1] + url = subscription[2] try: letter = title[0].capitalize() if letter not in categorized_subscriptions: @@ -1906,10 +1950,10 @@ class Slixfeed(slixmpp.ClientXMPP): 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[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[0] - url = subscription[1] + title = subscription[1] + url = subscription[2] options.addOption(title, url) # options = form.add_field(var='action', # ftype='list-single', @@ -1949,7 +1993,7 @@ class Slixfeed(slixmpp.ClientXMPP): options.addOption('Import', 'import') options.addOption('Export', 'export') jid = session['from'].bare - if jid == self.settings['xmpp']['operator']: + if is_operator(self, jid): options.addOption('Administration', 'admin') session['payload'] = form session['next'] = self._handle_advanced_result @@ -1976,7 +2020,7 @@ class Slixfeed(slixmpp.ClientXMPP): # NOTE Even though this check is already conducted on previous # form, this check is being done just in case. jid_bare = session['from'].bare - if jid_bare == self.settings['xmpp']['operator']: + if is_operator(self, jid_bare): if self.is_component: # NOTE This will be changed with XEP-0222 XEP-0223 text_info = ('Subscriber management options are ' @@ -2076,9 +2120,10 @@ class Slixfeed(slixmpp.ClientXMPP): options = form.add_field(var='option', ftype='list-single', label='About', - required=True) + required=True, + value='about') config_dir = config.get_default_config_directory() - with open(config_dir + '/' + 'information.toml', mode="rb") as information: + with open(config_dir + '/' + 'about.toml', mode="rb") as information: entries = tomllib.load(information) for entry in entries: label = entries[entry][0]['title'] @@ -2096,7 +2141,7 @@ class Slixfeed(slixmpp.ClientXMPP): logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) config_dir = config.get_default_config_directory() - with open(config_dir + '/' + 'information.toml', mode="rb") as information: + with open(config_dir + '/' + 'about.toml', mode="rb") as information: entries = tomllib.load(information) entry_key = payload['values']['option'] # case 'terms': @@ -2174,7 +2219,7 @@ class Slixfeed(slixmpp.ClientXMPP): cmds = tomllib.load(commands) form = self['xep_0004'].make_form('result', 'Manual') - form['instructions'] = '🛟️ Help manual for interactive chat' + form['instructions'] = 'Help manual for interactive chat' # text = '🛟️ Help and Information about Slixfeed\n\n' # for cmd in cmds: @@ -2350,7 +2395,7 @@ class Slixfeed(slixmpp.ClientXMPP): 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[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) form = self['xep_0004'].make_form('form', 'Subscriptions') match payload['values']['action']: case 'bookmarks': @@ -2680,7 +2725,7 @@ class Slixfeed(slixmpp.ClientXMPP): Config.add_settings_jid(self.settings, jid_bare, db_file) form = self['xep_0004'].make_form('form', 'Settings') form['instructions'] = 'Editing settings' - value = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] + value = Config.get_setting_value(self.settings, jid_bare, 'enabled') value = str(value) value = int(value) if value: @@ -2692,7 +2737,7 @@ class Slixfeed(slixmpp.ClientXMPP): label='Enabled', desc='Enable news updates.', value=value) - value = self.settings[jid_bare]['media'] or self.settings['default']['media'] + value = Config.get_setting_value(self.settings, jid_bare, 'media') value = str(value) value = int(value) if value: @@ -2704,7 +2749,7 @@ class Slixfeed(slixmpp.ClientXMPP): desc='Send audio, images or videos if found.', label='Display media', value=value) - value = self.settings[jid_bare]['old'] or self.settings['default']['old'] + value = Config.get_setting_value(self.settings, jid_bare, 'old') value = str(value) value = int(value) if value: @@ -2717,7 +2762,7 @@ class Slixfeed(slixmpp.ClientXMPP): # label='Send only new items', label='Include old news', value=value) - value = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + value = Config.get_setting_value(self.settings, jid_bare, 'interval') value = str(value) value = int(value) value = value/60 @@ -2738,7 +2783,7 @@ class Slixfeed(slixmpp.ClientXMPP): i += 6 else: i += 1 - value = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] + value = Config.get_setting_value(self.settings, jid_bare, 'quantum') value = str(value) options = form.add_field(var='quantum', ftype='list-single', @@ -2752,7 +2797,7 @@ class Slixfeed(slixmpp.ClientXMPP): x = str(i) options.addOption(x, x) i += 1 - value = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] + value = Config.get_setting_value(self.settings, jid_bare, 'archive') value = str(value) options = form.add_field(var='archive', ftype='list-single', @@ -2807,7 +2852,7 @@ class Slixfeed(slixmpp.ClientXMPP): if val < 1: val = 1 val = val * 60 - is_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] + is_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled') if (key == 'enabled' and val == 1 and diff --git a/slixfeed/xmpp/component.py b/slixfeed/xmpp/component.py index dfdab5b..27a21a0 100644 --- a/slixfeed/xmpp/component.py +++ b/slixfeed/xmpp/component.py @@ -36,7 +36,7 @@ from slixfeed.log import Logger from slixfeed.version import __version__ from slixfeed.xmpp.connect import XmppConnect # NOTE MUC is possible for component -# from slixfeed.xmpp.muc import XmppGroupchat +from slixfeed.xmpp.muc import XmppGroupchat from slixfeed.xmpp.message import XmppMessage import slixfeed.xmpp.process as process import slixfeed.xmpp.profile as profile @@ -46,7 +46,7 @@ from slixfeed.xmpp.presence import XmppPresence # from slixmpp.xmlstream import ET # from slixmpp.xmlstream.handler import Callback # from slixmpp.xmlstream.matcher import MatchXPath -from slixfeed.xmpp.utility import get_chat_type +from slixfeed.xmpp.utility import get_chat_type, is_operator import sys import time @@ -115,14 +115,18 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): self.task_ping_instance = {} # Handler for configuration - self.settings = {} - # Populate handler - Config.add_settings_default(self.settings) - Config.add_settings_xmpp(self.settings) + self.settings = config.get_values('settings.toml') + # Handler for operators + self.operators = config.get_values('accounts.toml', 'xmpp')['operators'] + + # self.settings = {} + # # Populate dict handler + # Config.add_settings_default(self.settings) # Handlers for connection events self.connection_attempts = 0 self.max_connection_attempts = 10 + self.reconnect_timeout = config.get_values('accounts.toml', 'xmpp')['settings']['reconnect_timeout'] self.add_event_handler("session_start", self.on_session_start) @@ -155,10 +159,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): self.add_event_handler("message", self.on_message) - # self.add_event_handler("groupchat_invite", - # self.on_groupchat_invite) # XEP_0045 - # self.add_event_handler("groupchat_direct_invite", - # self.on_groupchat_direct_invite) # XEP_0249 + self.add_event_handler("groupchat_invite", + self.on_groupchat_invite) # XEP_0045 + self.add_event_handler("groupchat_direct_invite", + self.on_groupchat_direct_invite) # XEP_0249 # self.add_event_handler("groupchat_message", self.message) # self.add_event_handler("disconnected", self.reconnect) @@ -184,19 +188,19 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): self.on_session_end) - # async def on_groupchat_invite(self, message): + async def on_groupchat_invite(self, message): # logging.warning("on_groupchat_invite") - # inviter = message['from'].bare - # muc_jid = message['groupchat_invite']['jid'] - # await muc.join(self, inviter, muc_jid) + inviter = message['from'].bare + muc_jid = message['groupchat_invite']['jid'] + XmppGroupchat.join(self, inviter, muc_jid) # await bookmark.add(self, muc_jid) # NOTE Tested with Gajim and Psi - # async def on_groupchat_direct_invite(self, message): - # inviter = message['from'].bare - # muc_jid = message['groupchat_invite']['jid'] - # await muc.join(self, inviter, muc_jid) + async def on_groupchat_direct_invite(self, message): + inviter = message['from'].bare + muc_jid = message['groupchat_invite']['jid'] + XmppGroupchat.join(self, inviter, muc_jid) # await bookmark.add(self, muc_jid) @@ -234,10 +238,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): task.task_ping(self) # bookmarks = await self.plugin['xep_0048'].get_bookmarks() # XmppGroupchat.autojoin(self, bookmarks) - jid_operator = config.get_value('accounts', 'XMPP', 'operator') - if jid_operator: - status_message = 'Slixfeed version {}'.format(__version__) - XmppPresence.send(self, jid_operator, status_message) + status_message = 'Slixfeed version {}'.format(__version__) + for operator in self.operators: + XmppPresence.send(self, operator['jid'], status_message) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -346,7 +349,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if not self.client_roster[jid_bare]['to']: # XmppPresence.subscription(self, jid, 'subscribe') XmppPresence.subscription(self, jid_bare, 'subscribed') - await XmppRoster.add(self, jid_bare) + # await XmppRoster.add(self, jid_bare) status_message = '✒️ Share online status to receive updates' XmppPresence.send(self, jid_bare, status_message) message_subject = 'RSS News Bot' @@ -418,9 +421,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # status_message = None XmppMessage.send(self, jid_bare, message_body, 'chat') XmppPresence.subscription(self, jid_bare, 'unsubscribed') - # XmppPresence.send(self, jid, status_message, + # status_message = '🖋️ You have been uubscribed' + # XmppPresence.send(self, jid_bare, status_message, # presence_type='unsubscribed') - XmppRoster.remove(self, jid_bare) + # XmppRoster.remove(self, jid_bare) time_end = time.time() difference = time_end - time_begin if difference > 1: logger.warning('{} (time: {})'.format(function_name, @@ -631,13 +635,16 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # ) # NOTE https://codeberg.org/poezio/slixmpp/issues/3515 - # if jid == self.settings['xmpp']['operator']: + # if is_operator(self, jid_bare): self['xep_0050'].add_command(node='recent', name='📰️ Browse', handler=self._handle_recent) self['xep_0050'].add_command(node='subscription', name='🪶️ Subscribe', handler=self._handle_subscription_add) + self['xep_0050'].add_command(node='publish', + name='📣️ Publish', + handler=self._handle_publish) self['xep_0050'].add_command(node='subscriptions', name='🎫️ Subscriptions', handler=self._handle_subscriptions) @@ -672,6 +679,42 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # Special interface # http://jabber.org/protocol/commands#actions + async def _handle_publish(self, iq, session): + form = self['xep_0004'].make_form('form', 'Publish') + form['instructions'] = ('In order to publish via Pubsub Social Feed ' + '(XEP-0472), you will have to choose a ' + 'Publish-Subscribe (XEP-0060) hostname and ' + 'be permitted to publish into it.') + # TODO Select from list-multi + form.add_field(var='subscription', + ftype='text-single', + label='URL', + desc='Enter subscription URL.', + value='http://', + required=True) + form.add_field(var='subscription', + ftype='text-single', + label='PubSub', + desc='Enter a PubSub URL.', + value='pubsub.' + self.boundjid.host, + required=True) + session['allow_prev'] = False + session['has_next'] = True + session['next'] = self._handle_preview + session['prev'] = None + session['payload'] = form + return session + + def _handle_preview(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)) + text_note = ('XEP-0472: Pubsub Social Feed will be available soon.') + session['notes'] = [['info', text_note]] + session['payload'] = None + return session + async def _handle_profile(self, iq, session): jid_full = str(session['from']) function_name = sys._getframe().f_code.co_name @@ -707,42 +750,42 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): value=unread) form.add_field(ftype='fixed', value='Options') - key_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] + key_archive = Config.get_setting_value(self.settings, jid_bare, 'archive') key_archive = str(key_archive) form.add_field(label='Archive', ftype='text-single', value=key_archive) - key_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] + key_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled') key_enabled = str(key_enabled) form.add_field(label='Enabled', ftype='text-single', value=key_enabled) - key_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + key_interval = Config.get_setting_value(self.settings, jid_bare, 'interval') key_interval = str(key_interval) form.add_field(label='Interval', ftype='text-single', value=key_interval) - key_length = self.settings[jid_bare]['length'] or self.settings['default']['length'] + key_length = Config.get_setting_value(self.settings, jid_bare, 'length') key_length = str(key_length) form.add_field(label='Length', ftype='text-single', value=key_length) - key_media = self.settings[jid_bare]['media'] or self.settings['default']['media'] + key_media = Config.get_setting_value(self.settings, jid_bare, 'media') key_media = str(key_media) form.add_field(label='Media', ftype='text-single', value=key_media) - key_old = self.settings[jid_bare]['old'] or self.settings['default']['old'] + key_old = Config.get_setting_value(self.settings, jid_bare, 'old') key_old = str(key_old) form.add_field(label='Old', ftype='text-single', value=key_old) - key_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] + key_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum') key_quantum = str(key_quantum) form.add_field(label='Quantum', ftype='text-single', value=key_quantum) - update_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + update_interval = Config.get_setting_value(self.settings, jid_bare, 'interval') update_interval = str(update_interval) update_interval = 60 * int(update_interval) last_update_time = sqlite.get_last_update_time(db_file) @@ -757,7 +800,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): else: next_update = 'n/a' else: - last_update_time = 'n/a' + last_update = 'n/a' next_update = 'n/a' form.add_field(ftype='fixed', value='Schedule') @@ -1354,8 +1397,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): .format(function_name, jid_full)) jid_bare = session['from'].bare chat_type = await get_chat_type(self, jid_bare) + moderator = None if chat_type == 'groupchat': moderator = is_moderator(self, jid_bare, jid_full) + # moderator = moderator if moderator else None if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Discover & Search') form['instructions'] = 'Discover news subscriptions of all kinds' @@ -1517,10 +1562,10 @@ 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[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[0] - url = subscription[1] + title = subscription[1] + url = subscription[2] options.addOption(title, url) session['has_next'] = True session['next'] = self._handle_subscription_editor @@ -1536,10 +1581,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): desc=('Select subscriptions to remove.'), required=True) subscriptions = sqlite.get_feeds(db_file) - subscriptions = sorted(subscriptions, key=lambda x: x[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[0] - ix = str(subscription[2]) + title = subscription[1] + ix = str(subscription[0]) options.addOption(title, ix) session['cancel'] = self._handle_cancel session['has_next'] = False @@ -1653,8 +1698,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # subscriptions = set(subscriptions) categorized_subscriptions = {} for subscription in subscriptions: - title = subscription[0] - url = subscription[1] + title = subscription[1] + url = subscription[2] try: letter = title[0].capitalize() if letter not in categorized_subscriptions: @@ -1866,10 +1911,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): 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[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) for subscription in subscriptions: - title = subscription[0] - url = subscription[1] + title = subscription[1] + url = subscription[2] options.addOption(title, url) # options = form.add_field(var='action', # ftype='list-single', @@ -1909,7 +1954,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): options.addOption('Import', 'import') options.addOption('Export', 'export') jid = session['from'].bare - if jid == self.settings['xmpp']['operator']: + if is_operator(self, jid): options.addOption('Administration', 'admin') session['payload'] = form session['next'] = self._handle_advanced_result @@ -1936,7 +1981,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # NOTE Even though this check is already conducted on previous # form, this check is being done just in case. jid_bare = session['from'].bare - if jid_bare == self.settings['xmpp']['operator']: + if is_operator(self, jid_bare): if self.is_component: # NOTE This will be changed with XEP-0222 XEP-0223 text_info = ('Subscriber management options are ' @@ -2036,9 +2081,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): options = form.add_field(var='option', ftype='list-single', label='About', - required=True) + required=True, + value='about') config_dir = config.get_default_config_directory() - with open(config_dir + '/' + 'information.toml', mode="rb") as information: + with open(config_dir + '/' + 'about.toml', mode="rb") as information: entries = tomllib.load(information) for entry in entries: label = entries[entry][0]['title'] @@ -2056,7 +2102,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): logger.debug('{}: jid_full: {}' .format(function_name, jid_full)) config_dir = config.get_default_config_directory() - with open(config_dir + '/' + 'information.toml', mode="rb") as information: + with open(config_dir + '/' + 'about.toml', mode="rb") as information: entries = tomllib.load(information) entry_key = payload['values']['option'] # case 'terms': @@ -2086,7 +2132,6 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): e_key = e_key.capitalize() # form.add_field(ftype='fixed', # value=e_val) - print(type(e_val)) if e_key == 'Name': form.add_field(ftype='fixed', value=e_val) @@ -2135,7 +2180,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): cmds = tomllib.load(commands) form = self['xep_0004'].make_form('result', 'Manual') - form['instructions'] = '🛟️ Help manual for interactive chat' + form['instructions'] = 'Help manual for interactive chat' # text = '🛟️ Help and Information about Slixfeed\n\n' # for cmd in cmds: @@ -2311,7 +2356,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): 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[0]) + subscriptions = sorted(subscriptions, key=lambda x: x[1]) form = self['xep_0004'].make_form('form', 'Subscriptions') match payload['values']['action']: case 'bookmarks': @@ -2641,7 +2686,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): Config.add_settings_jid(self.settings, jid_bare, db_file) form = self['xep_0004'].make_form('form', 'Settings') form['instructions'] = 'Editing settings' - value = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] + value = Config.get_setting_value(self.settings, jid_bare, 'enabled') value = str(value) value = int(value) if value: @@ -2653,7 +2698,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): label='Enabled', desc='Enable news updates.', value=value) - value = self.settings[jid_bare]['media'] or self.settings['default']['media'] + value = Config.get_setting_value(self.settings, jid_bare, 'media') value = str(value) value = int(value) if value: @@ -2665,7 +2710,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): desc='Send audio, images or videos if found.', label='Display media', value=value) - value = self.settings[jid_bare]['old'] or self.settings['default']['old'] + value = Config.get_setting_value(self.settings, jid_bare, 'old') value = str(value) value = int(value) if value: @@ -2678,7 +2723,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): # label='Send only new items', label='Include old news', value=value) - value = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] + value = Config.get_setting_value(self.settings, jid_bare, 'interval') value = str(value) value = int(value) value = value/60 @@ -2699,7 +2744,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): i += 6 else: i += 1 - value = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] + value = Config.get_setting_value(self.settings, jid_bare, 'quantum') value = str(value) options = form.add_field(var='quantum', ftype='list-single', @@ -2713,7 +2758,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): x = str(i) options.addOption(x, x) i += 1 - value = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] + value = Config.get_setting_value(self.settings, jid_bare, 'archive') value = str(value) options = form.add_field(var='archive', ftype='list-single', @@ -2768,7 +2813,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP): if val < 1: val = 1 val = val * 60 - is_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] + is_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled') if (key == 'enabled' and val == 1 and diff --git a/slixfeed/xmpp/connect.py b/slixfeed/xmpp/connect.py index a3eb29b..0d8af4a 100644 --- a/slixfeed/xmpp/connect.py +++ b/slixfeed/xmpp/connect.py @@ -3,6 +3,13 @@ """ +FIXME + +Message from OpenFire server log. + +2024.03.12 14:21:22.518 ERROR [nioEventLoopGroup-3-2]: org.jivesoftware.openfire.IQRouter - Unable to process a stanza that has no 'from' attribute, addressed to a remote entity. Stanza is being dropped: + + TODO 1) Check interval, and if no connection is establish after 30 seconds @@ -38,21 +45,23 @@ class XmppConnect: None. """ + jid_from = str(self.boundjid) if self.is_component else None if not jid: jid = self.boundjid.bare while True: rtt = None try: - rtt = await self['xep_0199'].ping(jid, timeout=10) + rtt = await self['xep_0199'].ping(jid, + ifrom=jid_from, + timeout=10) logging.info('Success! RTT: %s', rtt) except IqError as e: - logging.info('Error pinging %s: %s', - jid, - e.iq['error']['condition']) + logging.error('Error pinging %s: %s', jid, + e.iq['error']['condition']) except IqTimeout: - logging.info('No response from %s', jid) + logging.warning('No response from %s', jid) if not rtt: - logging.info('Disconnecting...') + logging.warning('Disconnecting...') self.disconnect() break await asyncio.sleep(60 * 1) @@ -68,7 +77,7 @@ class XmppConnect: # print(current_time(),"Maximum connection attempts exceeded.") # logging.error("Maximum connection attempts exceeded.") print(current_time(), 'Attempt number', self.connection_attempts) - seconds = (get_value('accounts', 'XMPP', 'reconnect_timeout')) or 30 + seconds = self.reconnect_timeout or 30 seconds = int(seconds) print(current_time(), 'Next attempt within', seconds, 'seconds') # NOTE asyncio.sleep doesn't interval as expected diff --git a/slixfeed/xmpp/iq.py b/slixfeed/xmpp/iq.py new file mode 100644 index 0000000..b139e36 --- /dev/null +++ b/slixfeed/xmpp/iq.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from slixmpp.exceptions import IqError + +class XmppIQ: + + async def send(self, iq): + try: + await iq.send(timeout=5) + except IqError as e: + if e.etype == 'cancel' and e.condition == 'conflict': + return + raise diff --git a/slixfeed/xmpp/message.py b/slixfeed/xmpp/message.py index be94f42..9f4c002 100644 --- a/slixfeed/xmpp/message.py +++ b/slixfeed/xmpp/message.py @@ -34,15 +34,17 @@ class XmppMessage: def send(self, jid, message_body, chat_type): + jid_from = str(self.boundjid) if self.is_component else None self.send_message(mto=jid, - mfrom=self.boundjid.bare, + mfrom=jid_from, mbody=message_body, mtype=chat_type) def send_headline(self, jid, message_subject, message_body, chat_type): + jid_from = str(self.boundjid) if self.is_component else None self.send_message(mto=jid, - mfrom=self.boundjid.bare, + mfrom=jid_from, # mtype='headline', msubject=message_subject, mbody=message_body, @@ -58,13 +60,14 @@ class XmppMessage: # } # return saxutils.escape(raw_string, escape_map) def send_oob(self, jid, url, chat_type): + jid_from = str(self.boundjid) if self.is_component else None url = saxutils.escape(url) # try: html = ( f'' f'{url}') message = self.make_message(mto=jid, - mfrom=self.boundjid.bare, + mfrom=jid_from, mbody=url, mhtml=html, mtype=chat_type) diff --git a/slixfeed/xmpp/muc.py b/slixfeed/xmpp/muc.py index c9e9cc2..6a645dc 100644 --- a/slixfeed/xmpp/muc.py +++ b/slixfeed/xmpp/muc.py @@ -42,8 +42,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, ) @@ -83,8 +85,10 @@ class XmppGroupchat: 'JID : {}\n' '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, ) diff --git a/slixfeed/xmpp/process.py b/slixfeed/xmpp/process.py index 45d0d0e..6a4c6b0 100644 --- a/slixfeed/xmpp/process.py +++ b/slixfeed/xmpp/process.py @@ -23,6 +23,9 @@ TODO """ +from slixfeed.xmpp.publish import XmppPubsub +from slixfeed.xmpp.iq import XmppIQ + import asyncio import logging import os @@ -40,9 +43,14 @@ from slixfeed.xmpp.muc import XmppGroupchat from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.upload import XmppUpload -from slixfeed.xmpp.utility import get_chat_type, is_moderator +from slixfeed.xmpp.utility import get_chat_type, is_moderator, is_operator import time +try: + import tomllib +except: + import tomli as tomllib + # for task in main_task: # task.cancel() @@ -181,7 +189,7 @@ async def message(self, message): response = None match message_lowercase: # case 'breakpoint': - # if jid == get_value('accounts', 'XMPP', 'operator'): + # if is_operator(self, jid_bare): # breakpoint() # print('task_manager[jid]') # print(task_manager[jid]) @@ -244,21 +252,25 @@ async def message(self, message): 'or command key & name') XmppMessage.send_reply(self, message, response) case 'info': - command_list = action.manual('information.toml') + config_dir = config.get_default_config_directory() + with open(config_dir + '/' + 'information.toml', mode="rb") as information: + entries = tomllib.load(information) response = ('Available command options:\n' '```\n{}\n```\n' 'Usage: `info