Add service component support.

Thank you to Mr. Guus der Kinderen from IgniteRealtime.org for promptly providing an Openfire instance for development.
This commit is contained in:
Schimon Jehudah 2024-01-24 18:11:39 +00:00
parent c9c552e33f
commit 2f6b86522f
16 changed files with 548 additions and 118 deletions

View file

@ -1,9 +1,13 @@
Thank you goes to the following kind people. 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/) [Alixander Court](https://alixandercourt.com/)
[edhelas](https://github.com/edhelas/atomtopubsub) [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) [habnabit_](irc://irc.libera.chat/#python) (SQL security)
[imattau](https://github.com/imattau/atomtopubsub) (Some code, mostly URL handling, was taken from imattau) [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 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.

View file

@ -96,21 +96,54 @@ import os
# # with start_action(action_type="set_date()", jid=jid): # # with start_action(action_type="set_date()", jid=jid):
# # with start_action(action_type="message()", msg=msg): # # 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.config import get_value
from slixfeed.xmpp.client import Slixfeed
#import slixfeed.matrixhandler
import socks import socks
import socket import socket
xmpp_type = get_value(
"accounts", "XMPP", "type")
class Jabber: match xmpp_type:
def __init__(self, jid, password, nick): 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 # Setup the Slixfeed and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does # have interdependencies, the order in which you register them does
# not matter. # not matter.
xmpp = Slixfeed(jid, password, nick) xmpp = Slixfeed(jid, password, alias)
xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0045') # Multi-User Chat 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_0363') # HTTP File Upload
xmpp.register_plugin('xep_0402') # PEP Native Bookmarks 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': # if proxy_enabled == '1':
# values = get_value("accounts", "XMPP Connect", [ # values = get_value("accounts", "XMPP", [
# "proxy_host", # "proxy_host",
# "proxy_port", # "proxy_port",
# "proxy_username", # "proxy_username",
@ -150,7 +183,7 @@ class Jabber:
# Connect to the XMPP server and start processing XMPP stanzas. # Connect to the XMPP server and start processing XMPP stanzas.
address = get_value( address = get_value(
"accounts", "XMPP", ["address", "port"]) "accounts", "XMPP Client", ["hostname", "port"])
if address[0] and address[1]: if address[0] and address[1]:
xmpp.connect(tuple(address)) xmpp.connect(tuple(address))
else: else:
@ -189,7 +222,11 @@ def main():
parser.add_argument( parser.add_argument(
"-p", "--password", dest="password", help="Password of JID") "-p", "--password", dest="password", help="Password of JID")
parser.add_argument( 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() args = parser.parse_args()
@ -199,28 +236,39 @@ def main():
# Try configuration file # Try configuration file
values = get_value( values = get_value(
"accounts", "XMPP", ["nickname", "username", "password"]) "accounts", "XMPP Client", [
nickname = values[0] "alias", "username", "password", "hostname", "port"])
alias = values[0]
username = values[1] username = values[1]
password = values[2] password = values[2]
hostname = values[3]
port = values[4]
# Use arguments if were given # Use arguments if were given
if args.jid: if args.jid:
username = args.jid username = args.jid
if args.password: if args.password:
password = args.password password = args.password
if args.nickname: if args.alias:
nickname = args.nickname alias = args.alias
if args.hostname:
hostname = args.hostname
if args.port:
port = args.port
# Prompt for credentials if none were given # Prompt for credentials if none were given
if not username: if not username:
username = input("Username: ") username = input("Username: ")
if not password: if not password:
password = getpass("Password: ") password = getpass("Password: ")
if not nickname: if not alias:
nickname = input("Nickname: ") 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) sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -123,17 +123,22 @@ def is_feed_json(document):
True or False. True or False.
""" """
value = False value = False
feed = json.loads(document) try:
if not feed['items']: feed = json.loads(document)
if "version" in feed.keys(): if not feed['items']:
if 'jsonfeed' in feed['version']: if "version" in feed.keys():
value = True if 'jsonfeed' in feed['version']:
# elif 'title' in feed.keys(): value = True
# value = True else: # TODO Test
value = False
# elif 'title' in feed.keys():
# value = True
else:
value = False
else: else:
value = False value = True
else: except:
value = True pass
return value return value
@ -1039,8 +1044,6 @@ def generate_document(data, url, ext, filename):
"are installed, or try again.") "are installed, or try again.")
error = ( error = (
"Package pdfkit or wkhtmltopdf was not found.") "Package pdfkit or wkhtmltopdf was not found.")
case "text":
generate_txt(content, filename)
case "txt": case "txt":
generate_txt(content, filename) generate_txt(content, filename)
if error: if error:

