Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ The table below identifies the services this tool supports and some example serv
| [WhatsApp](https://github.com/caronc/apprise/wiki/Notify_whatsapp) | whatsapp:// | (TCP) 443 | whatsapp://AccessToken@FromPhoneID/ToPhoneNo<br/>whatsapp://Template:AccessToken@FromPhoneID/ToPhoneNo
| [WxPusher](https://github.com/caronc/apprise/wiki/Notify_wxpusher) | wxpusher:// | (TCP) 443 | wxpusher://AppToken@UserID1/UserID2/UserIDN<br/>wxpusher://AppToken@Topic1/Topic2/Topic3<br/>wxpusher://AppToken@UserID1/Topic1/
| [XBMC](https://github.com/caronc/apprise/wiki/Notify_xbmc) | xbmc:// or xbmcs:// | (TCP) 8080 or 443 | xbmc://hostname<br />xbmc://user@hostname<br />xbmc://user:password@hostname:port
| [XMPP](https://github.com/caronc/apprise/wiki/Notify_xmpp) | xmpp:// or xmpps:// | (TCP) 5222 or 5223 | xmpp://user:password@hostname<br />xmpps://user:password@hostname:port?jid=user@hostname/resource<br />xmpps://user:password@hostname/targetuser@somehost, othertargetuser@otherhost/resource
| [Zulip Chat](https://github.com/caronc/apprise/wiki/Notify_zulip) | zulip:// | (TCP) 443 | zulip://botname@Organization/Token<br />zulip://botname@Organization/Token/Stream<br />zulip://botname@Organization/Token/Email

## SMS Notifications
Expand Down
3 changes: 3 additions & 0 deletions all-plugin-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# Provides fcm:// and spush://
cryptography

# Provides xmpp:// support
slixmpp >= 1.12.0; python_version >= '3.7'

# Provides growl:// support
gntp

Expand Down
221 changes: 221 additions & 0 deletions apprise/plugins/NotifyXMPP/SliXmppAdapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
#
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import logging
from os.path import isfile
import ssl

# Default our global support flag
SLIXMPP_SUPPORT_AVAILABLE = False

try:
# Import slixmpp if available
import asyncio

import slixmpp

SLIXMPP_SUPPORT_AVAILABLE = True

except ImportError:
# No problem; we just simply can't support this plugin because we're
# either using Linux, or simply do not have slixmpp installed.
pass


class SliXmppAdapter:
"""
Wrapper to slixmpp

"""

# Reference to XMPP client.
xmpp = None

# Whether everything succeeded
success = False

# The default protocol
protocol = "xmpp"

# The default secure protocol
secure_protocol = "xmpps"

# Taken from https://golang.org/src/crypto/x509/root_linux.go
CA_CERTIFICATE_FILE_LOCATIONS = [
# Debian/Ubuntu/Gentoo etc.
"/etc/ssl/certs/ca-certificates.crt",
# Fedora/RHEL 6
"/etc/pki/tls/certs/ca-bundle.crt",
# OpenSUSE
"/etc/ssl/ca-bundle.pem",
# OpenELEC
"/etc/pki/tls/cacert.pem",
# CentOS/RHEL 7
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
]

# This entry is a bit hacky, but it allows us to unit-test this library
# in an environment that simply doesn't have the slixmpp package
# available to us.
#
# If anyone is seeing this had knows a better way of testing this
# outside of what is defined in test/test_xmpp_plugin.py, please
# let me know! :)
_enabled = SLIXMPP_SUPPORT_AVAILABLE

def __init__(self, host=None, port=None, secure=False,
verify_certificate=True, xep=None, jid=None, password=None,
body=None, subject=None, targets=None, before_message=None,
logger=None):
"""
Initialize our SliXmppAdapter object
"""

self.host = host
self.port = port
self.secure = secure
self.verify_certificate = verify_certificate

self.xep = xep
self.jid = jid
self.password = password

self.body = body
self.subject = subject
self.targets = targets
self.before_message = before_message

self.logger = logger or logging.getLogger(__name__)

# Use the Apprise log handlers for configuring the slixmpp logger.
apprise_logger = logging.getLogger("apprise")
sli_logger = logging.getLogger("slixmpp")
for handler in apprise_logger.handlers:
sli_logger.addHandler(handler)
sli_logger.setLevel(apprise_logger.level)

if not self.load():
raise ValueError("Invalid XMPP Configuration")

def load(self):
try:
asyncio.get_event_loop()

except RuntimeError:
# slixmpp can not handle not having an event_loop
# see: https://codeberg.org/poezio/slixmpp/issues/3456
# This is a work-around to this problem
asyncio.set_event_loop(asyncio.new_event_loop())

self.xmpp = slixmpp.ClientXMPP(self.jid, self.password)

# Register our session
self.xmpp.add_event_handler("session_start", self.session_start)

for xep in self.xep:
# Load xep entries
try:
self.xmpp.register_plugin("xep_{:04d}".format(xep))

except slixmpp.plugins.base.PluginNotFound:
self.logger.warning(
"Could not register plugin {}".format(
"xep_{:04d}".format(xep)))
return False

if self.secure:
# Don't even try to use the outdated ssl.PROTOCOL_SSLx
self.xmpp.ssl_version = ssl.PROTOCOL_TLSv1

# If the python version supports it, use highest TLS version
# automatically
if hasattr(ssl, "PROTOCOL_TLS"):
# Use the best version of TLS available to us
self.xmpp.ssl_version = ssl.PROTOCOL_TLS

self.xmpp.ca_certs = None
if self.verify_certificate:
# Set the ca_certs variable for certificate verification
self.xmpp.ca_certs = next(
(cert for cert in self.CA_CERTIFICATE_FILE_LOCATIONS
if isfile(cert)), None)

if self.xmpp.ca_certs is None:
self.logger.warning(
"XMPP Secure comunication can not be verified; "
"no local CA certificate file")
return False

# If the user specified a port, skip SRV resolving
# by leaving the host set, otherwise it is a lot easier
# to let slixmpp handle DNS instead of the user.
if not self.port:
self.host = None

# We're good
return True

def process(self):
"""
Thread that handles the server/client i/o

"""

self.xmpp.connect(self.host, self.port)

# Run the asyncio event loop, and return once disconnected,
# for any reason.
asyncio.get_event_loop().run_until_complete(self.xmpp.disconnected)

return self.success

async def session_start(self, *args, **kwargs):
"""
Session Manager
"""

self.xmpp.send_presence()
await self.xmpp.get_roster()

targets = list(self.targets)
if not targets:
# We always default to notifying ourselves
targets.append(self.jid)

while len(targets) > 0:
# Get next target (via JID)
target = targets.pop(0)

# Invoke "before_message" event hook.
self.before_message()

# The message we wish to send, and the JID that will receive it.
self.xmpp.send_message(
mto=target, msubject=self.subject,
mbody=self.body, mtype="chat")

self.xmpp.disconnect()

# Toggle our success flag
self.success = True
Loading
Loading