From 2f6b86522f6be707ac8c4f2a9681e63e8e72dd6e Mon Sep 17 00:00:00 2001 From: Schimon Jehudah Date: Wed, 24 Jan 2024 18:11:39 +0000 Subject: [PATCH] Add service component support. Thank you to Mr. Guus der Kinderen from IgniteRealtime.org for promptly providing an Openfire instance for development. --- THANKS.md | 6 +- slixfeed/__main__.py | 82 +++++++--- slixfeed/action.py | 27 ++-- slixfeed/assets/accounts.ini | 29 ++-- slixfeed/assets/proxies.yaml | 2 +- slixfeed/config.py | 2 + slixfeed/task.py | 29 +++- slixfeed/xmpp/bookmark.py | 6 +- slixfeed/xmpp/client.py | 20 +-- slixfeed/xmpp/component.py | 284 +++++++++++++++++++++++++++++++++++ slixfeed/xmpp/connect.py | 14 +- slixfeed/xmpp/manual.py | 14 +- slixfeed/xmpp/muc.py | 15 +- slixfeed/xmpp/process.py | 41 +++-- slixfeed/xmpp/roster.py | 22 ++- slixfeed/xmpp/state.py | 73 +++++---- 16 files changed, 548 insertions(+), 118 deletions(-) create mode 100644 slixfeed/xmpp/component.py diff --git a/THANKS.md b/THANKS.md index bc0f8e4..a2bdae0 100644 --- a/THANKS.md +++ b/THANKS.md @@ -1,9 +1,13 @@ Thank you goes to the following kind people. +The following people have been involved in this project by consulting, developing and testing. + [Alixander Court](https://alixandercourt.com/) [edhelas](https://github.com/edhelas/atomtopubsub) +Guus der Kinderen (promptly dedicating a personal [Openfire](https://www.igniterealtime.org/projects/) server for further developments and tests) + [habnabit_](irc://irc.libera.chat/#python) (SQL security) [imattau](https://github.com/imattau/atomtopubsub) (Some code, mostly URL handling, was taken from imattau) @@ -16,4 +20,4 @@ Simone "roughnecks" Canaletti Strix from Loqi -Slixmpp participants who chose to remain anonymous or not to appear in this list. +Slixmpp participants amf #python members who explicitly chose to remain anonymous or not to appear in this list. diff --git a/slixfeed/__main__.py b/slixfeed/__main__.py index 822f000..50c1e0f 100644 --- a/slixfeed/__main__.py +++ b/slixfeed/__main__.py @@ -96,21 +96,54 @@ import os # # with start_action(action_type="set_date()", jid=jid): # # with start_action(action_type="message()", msg=msg): -#import slixfeed.irchandler +#import slixfeed.smtp +#import slixfeed.irc +#import slixfeed.matrix + from slixfeed.config import get_value -from slixfeed.xmpp.client import Slixfeed -#import slixfeed.matrixhandler import socks import socket +xmpp_type = get_value( + "accounts", "XMPP", "type") -class Jabber: - def __init__(self, jid, password, nick): +match xmpp_type: + case "client": + from slixfeed.xmpp.client import Slixfeed + case "component": + from slixfeed.xmpp.component import Slixfeed + + +class JabberComponent: + def __init__(self, jid, secret, hostname, port, alias): + xmpp = Slixfeed(jid, secret, hostname, port, alias) + xmpp.register_plugin('xep_0004') # Data Forms + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0045') # Multi-User Chat + # xmpp.register_plugin('xep_0048') # Bookmarks + xmpp.register_plugin('xep_0054') # vcard-temp + xmpp.register_plugin('xep_0060') # Publish-Subscribe + # xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams + xmpp.register_plugin('xep_0066') # Out of Band Data + xmpp.register_plugin('xep_0071') # XHTML-IM + xmpp.register_plugin('xep_0084') # User Avatar + # xmpp.register_plugin('xep_0085') # Chat State Notifications + xmpp.register_plugin('xep_0153') # vCard-Based Avatars + xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping + xmpp.register_plugin('xep_0249') # Multi-User Chat + xmpp.register_plugin('xep_0363') # HTTP File Upload + xmpp.register_plugin('xep_0402') # PEP Native Bookmarks + xmpp.connect() + xmpp.process() + + +class JabberClient: + def __init__(self, jid, password, alias): # Setup the Slixfeed and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. - xmpp = Slixfeed(jid, password, nick) + xmpp = Slixfeed(jid, password, alias) xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0045') # Multi-User Chat @@ -128,9 +161,9 @@ class Jabber: xmpp.register_plugin('xep_0363') # HTTP File Upload xmpp.register_plugin('xep_0402') # PEP Native Bookmarks - # proxy_enabled = get_value("accounts", "XMPP Connect", "proxy_enabled") + # proxy_enabled = get_value("accounts", "XMPP", "proxy_enabled") # if proxy_enabled == '1': - # values = get_value("accounts", "XMPP Connect", [ + # values = get_value("accounts", "XMPP", [ # "proxy_host", # "proxy_port", # "proxy_username", @@ -150,7 +183,7 @@ class Jabber: # Connect to the XMPP server and start processing XMPP stanzas. address = get_value( - "accounts", "XMPP", ["address", "port"]) + "accounts", "XMPP Client", ["hostname", "port"]) if address[0] and address[1]: xmpp.connect(tuple(address)) else: @@ -189,7 +222,11 @@ def main(): parser.add_argument( "-p", "--password", dest="password", help="Password of JID") parser.add_argument( - "-n", "--nickname", dest="nickname", help="Display name") + "-a", "--alias", dest="alias", help="Display name") + parser.add_argument( + "-n", "--hostname", dest="hostname", help="Hostname") + parser.add_argument( + "-o", "--port", dest="port", help="Port number") args = parser.parse_args() @@ -199,28 +236,39 @@ def main(): # Try configuration file values = get_value( - "accounts", "XMPP", ["nickname", "username", "password"]) - nickname = values[0] + "accounts", "XMPP Client", [ + "alias", "username", "password", "hostname", "port"]) + alias = values[0] username = values[1] password = values[2] + hostname = values[3] + port = values[4] # Use arguments if were given if args.jid: username = args.jid if args.password: password = args.password - if args.nickname: - nickname = args.nickname + if args.alias: + alias = args.alias + if args.hostname: + hostname = args.hostname + if args.port: + port = args.port # Prompt for credentials if none were given if not username: username = input("Username: ") if not password: password = getpass("Password: ") - if not nickname: - nickname = input("Nickname: ") + if not alias: + alias = (input("Alias: ")) or "Slixfeed" - Jabber(username, password, nickname) + match xmpp_type: + case "client": + JabberClient(username, password, alias) + case "component": + JabberComponent(username, password, hostname, port, alias) sys.exit(0) if __name__ == "__main__": diff --git a/slixfeed/action.py b/slixfeed/action.py index 3311afd..3a1db5a 100644 --- a/slixfeed/action.py +++ b/slixfeed/action.py @@ -123,17 +123,22 @@ def is_feed_json(document): True or False. """ value = False - feed = json.loads(document) - if not feed['items']: - if "version" in feed.keys(): - if 'jsonfeed' in feed['version']: - value = True - # elif 'title' in feed.keys(): - # value = True + try: + feed = json.loads(document) + if not feed['items']: + if "version" in feed.keys(): + if 'jsonfeed' in feed['version']: + value = True + else: # TODO Test + value = False + # elif 'title' in feed.keys(): + # value = True + else: + value = False else: - value = False - else: - value = True + value = True + except: + pass return value @@ -1039,8 +1044,6 @@ def generate_document(data, url, ext, filename): "are installed, or try again.") error = ( "Package pdfkit or wkhtmltopdf was not found.") - case "text": - generate_txt(content, filename) case "txt": generate_txt(content, filename) if error: diff --git a/slixfeed/assets/accounts.ini b/slixfeed/assets/accounts.ini index df9c6c7..ef5f150 100644 --- a/slixfeed/assets/accounts.ini +++ b/slixfeed/assets/accounts.ini @@ -2,36 +2,45 @@ # and also from which accounts it receives instructions. [XMPP] -nickname = Slixfeed +reconnect_timeout = 30 +type = client +#type = component + +[XMPP Client] +alias = Slixfeed username = password = -# JID of bot master -operator = -# Address may be an onion hostname -address = +# Hostname (also address) may be an i2p or onion hostname +hostname = # Port may be 5347 port = +[XMPP Component] +alias = Slixfeed +username = +password = +hostname = +port = + [XMPP Profile] name = Slixfeed -nickname = Slixfeed +alias = 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 +# JID of bot master +operator = -[XMPP Connect] -reconnect_timeout = 30 - +[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. -[XMPP Proxy] # Example hostname 127.0.0.1 socks5_host = # Example port 9050 diff --git a/slixfeed/assets/proxies.yaml b/slixfeed/assets/proxies.yaml index 792c686..c8c6fd9 100644 --- a/slixfeed/assets/proxies.yaml +++ b/slixfeed/assets/proxies.yaml @@ -413,7 +413,7 @@ proxies: - https://quetre.projectsegfau.lt - https://quetre.esmailelbob.xyz - https://quetre.odyssey346.dev - - ://ask.habedieeh.re + - https://ask.habedieeh.re - https://quetre.marcopisco.com - https://quetre.blackdrgn.nl - https://quetre.lunar.icu diff --git a/slixfeed/config.py b/slixfeed/config.py index 3995956..17509e4 100644 --- a/slixfeed/config.py +++ b/slixfeed/config.py @@ -17,6 +17,8 @@ TODO 5) Merge get_value_default into get_value. +6) Use TOML https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell + """ import configparser diff --git a/slixfeed/task.py b/slixfeed/task.py index d3cd304..db91a59 100644 --- a/slixfeed/task.py +++ b/slixfeed/task.py @@ -35,7 +35,7 @@ NOTE check_readiness ๐Ÿ“‚ Send a URL from a blog or a news website. JID: self.boundjid.bare - MUC: self.nick + MUC: self.alias """ @@ -258,11 +258,21 @@ async def send_update(self, jid, num=None): if media and news_digest: # Send textual message xmpp.Slixfeed.send_message( - self, mto=jid, mbody=news_digest, mtype=chat_type) + self, + mto=jid, + mfrom=self.boundjid.bare, + mbody=news_digest, + mtype=chat_type + ) news_digest = '' # Send media message = xmpp.Slixfeed.make_message( - self, mto=jid, mbody=media, mtype=chat_type) + self, + mto=jid, + mfrom=self.boundjid.bare, + mbody=media, + mtype=chat_type + ) message['oob']['url'] = media message.send() media = None @@ -274,7 +284,12 @@ async def send_update(self, jid, num=None): if chat_type in ("chat", "groupchat"): # TODO Provide a choice (with or without images) xmpp.Slixfeed.send_message( - self, mto=jid, mbody=news_digest, mtype=chat_type) + self, + mto=jid, + mfrom=self.boundjid.bare, + mbody=news_digest, + mtype=chat_type + ) # if media: # # message = xmpp.Slixfeed.make_message( # # self, mto=jid, mbody=new, mtype=chat_type) @@ -365,10 +380,10 @@ async def send_status(self, jid): # print(await current_time(), status_text, "for", jid) xmpp.Slixfeed.send_presence( self, - pshow=status_mode, - pstatus=status_text, pto=jid, - #pfrom=None + pfrom=self.boundjid.bare, + pshow=status_mode, + pstatus=status_text ) # await asyncio.sleep(60 * 20) await refresh_task( diff --git a/slixfeed/xmpp/bookmark.py b/slixfeed/xmpp/bookmark.py index 2990576..e8bc8ac 100644 --- a/slixfeed/xmpp/bookmark.py +++ b/slixfeed/xmpp/bookmark.py @@ -19,7 +19,7 @@ async def add(self, muc_jid): for muc in mucs: bookmarks.add_conference( muc, - self.nick, + self.alias, autojoin=True ) await self.plugin['xep_0048'].set_bookmarks(bookmarks) @@ -28,7 +28,7 @@ async def add(self, muc_jid): # print(await self.plugin['xep_0048'].get_bookmarks()) # bm = BookmarkStorage() - # bm.conferences.append(Conference(muc_jid, autojoin=True, nick=self.nick)) + # bm.conferences.append(Conference(muc_jid, autojoin=True, nick=self.alias)) # await self['xep_0402'].publish(bm) @@ -53,7 +53,7 @@ async def remove(self, muc_jid): for muc in mucs: bookmarks.add_conference( muc, - self.nick, + self.alias, autojoin=True ) await self.plugin['xep_0048'].set_bookmarks(bookmarks) diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index fa7a528..75ae1de 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -35,7 +35,7 @@ NOTE check_readiness ๐Ÿ“‚ Send a URL from a blog or a news website. JID: self.boundjid.bare - MUC: self.nick + MUC: self.alias 2) Extracting attribute using xmltodict. import xmltodict @@ -91,13 +91,13 @@ class Slixfeed(slixmpp.ClientXMPP): ------- News bot that sends updates from RSS feeds. """ - def __init__(self, jid, password, nick): + def __init__(self, jid, password, alias): slixmpp.ClientXMPP.__init__(self, jid, password) # NOTE # The bot works fine when the nickname is hardcoded; or # The bot won't join some MUCs when its nickname has brackets - self.nick = nick + self.alias = alias # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to @@ -158,24 +158,24 @@ class Slixfeed(slixmpp.ClientXMPP): async def on_session_end(self, event): - message = "Session has ended." + message = "Session has ended. Reason: {}".format(event) await connect.recover_connection(self, event, message) async def on_connection_failed(self, event): - message = "Connection has failed." + message = "Connection has failed. Reason: {}".format(event) await connect.recover_connection(self, event, message) async def on_session_start(self, event): await process.event(self, event) - await muc.autojoin(self, event) + await muc.autojoin(self) await profile.update(self) async def on_session_resumed(self, event): await process.event(self, event) - await muc.autojoin(self, event) + await muc.autojoin(self) # TODO Request for subscription @@ -196,10 +196,10 @@ class Slixfeed(slixmpp.ClientXMPP): # TODO Request for subscription async def on_presence_subscribe(self, presence): - jid = presence["from"].bare - await state.request(self, jid) print("on_presence_subscribe") print(presence) + jid = presence["from"].bare + await state.request(self, jid) async def on_presence_subscribed(self, presence): @@ -214,6 +214,8 @@ class Slixfeed(slixmpp.ClientXMPP): async def on_presence_unsubscribed(self, presence): await state.unsubscribed(self, presence) + jid = presence["from"].bare + await roster.remove(self, jid) async def on_presence_unavailable(self, presence): diff --git a/slixfeed/xmpp/component.py b/slixfeed/xmpp/component.py new file mode 100644 index 0000000..750f5b0 --- /dev/null +++ b/slixfeed/xmpp/component.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" + +FIXME + +1) Function check_readiness or event "changed_status" is causing for + triple status messages and also false ones that indicate of lack + of feeds. + +TODO + +1) Use loop (with gather) instead of TaskGroup. + +2) Assure message delivery before calling a new task. + See https://slixmpp.readthedocs.io/en/latest/event_index.html#term-marker_acknowledged + +3) XHTTML-IM + case _ if message_lowercase.startswith("html"): + message['html']=" +Parse me! +" + self.send_message( + mto=jid, + mfrom=self.boundjid.bare, + mhtml=message + ) + +NOTE + +1) Self presence + Apparently, it is possible to view self presence. + This means that there is no need to store presences in order to switch or restore presence. + check_readiness + ๐Ÿ“‚ Send a URL from a blog or a news website. + JID: self.boundjid.bare + MUC: self.alias + +2) Extracting attribute using xmltodict. + import xmltodict + message = xmltodict.parse(str(message)) + jid = message["message"]["x"]["@jid"] + +""" + +import asyncio +import logging +# import os +from random import randrange +import slixmpp +import slixfeed.task as task +from time import sleep + +from slixmpp.plugins.xep_0363.http_upload import FileTooBig, HTTPError, UploadServiceNotFound +# from slixmpp.plugins.xep_0402 import BookmarkStorage, Conference +from slixmpp.plugins.xep_0048.stanza import Bookmarks + +# import xmltodict +# import xml.etree.ElementTree as ET +# from lxml import etree + +import slixfeed.xmpp.connect as connect +import slixfeed.xmpp.muc as muc +import slixfeed.xmpp.process as process +import slixfeed.xmpp.profile as profile +import slixfeed.xmpp.roster as roster +import slixfeed.xmpp.state as state +import slixfeed.xmpp.status as status +import slixfeed.xmpp.utility as utility + +main_task = [] +jid_tasker = {} +task_manager = {} +loop = asyncio.get_event_loop() +# asyncio.set_event_loop(loop) + +# time_now = datetime.now() +# time_now = time_now.strftime("%H:%M:%S") + +# def print_time(): +# # return datetime.now().strftime("%H:%M:%S") +# now = datetime.now() +# current_time = now.strftime("%H:%M:%S") +# return current_time + + +class Slixfeed(slixmpp.ComponentXMPP): + """ + Slixmpp + ------- + News bot that sends updates from RSS feeds. + """ + def __init__(self, jid, secret, hostname, port, alias): + slixmpp.ComponentXMPP.__init__(self, jid, secret, hostname, port) + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can initialize + # our roster. + self.add_event_handler("session_start", self.on_session_start) + self.add_event_handler("session_resumed", self.on_session_resumed) + self.add_event_handler("got_offline", print("got_offline")) + # self.add_event_handler("got_online", self.check_readiness) + self.add_event_handler("changed_status", self.on_changed_status) + self.add_event_handler("presence_available", self.on_presence_available) + self.add_event_handler("presence_unavailable", self.on_presence_unavailable) + + self.add_event_handler("changed_subscription", self.on_changed_subscription) + + self.add_event_handler("chatstate_active", self.on_chatstate_active) + self.add_event_handler("chatstate_gone", self.on_chatstate_gone) + self.add_event_handler("chatstate_composing", self.check_chatstate_composing) + self.add_event_handler("chatstate_paused", self.check_chatstate_paused) + + # The message event is triggered whenever a message + # stanza is received. Be aware that that includes + # MUC messages and error messages. + 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_message", self.message) + + # self.add_event_handler("disconnected", self.reconnect) + # self.add_event_handler("disconnected", self.inspect_connection) + + self.add_event_handler("reactions", self.on_reactions) + self.add_event_handler("presence_error", self.on_presence_error) + self.add_event_handler("presence_subscribe", self.on_presence_subscribe) + self.add_event_handler("presence_subscribed", self.on_presence_subscribed) + self.add_event_handler("presence_unsubscribe", self.on_presence_unsubscribe) + self.add_event_handler("presence_unsubscribed", self.on_presence_unsubscribed) + + # Initialize event loop + # self.loop = asyncio.get_event_loop() + + # handlers for connection events + self.connection_attempts = 0 + self.max_connection_attempts = 10 + self.add_event_handler("connection_failed", self.on_connection_failed) + self.add_event_handler("session_end", self.on_session_end) + + + async def on_groupchat_invite(self, message): + print("on_groupchat_invite") + await muc.accept_invitation(self, message) + + + async def on_groupchat_direct_invite(self, message): + print("on_groupchat_direct_invite") + await muc.accept_invitation(self, message) + + + async def on_session_end(self, event): + message = "Session has ended. Reason: {}".format(event) + await connect.recover_connection(self, event, message) + + + async def on_connection_failed(self, event): + message = "Connection has failed. Reason: {}".format(event) + await connect.recover_connection(self, event, message) + + + async def on_session_start(self, event): + await process.event_component(self, event) + # await muc.autojoin(self) + await profile.update(self) + + + async def on_session_resumed(self, event): + await process.event_component(self, event) + # await muc.autojoin(self) + + + # TODO Request for subscription + async def on_message(self, message): + # print(message) + # breakpoint() + jid = message["from"].bare + # if "chat" == await utility.jid_type(self, jid): + # await roster.add(self, jid) + # await state.request(self, jid) + # chat_type = message["type"] + # message_body = message["body"] + # message_reply = message.reply + await process.message(self, message) + + + async def on_changed_status(self, presence): + await task.check_readiness(self, presence) + + + # TODO Request for subscription + async def on_presence_subscribe(self, presence): + print("on_presence_subscribe") + print(presence) + jid = presence["from"].bare + # await state.request(self, jid) + self.send_presence_subscription( + pto=jid, + pfrom=self.boundjid.bare, + ptype="subscribe", + pnick=self.alias + ) + + + async def on_presence_subscribed(self, presence): + jid = presence["from"].bare + process.greet(self, jid) + + + async def on_presence_available(self, presence): + # TODO Add function to check whether task is already running or not + await task.start_tasks(self, presence) + + + async def on_presence_unsubscribed(self, presence): + await state.unsubscribed(self, presence) + + + async def on_presence_unavailable(self, presence): + await task.stop_tasks(self, presence) + + + async def on_changed_subscription(self, presence): + print("on_changed_subscription") + print(presence) + jid = presence["from"].bare + # breakpoint() + + + async def on_presence_unsubscribe(self, presence): + print("on_presence_unsubscribe") + print(presence) + + + async def on_presence_error(self, presence): + print("on_presence_error") + print(presence) + + + async def on_reactions(self, message): + print("on_reactions") + print(message) + + + async def on_chatstate_active(self, message): + print("on_chatstate_active") + print(message) + + + async def on_chatstate_gone(self, message): + print("on_chatstate_gone") + print(message) + + + async def check_chatstate_composing(self, message): + print("def check_chatstate_composing") + print(message) + if message["type"] in ("chat", "normal"): + jid = message["from"].bare + status_text="Press \"help\" for manual." + self.send_presence( + # pshow=status_mode, + pstatus=status_text, + pto=jid, + ) + + + async def check_chatstate_paused(self, message): + print("def check_chatstate_paused") + print(message) + if message["type"] in ("chat", "normal"): + jid = message["from"].bare + await task.refresh_task( + self, + jid, + task.send_status, + "status", + 20 + ) + diff --git a/slixfeed/xmpp/connect.py b/slixfeed/xmpp/connect.py index 7d9f1ac..d2bd96c 100644 --- a/slixfeed/xmpp/connect.py +++ b/slixfeed/xmpp/connect.py @@ -1,6 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" + +TODO + +1) Check interval, and if no connection is establish after 30 seconds + then disconnect and reconnect again. + +2) or Check ping, and if no ping is received after 30 seconds then + disconnect and try to reconnect again. + +""" + from slixfeed.config import get_value from slixfeed.dt import current_time from time import sleep @@ -18,7 +30,7 @@ async def recover_connection(self, event, message): # logging.error("Maximum connection attempts exceeded.") print(current_time(), "Attempt number", self.connection_attempts) seconds = (get_value( - "accounts", "XMPP Connect", "reconnect_timeout")) or 30 + "accounts", "XMPP", "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/manual.py b/slixfeed/xmpp/manual.py index 01c79c3..f068162 100644 --- a/slixfeed/xmpp/manual.py +++ b/slixfeed/xmpp/manual.py @@ -40,7 +40,7 @@ def print_info(): " Supported protocols: Dat, FTP, Gemini, Gopher, HTTP and IPFS.\n" "\n" "AUTHORS\n" - " Laura Harbinger, Schimon Zackary.\n" + " Laura Lapina, Schimon Zackary.\n" "\n" "THANKS\n" " Christian Dersch (SalixOS)," @@ -53,15 +53,19 @@ def print_info(): " Florent Le Coz (poezio, France)," "\n" " George Vlahavas (SalixOS, Greece)," + " Guus der Kinderen (IgniteRealtime.org Openfire, Netherlands)," + "\n" " Maxime Buquet (slixmpp, France)," - "\n" " Mathieu Pasquet (slixmpp, France)," - " Pierrick Le Brun (SalixOS, France)," "\n" + " Pierrick Le Brun (SalixOS, France)," " Remko Tronรงon (Swift, Germany)," + "\n" + " Raphael Groner (Fedora, Germany)," " Thorsten Mรผhlfelder (SalixOS, Germany)," "\n" - " Yann Leboulanger (Gajim, France).\n" + " Yann Leboulanger (Gajim, France)." + "\n" "\n" "COPYRIGHT\n" " Slixfeed is free software; you can redistribute it and/or\n" @@ -70,7 +74,7 @@ def print_info(): " Slixfeed is distributed in the hope that it will be useful,\n" " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" - " GNU General Public License for more details.\n" + " MIT License for more details.\n" "\n" "NOTE\n" " You can run Slixfeed on your own computer, server, and\n" diff --git a/slixfeed/xmpp/muc.py b/slixfeed/xmpp/muc.py index ce5ac8b..5c911a5 100644 --- a/slixfeed/xmpp/muc.py +++ b/slixfeed/xmpp/muc.py @@ -31,17 +31,20 @@ async def accept_invitation(self, message): await join(self, inviter, muc_jid) -async def autojoin(self, event): +async def autojoin(self): result = await self.plugin['xep_0048'].get_bookmarks() bookmarks = result["private"]["bookmarks"] conferences = bookmarks["conferences"] for conference in conferences: if conference["autojoin"]: muc_jid = conference["jid"] - logging.debug("Autojoin groupchat", muc_jid) + logging.debug( + "Autojoin {} ({})".format(conference["name"], muc_jid)) + print( + "Autojoin {} ({})".format(conference["name"], muc_jid)) self.plugin['xep_0045'].join_muc( muc_jid, - self.nick, + conference["nick"], # If a room password is needed, use: # password=the_room_password, ) @@ -61,6 +64,7 @@ async def join(self, inviter, muc_jid): # ) # self.send_message( # mto=inviter, + # mfrom=self.boundjid.bare, # mbody=( # "Send activation token {} to groupchat xmpp:{}?join." # ).format(token, muc_jid) @@ -69,7 +73,7 @@ async def join(self, inviter, muc_jid): print(muc_jid) self.plugin['xep_0045'].join_muc( muc_jid, - self.nick, + self.alias, # If a room password is needed, use: # password=the_room_password, ) @@ -87,13 +91,14 @@ async def leave(self, muc_jid): for message in messages: self.send_message( mto=muc_jid, + mfrom=self.boundjid.bare, mbody=message, mtype="groupchat" ) await bookmark.remove(self, muc_jid) self.plugin['xep_0045'].leave_muc( muc_jid, - self.nick, + self.alias, "Goodbye!", self.boundjid.bare ) diff --git a/slixfeed/xmpp/process.py b/slixfeed/xmpp/process.py index 149ea5e..4824575 100644 --- a/slixfeed/xmpp/process.py +++ b/slixfeed/xmpp/process.py @@ -41,6 +41,10 @@ import slixfeed.xmpp.upload as upload from slixfeed.xmpp.utility import jid_type +async def event_component(self, event): + self.send_presence() + + async def event(self, event): """ Process the session_start event. @@ -84,12 +88,12 @@ async def message(self, message): message_text = " ".join(message["body"].split()) # if (message["type"] == "groupchat" and - # message['muc']['nick'] == self.nick): + # message['muc']['nick'] == self.alias): # return # FIXME Code repetition. See below. if message["type"] == "groupchat": - if (message['muc']['nick'] == self.nick): + if (message['muc']['nick'] == self.alias): return jid_full = str(message["from"]) role = self.plugin['xep_0045'].get_jid_property( @@ -136,7 +140,7 @@ async def message(self, message): # nick = message["from"][message["from"].index("/")+1:] # nick = str(message["from"]) # nick = nick[nick.index("/")+1:] - if (message['muc']['nick'] == self.nick or + if (message['muc']['nick'] == self.alias or not message["body"].startswith("!")): return # token = await initdb( @@ -198,7 +202,7 @@ async def message(self, message): response = None match message_lowercase: # case "breakpoint": - # if jid == get_value("accounts", "XMPP", "operator"): + # if jid == get_value("accounts", "XMPP Profile", "operator"): # breakpoint() # print("task_manager[jid]") # print(task_manager[jid]) @@ -368,7 +372,7 @@ async def message(self, message): send_reply_message(self, message, response) case _ if message_lowercase.startswith("bookmark -"): if jid == get_value( - "accounts", "XMPP", "operator"): + "accounts", "XMPP Profile", "operator"): muc_jid = message_text[11:] await bookmark.remove(self, muc_jid) response = ( @@ -382,7 +386,7 @@ async def message(self, message): send_reply_message(self, message, response) case "bookmarks": if jid == get_value( - "accounts", "XMPP", "operator"): + "accounts", "XMPP Profile", "operator"): response = await action.list_bookmarks(self) else: response = ( @@ -485,7 +489,13 @@ async def message(self, message): ext = ext if ext else 'pdf' url = None error = None - if ext in ("epub", "html", "md", "pdf", "txt"): + if ext in ( + "epub", "html", "markdown", "md", "pdf", "text", "txt"): + match ext: + case "markdown": + ext = "md" + case "text": + ext = "txt" status_type = "dnd" status_message = ( "๐Ÿ“ƒ๏ธ Procesing request to produce {} document..." @@ -504,7 +514,7 @@ async def message(self, message): try: url = sqlite.get_entry_url(db_file, ix) except: - response = "No entry with Id {}".format(ix) + response = "No entry with index {}".format(ix) except: url = ix_url if url: @@ -539,7 +549,7 @@ async def message(self, message): await task.start_tasks_xmpp( self, jid, ["status"]) else: - response = "Missing entry Id." + response = "Missing entry index number." else: response = "Unsupported filetype." if response: @@ -859,7 +869,7 @@ async def message(self, message): ).format(url, ix) except: response = ( - "No news source with ID {}." + "No news source with index {}." ).format(ix) except: url = ix_url @@ -878,7 +888,7 @@ async def message(self, message): await task.clean_tasks_xmpp(jid, ["status"]) await task.start_tasks_xmpp(self, jid, ["status"]) else: - response = "Missing feed ID." + response = "Missing feed index number." send_reply_message(self, message, response) case _ if message_lowercase.startswith("reset"): url = message_text[6:] @@ -947,7 +957,7 @@ async def message(self, message): "Updates are now disabled for news source {}." ).format(ix) except: - response = "No news source with ID {}.".format(ix) + response = "No news source with index {}.".format(ix) send_reply_message(self, message, response) case _ if message_lowercase.startswith("enable"): ix = message_text[7:] @@ -958,7 +968,7 @@ async def message(self, message): "Updates are now enabled for news source {}." ).format(ix) except: - response = "No news source with ID {}.".format(ix) + response = "No news source with index {}.".format(ix) send_reply_message(self, message, response) case "stop": # FIXME @@ -1067,6 +1077,7 @@ async def send_oob_message(self, jid, url): f'{url}') message = self.make_message( mto=jid, + mfrom=self.boundjid.bare, mbody=url, mhtml=html, mtype=chat_type @@ -1087,6 +1098,7 @@ async def send_oob_message(self, jid, url): # for message in messages: # self.send_message( # mto=jid, +# mfrom=self.boundjid.bare, # mbody=message, # mtype=chat_type # ) @@ -1099,11 +1111,12 @@ def greet(self, jid, chat_type="chat"): "My job is to bring you the latest " "news from sources you provide me with.\n" "You may always reach me via xmpp:{}?message").format( - self.nick, + self.alias, self.boundjid.bare ) self.send_message( mto=jid, + mfrom=self.boundjid.bare, mbody=message, mtype=chat_type ) diff --git a/slixfeed/xmpp/roster.py b/slixfeed/xmpp/roster.py index 09f33f4..b1d4447 100644 --- a/slixfeed/xmpp/roster.py +++ b/slixfeed/xmpp/roster.py @@ -12,6 +12,25 @@ TODO import slixfeed.xmpp.utility as utility +async def remove(self, jid): + """ + Remove JID to roster. + + Parameters + ---------- + jid : str + Jabber ID. + + Returns + ------- + None. + """ + self.update_roster( + jid, + subscription="remove" + ) + + async def add(self, jid): """ Add JID to roster. @@ -35,8 +54,9 @@ async def add(self, jid): if jid not in self.client_roster.keys(): self.send_presence_subscription( pto=jid, + pfrom=self.boundjid.bare, ptype="subscribe", - pnick=self.nick + pnick=self.alias ) self.update_roster( jid, diff --git a/slixfeed/xmpp/state.py b/slixfeed/xmpp/state.py index b0e724f..723e978 100644 --- a/slixfeed/xmpp/state.py +++ b/slixfeed/xmpp/state.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from slixmpp.xmlstream.matcher import MatchXPath +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream import ET async def request(self, jid): """ @@ -16,48 +19,54 @@ async def request(self, jid): None. """ # Check whether JID is subscribed; otherwise, ask for presence. - if not self.client_roster[jid]["to"]: - self.send_presence_subscription( - pto=jid, - pfrom=self.boundjid.bare, - ptype="subscribe", - pnick=self.nick - ) - self.send_message( - mto=jid, - # mtype="headline", - msubject="RSS News Bot", - mbody=( - "Share online status to receive updates." - ), - mfrom=self.boundjid.bare, - mnick=self.nick - ) - self.send_presence( - pto=jid, - pfrom=self.boundjid.bare, - # Accept symbol ๐Ÿ‰‘๏ธ ๐Ÿ‘๏ธ โœ - pstatus=( - "โœ’๏ธ Share online status to receive updates." - ), - # ptype="subscribe", - pnick=self.nick - ) + if self.is_component: + presence_probe = ET.Element('presence') + presence_probe.attrib['type'] = 'probe' + presence_probe.attrib['to'] = jid + print(presence_probe) + breakpoint() + self.send_raw(str(presence_probe)) + presence_probe.send() + else: + if not self.client_roster[jid]["to"]: + self.send_presence_subscription( + pto=jid, + pfrom=self.boundjid.bare, + ptype="subscribe", + pnick=self.alias + ) + self.send_message( + mto=jid, + mfrom=self.boundjid.bare, + # mtype="headline", + msubject="RSS News Bot", + mbody=( + "Share online status to receive updates." + ), + mnick=self.alias + ) + self.send_presence( + pto=jid, + pfrom=self.boundjid.bare, + # Accept symbol ๐Ÿ‰‘๏ธ ๐Ÿ‘๏ธ โœ + pstatus=( + "โœ’๏ธ Share online status to receive updates." + ), + # ptype="subscribe", + pnick=self.alias + ) async def unsubscribed(self, presence): jid = presence["from"].bare self.send_message( mto=jid, + mfrom=self.boundjid.bare, mbody="You have been unsubscribed." ) self.send_presence( pto=jid, pfrom=self.boundjid.bare, pstatus="๐Ÿ–‹๏ธ Subscribe to receive updates", - pnick=self.nick - ) - self.update_roster( - jid, - subscription="remove" + pnick=self.alias )