diff --git a/slixfeed/__main__.py b/slixfeed/__main__.py index 5982de0..03ef0cc 100644 --- a/slixfeed/__main__.py +++ b/slixfeed/__main__.py @@ -113,6 +113,7 @@ class JabberComponent: xmpp.register_plugin('xep_0084') # User Avatar xmpp.register_plugin('xep_0085') # Chat State Notifications xmpp.register_plugin('xep_0115') # Entity Capabilities + xmpp.register_plugin('xep_0122') # Data Forms Validation xmpp.register_plugin('xep_0153') # vCard-Based Avatars xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping xmpp.register_plugin('xep_0249') # Direct MUC Invitations @@ -127,7 +128,6 @@ class JabberClient: def __init__(self, jid, password, hostname=None, port=None, alias=None): xmpp = Slixfeed(jid, password, hostname, port, alias) xmpp.register_plugin('xep_0004') # Data Forms - xmpp.register_plugin('xep_0122') # Data Forms Validation xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0045') # Multi-User Chat xmpp.register_plugin('xep_0048') # Bookmarks @@ -140,6 +140,7 @@ class JabberClient: xmpp.register_plugin('xep_0084') # User Avatar xmpp.register_plugin('xep_0085') # Chat State Notifications xmpp.register_plugin('xep_0115') # Entity Capabilities + xmpp.register_plugin('xep_0122') # Data Forms Validation xmpp.register_plugin('xep_0153') # vCard-Based Avatars xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping xmpp.register_plugin('xep_0249') # Direct MUC Invitations diff --git a/slixfeed/assets/information.toml b/slixfeed/assets/information.toml index 35bedca..8b2e159 100644 --- a/slixfeed/assets/information.toml +++ b/slixfeed/assets/information.toml @@ -1,13 +1,15 @@ about = """ -Slixfeed -A Syndication bot for the XMPP communication network. +Slixfeed is a news broker bot for syndicated news which aims to be \ +an easy to use and fully-featured news aggregator bot. -Slixfeed is a news broker which aims to be an easy to use and fully-\ -featured news aggregator bot. It provides a convenient access to \ -Blogs, News websites and even Fediverse instances, along with \ -filtering functionality. -Slixfeed is primarily designed for XMPP (aka Jabber). \ -Visit https://xmpp.org/software/ for more information. +Slixfeed provides a convenient access to Blogs, News websites and \ +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. + +https://gitgud.io/sjehuda/slixfeed """ authors = """ @@ -58,7 +60,8 @@ No operator was specified for this instance. platforms = """ Supported platforms: XMPP -Platforms to be added in future: ActivityPub, Briar, Email, IRC, LXMF, Matrix, MQTT, Nostr, Session, Tox. +Platforms to be added in future: ActivityPub, Briar, Email, IRC, LXMF, \ +Matrix, MQTT, Nostr, Session, Tox. For ideal experience, we recommend using XMPP. """ @@ -86,6 +89,24 @@ XMPP https://xmpp.org/about/ """ +sleekxmpp = """ +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. + +https://codeberg.org/fritzy/SleekXMPP +""" + +slixmpp = """ +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. + +https://codeberg.org/poezio/slixmpp +""" + terms = """ Slixfeed is free software; you can redistribute it and/or \ modify it under the terms of the MIT License. @@ -129,6 +150,7 @@ Raphael Groner (Fedora, Germany); \ Remko Tronçon (Psi , Belgium); \ Simone "roughnecks" Canaletti (Italy); \ Richard Lapointe (SalixOS, Connecticut); \ +Stephen Paul Weber ; \ Strix from Loqi; \ Thibaud Guerin (SalixOS); \ Thorsten Fröhlich (France); \ @@ -144,6 +166,5 @@ chat, voice and video calls, collaboration, lightweight \ middleware, content syndication, and generalized routing of XML \ data. -Visit https://xmpp.org/about/ for more information on the XMPP \ -protocol. +https://xmpp.org/about/ """ diff --git a/slixfeed/version.py b/slixfeed/version.py index 3a7b545..130c7f0 100644 --- a/slixfeed/version.py +++ b/slixfeed/version.py @@ -1,2 +1,2 @@ -__version__ = '0.1.6' -__version_info__ = (0, 1, 6) +__version__ = '0.1.7' +__version_info__ = (0, 1, 7) diff --git a/slixfeed/xmpp/bookmark.py b/slixfeed/xmpp/bookmark.py index 921132a..65e487a 100644 --- a/slixfeed/xmpp/bookmark.py +++ b/slixfeed/xmpp/bookmark.py @@ -44,6 +44,8 @@ class XmppBookmark: groupchats.extend([conference]) if properties: properties['jid'] = properties['room'] + '@' + properties['host'] + if not properties['alias']: properties['alias'] = self.alias + else: properties = { 'jid' : jid, diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index a9d65a7..2bd1ab4 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -167,7 +167,7 @@ class Slixfeed(slixmpp.ClientXMPP): inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) - await XmppGroupchat.join(self, inviter, muc_jid) + 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' @@ -181,7 +181,7 @@ class Slixfeed(slixmpp.ClientXMPP): inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) - await XmppGroupchat.join(self, inviter, muc_jid) + 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' @@ -207,22 +207,23 @@ class Slixfeed(slixmpp.ClientXMPP): self.service_reactions() await self['xep_0115'].update_caps() await self.get_roster() - await XmppGroupchat.autojoin(self) await profile.update(self) task.task_ping(self) + bookmarks = await self.plugin['xep_0048'].get_bookmarks() + XmppGroupchat.autojoin(self, bookmarks) # Service.commands(self) # Service.reactions(self) - async def on_session_resumed(self, event): + def on_session_resumed(self, event): # self.send_presence() profile.set_identity(self, 'client') # self.service_commands() # self.service_reactions() self['xep_0115'].update_caps() - await XmppGroupchat.autojoin(self) + XmppGroupchat.autojoin(self) # Service.commands(self) # Service.reactions(self) @@ -358,7 +359,7 @@ class Slixfeed(slixmpp.ClientXMPP): return if message['type'] in ('chat', 'normal'): # NOTE: Required for Cheogram - await self['xep_0115'].update_caps(jid=jid) + # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) # task.clean_tasks_xmpp(self, jid, ['status']) await asyncio.sleep(5) @@ -369,7 +370,7 @@ class Slixfeed(slixmpp.ClientXMPP): if message['type'] in ('chat', 'normal'): jid = message['from'].bare # NOTE: Required for Cheogram - await self['xep_0115'].update_caps(jid=jid) + # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) # task.clean_tasks_xmpp(self, jid, ['status']) await asyncio.sleep(5) @@ -466,36 +467,42 @@ class Slixfeed(slixmpp.ClientXMPP): # ) # if jid == config.get_value('accounts', 'XMPP', 'operator'): - self['xep_0050'].add_command(node='settings', - name='📮️ Edit settings', - handler=self._handle_settings) - self['xep_0050'].add_command(node='filters', - name='🕸️ Manage filters', - handler=self._handle_filters) - self['xep_0050'].add_command(node='bookmarks', - name='📔️ Organize bookmarks - Restricted', - handler=self._handle_bookmarks) - self['xep_0050'].add_command(node='roster', - name='🧾️ Organize roster - Restricted', - handler=self._handle_roster) self['xep_0050'].add_command(node='subscriptions', - name='📰️ Subscriptions - All', + name='📰️ Subscriptions', handler=self._handle_subscriptions) self['xep_0050'].add_command(node='subscriptions_cat', - name='🔖️ Subscriptions - Categories', + name='🔖️ Categories', handler=self._handle_subscription) self['xep_0050'].add_command(node='subscriptions_tag', - name='🏷️ Subscriptions - Tags', + name='🏷️ Tags', handler=self._handle_subscription) self['xep_0050'].add_command(node='subscriptions_index', - name='📑️ Subscriptions - Indexed', + name='📑️ Index (A - Z)', handler=self._handle_subscription) - self['xep_0050'].add_command(node='credit', - name='💡️ Credit', - handler=self._handle_credit) + self['xep_0050'].add_command(node='settings', + name='📮️ Settings', + handler=self._handle_settings) + self['xep_0050'].add_command(node='filters', + name='🛡️ Filters', + handler=self._handle_filters) + self['xep_0050'].add_command(node='bookmarks', + name='📕 Bookmarks', + handler=self._handle_bookmarks) + self['xep_0050'].add_command(node='roster', + name='📓 Roster', # 📋 + handler=self._handle_roster) self['xep_0050'].add_command(node='help', - name='🛟️ Help', + name='📔️ Manual', handler=self._handle_help) + self['xep_0050'].add_command(node='motd', + name='🗓️ MOTD', + handler=self._handle_motd) + self['xep_0050'].add_command(node='credit', + name='Credits', # 💡️ + handler=self._handle_credit) + self['xep_0050'].add_command(node='about', + name='About', # 📜️ + handler=self._handle_about) # self['xep_0050'].add_command(node='search', # name='Search', # handler=self._handle_search) @@ -547,7 +554,7 @@ class Slixfeed(slixmpp.ClientXMPP): jid = session['from'].bare form = self['xep_0004'].make_form('form', 'Filters for {}'.format(jid)) - form['instructions'] = ('🛡️ Filters have been updated') + form['instructions'] = ('✅️ Filters have been updated') jid_file = jid db_file = config.get_pathname_to_database(jid_file) # In this case (as is typical), the payload is a form @@ -769,18 +776,55 @@ class Slixfeed(slixmpp.ClientXMPP): pass + async def _handle_about(self, iq, session): + import slixfeed.action as action + # form = self['xep_0004'].make_form('result', 'Thanks') + # form['instructions'] = action.manual('information.toml', 'thanks') + # session['payload'] = form + # text = '💡️ About Slixfeed, slixmpp and XMPP\n\n' + # text += '\n\n' + # form = self['xep_0004'].make_form('result', 'About') + text = 'Slixfeed\n\n' + text += ''.join(action.manual('information.toml', 'about')) + text += '\n\n' + text += 'Slixmpp\n\n' + text += ''.join(action.manual('information.toml', 'slixmpp')) + text += '\n\n' + text += 'SleekXMPP\n\n' + text += ''.join(action.manual('information.toml', 'sleekxmpp')) + text += '\n\n' + text += 'XMPP\n\n' + text += ''.join(action.manual('information.toml', 'xmpp')) + session['notes'] = [['info', text]] + # form.add_field(var='about', + # ftype='text-multi', + # label='About', + # value=text) + # session['payload'] = form + return session + + + async def _handle_motd(self, iq, session): + # TODO add functionality to attach image. + text = ('Here you can add groupchat rules,post schedule, tasks or ' + 'anything elaborated you might deem fit. Good luck!') + session['notes'] = [['info', text]] + return session + + async def _handle_credit(self, iq, session): import slixfeed.action as action # form = self['xep_0004'].make_form('result', 'Thanks') # form['instructions'] = action.manual('information.toml', 'thanks') # session['payload'] = form - text = '💡️ We are Jabber\n\n' + text = '💡️ We are XMPP\n\n' fren = action.manual('information.toml', 'thanks') fren = "".join(fren) fren = fren.split(';') fren = "\n".join(fren) text += fren - text += '\n\nYOU!\n\n🫵️\n\n- Join us -\n\n🤝️' + # text += '\n\nYOU!\n\n🫵️\n\n- Join us -\n\n🤝️' + text += '\n\nYOU!\n\n🫵️\n\n- Join us -' session['notes'] = [['info', text]] return session @@ -819,8 +863,6 @@ class Slixfeed(slixmpp.ClientXMPP): ftype='text-multi', label=key, value=value) - - session['payload'] = form return session @@ -864,7 +906,8 @@ class Slixfeed(slixmpp.ClientXMPP): form.addField(var='name', ftype='text-single', label='Name', - value=properties['name']) + value=properties['name'], + required=True) form.addField(var='room', ftype='text-single', label='Room', @@ -878,7 +921,8 @@ class Slixfeed(slixmpp.ClientXMPP): form.addField(var='alias', ftype='text-single', label='Alias', - value=properties['nick']) + value=properties['nick'], + required=True) form.addField(var='password', ftype='text-private', label='Password', @@ -920,13 +964,14 @@ class Slixfeed(slixmpp.ClientXMPP): """ form = self['xep_0004'].make_form('result', 'Bookmarks') - form['instructions'] = ('🛡️ Bookmark has been saved') + form['instructions'] = ('✅️ Bookmark has been saved') # In this case (as is typical), the payload is a form values = payload['values'] await XmppBookmark.add(self, properties=values) for value in values: key = str(value) val = str(values[value]) + if not val: val = 'None' # '(empty)' form.add_field(var=key, ftype='text-single', label=key.capitalize(), @@ -1067,7 +1112,7 @@ class Slixfeed(slixmpp.ClientXMPP): jid = session['from'].bare form = self['xep_0004'].make_form('form', 'Settings for {}'.format(jid)) - form['instructions'] = ('🛡️ Settings have been saved') + form['instructions'] = ('✅️ Settings have been saved') jid_file = jid db_file = config.get_pathname_to_database(jid_file) diff --git a/slixfeed/xmpp/muc.py b/slixfeed/xmpp/muc.py index fd1b9ec..a438325 100644 --- a/slixfeed/xmpp/muc.py +++ b/slixfeed/xmpp/muc.py @@ -27,21 +27,22 @@ class XmppGroupchat: # jid = message['groupchat_invite']['jid'] # else: # jid = message - async def accept_invitation(self, message): + def accept_invitation(self, message): # operator muc_chat inviter = message["from"].bare - muc_jid = message['groupchat_invite']['jid'] - await self.join(self, inviter, muc_jid) + jid = message['groupchat_invite']['jid'] + self.join(self, inviter, jid) - async def autojoin(self): - result = await self.plugin['xep_0048'].get_bookmarks() - bookmarks = result["private"]["bookmarks"] - conferences = bookmarks["conferences"] + def autojoin(self, bookmarks): + conferences = bookmarks["private"]["bookmarks"]["conferences"] for conference in conferences: - if conference["autojoin"]: - muc_jid = conference["jid"] - self.plugin['xep_0045'].join_muc(muc_jid, + if conference["jid"] and conference["autojoin"]: + if not conference["nick"]: + conference["nick"] = self.alias + logging.error('Alias (i.e. Nicknname) is missing for ' + 'bookmark {}'.format(conference['name'])) + self.plugin['xep_0045'].join_muc(conference["jid"], conference["nick"], # If a room password is needed, use: # password=the_room_password, @@ -51,11 +52,14 @@ class XmppGroupchat: 'JID : {}\n' 'Alias : {}\n' .format(conference["name"], - muc_jid, + conference["jid"], conference["nick"])) + elif not conference["jid"]: + logging.error('JID is missing for bookmark {}' + .format(conference['name'])) - async def join(self, inviter, muc_jid): + def join(self, inviter, jid): # token = await initdb( # muc_jid, # get_settings_value, @@ -78,27 +82,26 @@ class XmppGroupchat: logging.info('Joining groupchat\n' 'JID : {}\n' 'Inviter : {}\n' - .format(muc_jid, inviter)) - self.plugin['xep_0045'].join_muc(muc_jid, + .format(jid, inviter)) + self.plugin['xep_0045'].join_muc(jid, self.alias, # If a room password is needed, use: # password=the_room_password, ) - async def leave(self, muc_jid): - jid = self.boundjid.bare + def leave(self, jid): message = ('This news bot will now leave this groupchat.\n' 'The JID of this news bot is xmpp:{}?message' - .format(jid)) + .format(self.boundjid.bare)) status_message = ('This bot has left the group. ' 'It can be reached directly via {}' - .format(jid)) - self.send_message(mto=muc_jid, - mfrom=self.boundjid.bare, + .format(self.boundjid.bare)) + self.send_message(mto=jid, + mfrom=self.boundjid, mbody=message, mtype='groupchat') - self.plugin['xep_0045'].leave_muc(muc_jid, + self.plugin['xep_0045'].leave_muc(jid, self.alias, status_message, - self.boundjid.bare) + self.boundjid) diff --git a/slixfeed/xmpp/presence.py b/slixfeed/xmpp/presence.py index 6d59be8..a0ccaa1 100644 --- a/slixfeed/xmpp/presence.py +++ b/slixfeed/xmpp/presence.py @@ -17,8 +17,9 @@ class XmppPresence: def send(self, jid, status_message, presence_type=None, status_type=None): + jid_from = str(self.boundjid) if self.is_component else None self.send_presence(pto=jid, - pfrom=self.boundjid, + pfrom=jid_from, pshow=status_type, pstatus=status_message, ptype=presence_type) diff --git a/slixfeed/xmpp/process.py b/slixfeed/xmpp/process.py index 4ddc8b1..9c97f6e 100644 --- a/slixfeed/xmpp/process.py +++ b/slixfeed/xmpp/process.py @@ -565,7 +565,7 @@ async def message(self, message): XmppMessage.send_reply(self, message, response) case 'goodbye': if message['type'] == 'groupchat': - await XmppGroupchat.leave(self, jid) + XmppGroupchat.leave(self, jid) await XmppBookmark.remove(self, jid) else: response = 'This command is valid in groupchat only.' @@ -585,7 +585,7 @@ async def message(self, message): muc_jid = uri.check_xmpp_uri(message_text[5:]) if muc_jid: # TODO probe JID and confirm it's a groupchat - await XmppGroupchat.join(self, jid, muc_jid) + XmppGroupchat.join(self, jid, muc_jid) # await XmppBookmark.add(self, jid=muc_jid) response = ('Joined groupchat {}' .format(message_text)) @@ -923,7 +923,7 @@ async def message(self, message): muc_jid = uri.check_xmpp_uri(message_text) if muc_jid: # TODO probe JID and confirm it's a groupchat - await XmppGroupchat.join(self, jid, muc_jid) + XmppGroupchat.join(self, jid, muc_jid) # await XmppBookmark.add(self, jid=muc_jid) response = ('Joined groupchat {}' .format(message_text))