View file

@ -2,36 +2,45 @@
# and also from which accounts it receives instructions. # and also from which accounts it receives instructions.
[XMPP] [XMPP]
nickname = Slixfeed reconnect_timeout = 30
type = client
#type = component
[XMPP Client]
alias = Slixfeed
username = username =
password = password =
# JID of bot master # Hostname (also address) may be an i2p or onion hostname
operator = hostname =
# Address may be an onion hostname
address =
# Port may be 5347 # Port may be 5347
port = port =
[XMPP Component]
alias = Slixfeed
username =
password =
hostname =
port =
[XMPP Profile] [XMPP Profile]
name = Slixfeed name = Slixfeed
nickname = Slixfeed alias = Slixfeed
role = Syndication News Bot role = Syndication News Bot
organization = RSS Task Force organization = RSS Task Force
url = https://gitgud.io/sjehuda/slixfeed url = https://gitgud.io/sjehuda/slixfeed
description = XMPP news bot (supports Atom, JSON, RDF and RSS). description = XMPP news bot (supports Atom, JSON, RDF and RSS).
note = This is a syndication news bot powered by Slixfeed. note = This is a syndication news bot powered by Slixfeed.
birthday = 21 June 2022 birthday = 21 June 2022
# JID of bot master
operator =
[XMPP Connect] [XMPP Proxy]
reconnect_timeout = 30
# NOTE You might want to consider support for socks4 too (this # NOTE You might want to consider support for socks4 too (this
# note was written when keys were proxy_host and proxy_port) # note was written when keys were proxy_host and proxy_port)
# NOTE Consider not to use a version number as it might give an # NOTE Consider not to use a version number as it might give an
# impression of an archaic feature in the future. # impression of an archaic feature in the future.
[XMPP Proxy]
# Example hostname 127.0.0.1 # Example hostname 127.0.0.1
socks5_host = socks5_host =
# Example port 9050 # Example port 9050

View file

@ -413,7 +413,7 @@ proxies:
- https://quetre.projectsegfau.lt - https://quetre.projectsegfau.lt
- https://quetre.esmailelbob.xyz - https://quetre.esmailelbob.xyz
- https://quetre.odyssey346.dev - https://quetre.odyssey346.dev
- ://ask.habedieeh.re - https://ask.habedieeh.re
- https://quetre.marcopisco.com - https://quetre.marcopisco.com
- https://quetre.blackdrgn.nl - https://quetre.blackdrgn.nl
- https://quetre.lunar.icu - https://quetre.lunar.icu

View file

@ -17,6 +17,8 @@ TODO
5) Merge get_value_default into get_value. 5) Merge get_value_default into get_value.
6) Use TOML https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell
""" """
import configparser import configparser

View file

@ -35,7 +35,7 @@ NOTE
check_readiness check_readiness
<presence from="slixfeed@canchat.org/xAPgJLHtMMHF" xml:lang="en" id="ab35c07b63a444d0a7c0a9a0b272f301" to="slixfeed@canchat.org/xAPgJLHtMMHF"><status>📂 Send a URL from a blog or a news website.</status><x xmlns="vcard-temp:x:update"><photo /></x></presence> <presence from="slixfeed@canchat.org/xAPgJLHtMMHF" xml:lang="en" id="ab35c07b63a444d0a7c0a9a0b272f301" to="slixfeed@canchat.org/xAPgJLHtMMHF"><status>📂 Send a URL from a blog or a news website.</status><x xmlns="vcard-temp:x:update"><photo /></x></presence>
JID: self.boundjid.bare 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: if media and news_digest:
# Send textual message # Send textual message
xmpp.Slixfeed.send_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 = '' news_digest = ''
# Send media # Send media
message = xmpp.Slixfeed.make_message( 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['oob']['url'] = media
message.send() message.send()
media = None media = None
@ -274,7 +284,12 @@ async def send_update(self, jid, num=None):
if chat_type in ("chat", "groupchat"): if chat_type in ("chat", "groupchat"):
# TODO Provide a choice (with or without images) # TODO Provide a choice (with or without images)
xmpp.Slixfeed.send_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
)
# if media: # if media:
# # message = xmpp.Slixfeed.make_message( # # message = xmpp.Slixfeed.make_message(
# # self, mto=jid, mbody=new, mtype=chat_type) # # 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) # print(await current_time(), status_text, "for", jid)
xmpp.Slixfeed.send_presence( xmpp.Slixfeed.send_presence(
self, self,
pshow=status_mode,
pstatus=status_text,
pto=jid, pto=jid,
#pfrom=None pfrom=self.boundjid.bare,
pshow=status_mode,
pstatus=status_text
) )
# await asyncio.sleep(60 * 20) # await asyncio.sleep(60 * 20)
await refresh_task( await refresh_task(

View file

@ -19,7 +19,7 @@ async def add(self, muc_jid):
for muc in mucs: for muc in mucs:
bookmarks.add_conference( bookmarks.add_conference(
muc, muc,
self.nick, self.alias,
autojoin=True autojoin=True
) )
await self.plugin['xep_0048'].set_bookmarks(bookmarks) 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()) # print(await self.plugin['xep_0048'].get_bookmarks())
# bm = BookmarkStorage() # 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) # await self['xep_0402'].publish(bm)
@ -53,7 +53,7 @@ async def remove(self, muc_jid):
for muc in mucs: for muc in mucs:
bookmarks.add_conference( bookmarks.add_conference(
muc, muc,
self.nick, self.alias,
autojoin=True autojoin=True
) )
await self.plugin['xep_0048'].set_bookmarks(bookmarks) await self.plugin['xep_0048'].set_bookmarks(bookmarks)

View file

@ -35,7 +35,7 @@ NOTE
check_readiness check_readiness
📂 Send a URL from a blog or a news website. 📂 Send a URL from a blog or a news website.
JID: self.boundjid.bare JID: self.boundjid.bare
MUC: self.nick MUC: self.alias
2) Extracting attribute using xmltodict. 2) Extracting attribute using xmltodict.
import xmltodict import xmltodict
@ -91,13 +91,13 @@ class Slixfeed(slixmpp.ClientXMPP):
------- -------
News bot that sends updates from RSS feeds. 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) slixmpp.ClientXMPP.__init__(self, jid, password)
# NOTE # NOTE
# The bot works fine when the nickname is hardcoded; or # The bot works fine when the nickname is hardcoded; or
# The bot won't join some MUCs when its nickname has brackets # 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 session_start event will be triggered when
# the bot establishes its connection with the server # the bot establishes its connection with the server
# and the XML streams are ready for use. We want to # 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): 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) await connect.recover_connection(self, event, message)
async def on_connection_failed(self, event): 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) await connect.recover_connection(self, event, message)
async def on_session_start(self, event): async def on_session_start(self, event):
await process.event(self, event) await process.event(self, event)
await muc.autojoin(self, event) await muc.autojoin(self)
await profile.update(self) await profile.update(self)
async def on_session_resumed(self, event): async def on_session_resumed(self, event):
await process.event(self, event) await process.event(self, event)
await muc.autojoin(self, event) await muc.autojoin(self)
# TODO Request for subscription # TODO Request for subscription
@ -196,10 +196,10 @@ class Slixfeed(slixmpp.ClientXMPP):
# TODO Request for subscription # TODO Request for subscription
async def on_presence_subscribe(self, presence): async def on_presence_subscribe(self, presence):
jid = presence["from"].bare
await state.request(self, jid)
print("on_presence_subscribe") print("on_presence_subscribe")
print(presence) print(presence)
jid = presence["from"].bare
await state.request(self, jid)
async def on_presence_subscribed(self, presence): async def on_presence_subscribed(self, presence):
@ -214,6 +214,8 @@ class Slixfeed(slixmpp.ClientXMPP):
async def on_presence_unsubscribed(self, presence): async def on_presence_unsubscribed(self, presence):
await state.unsubscribed(self, presence) await state.unsubscribed(self, presence)
jid = presence["from"].bare
await roster.remove(self, jid)
async def on_presence_unavailable(self, presence): async def on_presence_unavailable(self, presence):

284
slixfeed/xmpp/component.py Normal file
View file

@ -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
)

View file

@ -1,6 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- 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.config import get_value
from slixfeed.dt import current_time from slixfeed.dt import current_time
from time import sleep from time import sleep
@ -18,7 +30,7 @@ async def recover_connection(self, event, message):
# logging.error("Maximum connection attempts exceeded.") # logging.error("Maximum connection attempts exceeded.")
print(current_time(), "Attempt number", self.connection_attempts) print(current_time(), "Attempt number", self.connection_attempts)
seconds = (get_value( seconds = (get_value(
"accounts", "XMPP Connect", "reconnect_timeout")) or 30 "accounts", "XMPP", "reconnect_timeout")) or 30
seconds = int(seconds) seconds = int(seconds)
print(current_time(), "Next attempt within", seconds, "seconds") print(current_time(), "Next attempt within", seconds, "seconds")
# NOTE asyncio.sleep doesn't interval as expected # NOTE asyncio.sleep doesn't interval as expected

View file

@ -40,7 +40,7 @@ def print_info():
" Supported protocols: Dat, FTP, Gemini, Gopher, HTTP and IPFS.\n" " Supported protocols: Dat, FTP, Gemini, Gopher, HTTP and IPFS.\n"
"\n" "\n"
"AUTHORS\n" "AUTHORS\n"
" Laura Harbinger, Schimon Zackary.\n" " Laura Lapina, Schimon Zackary.\n"
"\n" "\n"
"THANKS\n" "THANKS\n"
" Christian Dersch (SalixOS)," " Christian Dersch (SalixOS),"
@ -53,15 +53,19 @@ def print_info():
" Florent Le Coz (poezio, France)," " Florent Le Coz (poezio, France),"
"\n" "\n"
" George Vlahavas (SalixOS, Greece)," " George Vlahavas (SalixOS, Greece),"
" Guus der Kinderen (IgniteRealtime.org Openfire, Netherlands),"
"\n"
" Maxime Buquet (slixmpp, France)," " Maxime Buquet (slixmpp, France),"
"\n"
" Mathieu Pasquet (slixmpp, France)," " Mathieu Pasquet (slixmpp, France),"
" Pierrick Le Brun (SalixOS, France),"
"\n" "\n"
" Pierrick Le Brun (SalixOS, France),"
" Remko Tronçon (Swift, Germany)," " Remko Tronçon (Swift, Germany),"
"\n"
" Raphael Groner (Fedora, Germany),"
" Thorsten Mühlfelder (SalixOS, Germany)," " Thorsten Mühlfelder (SalixOS, Germany),"
"\n" "\n"
" Yann Leboulanger (Gajim, France).\n" " Yann Leboulanger (Gajim, France)."
"\n"
"\n" "\n"
"COPYRIGHT\n" "COPYRIGHT\n"
" Slixfeed is free software; you can redistribute it and/or\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" " Slixfeed is distributed in the hope that it will be useful,\n"
" but WITHOUT ANY WARRANTY; without even the implied warranty of\n" " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\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" "\n"
"NOTE\n" "NOTE\n"
" You can run Slixfeed on your own computer, server, and\n" " You can run Slixfeed on your own computer, server, and\n"

View file

@ -31,17 +31,20 @@ async def accept_invitation(self, message):
await join(self, inviter, muc_jid) await join(self, inviter, muc_jid)
async def autojoin(self, event): async def autojoin(self):
result = await self.plugin['xep_0048'].get_bookmarks() result = await self.plugin['xep_0048'].get_bookmarks()
bookmarks = result["private"]["bookmarks"] bookmarks = result["private"]["bookmarks"]
conferences = bookmarks["conferences"] conferences = bookmarks["conferences"]
for conference in conferences: for conference in conferences:
if conference["autojoin"]: if conference["autojoin"]:
muc_jid = conference["jid"] 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( self.plugin['xep_0045'].join_muc(
muc_jid, muc_jid,
self.nick, conference["nick"],
# If a room password is needed, use: # If a room password is needed, use:
# password=the_room_password, # password=the_room_password,
) )
@ -61,6 +64,7 @@ async def join(self, inviter, muc_jid):
# ) # )
# self.send_message( # self.send_message(
# mto=inviter, # mto=inviter,
# mfrom=self.boundjid.bare,
# mbody=( # mbody=(
# "Send activation token {} to groupchat xmpp:{}?join." # "Send activation token {} to groupchat xmpp:{}?join."
# ).format(token, muc_jid) # ).format(token, muc_jid)
@ -69,7 +73,7 @@ async def join(self, inviter, muc_jid):
print(muc_jid) print(muc_jid)
self.plugin['xep_0045'].join_muc( self.plugin['xep_0045'].join_muc(
muc_jid, muc_jid,
self.nick, self.alias,
# If a room password is needed, use: # If a room password is needed, use:
# password=the_room_password, # password=the_room_password,
) )
@ -87,13 +91,14 @@ async def leave(self, muc_jid):
for message in messages: for message in messages:
self.send_message( self.send_message(
mto=muc_jid, mto=muc_jid,
mfrom=self.boundjid.bare,
mbody=message, mbody=message,
mtype="groupchat" mtype="groupchat"
) )
await bookmark.remove(self, muc_jid) await bookmark.remove(self, muc_jid)
self.plugin['xep_0045'].leave_muc( self.plugin['xep_0045'].leave_muc(
muc_jid, muc_jid,
self.nick, self.alias,
"Goodbye!", "Goodbye!",
self.boundjid.bare self.boundjid.bare
) )

View file

@ -41,6 +41,10 @@ import slixfeed.xmpp.upload as upload
from slixfeed.xmpp.utility import jid_type from slixfeed.xmpp.utility import jid_type
async def event_component(self, event):
self.send_presence()
async def event(self, event): async def event(self, event):
""" """
Process the session_start event. Process the session_start event.
@ -84,12 +88,12 @@ async def message(self, message):
message_text = " ".join(message["body"].split()) message_text = " ".join(message["body"].split())
# if (message["type"] == "groupchat" and # if (message["type"] == "groupchat" and
# message['muc']['nick'] == self.nick): # message['muc']['nick'] == self.alias):
# return # return
# FIXME Code repetition. See below. # FIXME Code repetition. See below.
if message["type"] == "groupchat": if message["type"] == "groupchat":
if (message['muc']['nick'] == self.nick): if (message['muc']['nick'] == self.alias):
return return
jid_full = str(message["from"]) jid_full = str(message["from"])
role = self.plugin['xep_0045'].get_jid_property( 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 = message["from"][message["from"].index("/")+1:]
# nick = str(message["from"]) # nick = str(message["from"])
# nick = nick[nick.index("/")+1:] # nick = nick[nick.index("/")+1:]
if (message['muc']['nick'] == self.nick or if (message['muc']['nick'] == self.alias or
not message["body"].startswith("!")): not message["body"].startswith("!")):
return return
# token = await initdb( # token = await initdb(
@ -198,7 +202,7 @@ async def message(self, message):
response = None response = None
match message_lowercase: match message_lowercase:
# case "breakpoint": # case "breakpoint":
# if jid == get_value("accounts", "XMPP", "operator"): # if jid == get_value("accounts", "XMPP Profile", "operator"):
# breakpoint() # breakpoint()
# print("task_manager[jid]") # print("task_manager[jid]")
# print(task_manager[jid]) # print(task_manager[jid])
@ -368,7 +372,7 @@ async def message(self, message):
send_reply_message(self, message, response) send_reply_message(self, message, response)
case _ if message_lowercase.startswith("bookmark -"): case _ if message_lowercase.startswith("bookmark -"):
if jid == get_value( if jid == get_value(
"accounts", "XMPP", "operator"): "accounts", "XMPP Profile", "operator"):
muc_jid = message_text[11:] muc_jid = message_text[11:]
await bookmark.remove(self, muc_jid) await bookmark.remove(self, muc_jid)
response = ( response = (
@ -382,7 +386,7 @@ async def message(self, message):
send_reply_message(self, message, response) send_reply_message(self, message, response)
case "bookmarks": case "bookmarks":
if jid == get_value( if jid == get_value(
"accounts", "XMPP", "operator"): "accounts", "XMPP Profile", "operator"):
response = await action.list_bookmarks(self) response = await action.list_bookmarks(self)
else: else:
response = ( response = (
@ -485,7 +489,13 @@ async def message(self, message):
ext = ext if ext else 'pdf' ext = ext if ext else 'pdf'
url = None url = None
error = 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_type = "dnd"
status_message = ( status_message = (
"📃️ Procesing request to produce {} document..." "📃️ Procesing request to produce {} document..."
@ -504,7 +514,7 @@ async def message(self, message):
try: try:
url = sqlite.get_entry_url(db_file, ix) url = sqlite.get_entry_url(db_file, ix)
except: except:
response = "No entry with Id {}".format(ix) response = "No entry with index {}".format(ix)
except: except:
url = ix_url url = ix_url
if url: if url:
@ -539,7 +549,7 @@ async def message(self, message):
await task.start_tasks_xmpp( await task.start_tasks_xmpp(
self, jid, ["status"]) self, jid, ["status"])
else: else:
response = "Missing entry Id." response = "Missing entry index number."
else: else:
response = "Unsupported filetype." response = "Unsupported filetype."
if response: if response:
@ -859,7 +869,7 @@ async def message(self, message):
).format(url, ix) ).format(url, ix)
except: except:
response = ( response = (
"No news source with ID {}." "No news source with index {}."
).format(ix) ).format(ix)
except: except:
url = ix_url url = ix_url
@ -878,7 +888,7 @@ async def message(self, message):
await task.clean_tasks_xmpp(jid, ["status"]) await task.clean_tasks_xmpp(jid, ["status"])
await task.start_tasks_xmpp(self, jid, ["status"]) await task.start_tasks_xmpp(self, jid, ["status"])
else: else:
response = "Missing feed ID." response = "Missing feed index number."
send_reply_message(self, message, response) send_reply_message(self, message, response)
case _ if message_lowercase.startswith("reset"): case _ if message_lowercase.startswith("reset"):
url = message_text[6:] url = message_text[6:]
@ -947,7 +957,7 @@ async def message(self, message):
"Updates are now disabled for news source {}." "Updates are now disabled for news source {}."
).format(ix) ).format(ix)
except: except:
response = "No news source with ID {}.".format(ix) response = "No news source with index {}.".format(ix)
send_reply_message(self, message, response) send_reply_message(self, message, response)
case _ if message_lowercase.startswith("enable"): case _ if message_lowercase.startswith("enable"):
ix = message_text[7:] ix = message_text[7:]
@ -958,7 +968,7 @@ async def message(self, message):
"Updates are now enabled for news source {}." "Updates are now enabled for news source {}."
).format(ix) ).format(ix)
except: except:
response = "No news source with ID {}.".format(ix) response = "No news source with index {}.".format(ix)
send_reply_message(self, message, response) send_reply_message(self, message, response)
case "stop": case "stop":
# FIXME # FIXME
@ -1067,6 +1077,7 @@ async def send_oob_message(self, jid, url):
f'<a href="{url}">{url}</a></body>') f'<a href="{url}">{url}</a></body>')
message = self.make_message( message = self.make_message(
mto=jid, mto=jid,
mfrom=self.boundjid.bare,
mbody=url, mbody=url,
mhtml=html, mhtml=html,
mtype=chat_type mtype=chat_type
@ -1087,6 +1098,7 @@ async def send_oob_message(self, jid, url):
# for message in messages: # for message in messages:
# self.send_message( # self.send_message(
# mto=jid, # mto=jid,
# mfrom=self.boundjid.bare,
# mbody=message, # mbody=message,
# mtype=chat_type # mtype=chat_type
# ) # )
@ -1099,11 +1111,12 @@ def greet(self, jid, chat_type="chat"):
"My job is to bring you the latest " "My job is to bring you the latest "
"news from sources you provide me with.\n" "news from sources you provide me with.\n"
"You may always reach me via xmpp:{}?message").format( "You may always reach me via xmpp:{}?message").format(
self.nick, self.alias,
self.boundjid.bare self.boundjid.bare
) )
self.send_message( self.send_message(
mto=jid, mto=jid,
mfrom=self.boundjid.bare,
mbody=message, mbody=message,
mtype=chat_type mtype=chat_type
) )

View file

@ -12,6 +12,25 @@ TODO
import slixfeed.xmpp.utility as utility 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): async def add(self, jid):
""" """
Add JID to roster. Add JID to roster.
@ -35,8 +54,9 @@ async def add(self, jid):
if jid not in self.client_roster.keys(): if jid not in self.client_roster.keys():
self.send_presence_subscription( self.send_presence_subscription(
pto=jid, pto=jid,
pfrom=self.boundjid.bare,
ptype="subscribe", ptype="subscribe",
pnick=self.nick pnick=self.alias
) )
self.update_roster( self.update_roster(
jid, jid,

View file

@ -1,6 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- 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): async def request(self, jid):
""" """
@ -16,48 +19,54 @@ async def request(self, jid):
None. None.
""" """
# Check whether JID is subscribed; otherwise, ask for presence. # Check whether JID is subscribed; otherwise, ask for presence.
if not self.client_roster[jid]["to"]: if self.is_component:
self.send_presence_subscription( presence_probe = ET.Element('presence')
pto=jid, presence_probe.attrib['type'] = 'probe'
pfrom=self.boundjid.bare, presence_probe.attrib['to'] = jid
ptype="subscribe", print(presence_probe)
pnick=self.nick breakpoint()
) self.send_raw(str(presence_probe))
self.send_message( presence_probe.send()
mto=jid, else:
# mtype="headline", if not self.client_roster[jid]["to"]:
msubject="RSS News Bot", self.send_presence_subscription(
mbody=( pto=jid,
"Share online status to receive updates." pfrom=self.boundjid.bare,
), ptype="subscribe",
mfrom=self.boundjid.bare, pnick=self.alias
mnick=self.nick )
) self.send_message(
self.send_presence( mto=jid,
pto=jid, mfrom=self.boundjid.bare,
pfrom=self.boundjid.bare, # mtype="headline",
# Accept symbol 🉑️ 👍️ ✍ msubject="RSS News Bot",
pstatus=( mbody=(
"✒️ Share online status to receive updates." "Share online status to receive updates."
), ),
# ptype="subscribe", mnick=self.alias
pnick=self.nick )
) 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): async def unsubscribed(self, presence):
jid = presence["from"].bare jid = presence["from"].bare
self.send_message( self.send_message(
mto=jid, mto=jid,
mfrom=self.boundjid.bare,
mbody="You have been unsubscribed." mbody="You have been unsubscribed."
) )
self.send_presence( self.send_presence(
pto=jid, pto=jid,
pfrom=self.boundjid.bare, pfrom=self.boundjid.bare,
pstatus="🖋️ Subscribe to receive updates", pstatus="🖋️ Subscribe to receive updates",
pnick=self.nick pnick=self.alias
)
self.update_roster(
jid,
subscription="remove"
) )