mirror of https://github.com/caronc/apprise
Cleaned up Mattermost Integration (#355)
parent
f18b7c83ac
commit
5f945ceef7
|
@ -23,6 +23,16 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# Create an incoming webhook; the website will provide you with something like:
|
||||||
|
# http://localhost:8065/hooks/yobjmukpaw3r3urc5h6i369yima
|
||||||
|
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
# |-- this is the webhook --|
|
||||||
|
#
|
||||||
|
# You can effectively turn the url above to read this:
|
||||||
|
# mmost://localhost:8065/yobjmukpaw3r3urc5h6i369yima
|
||||||
|
# - swap http with mmost
|
||||||
|
# - drop /hooks/ reference
|
||||||
|
|
||||||
import six
|
import six
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
@ -40,13 +50,13 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
# - https://docs.mattermost.com/administration/config-settings.html
|
# - https://docs.mattermost.com/administration/config-settings.html
|
||||||
|
|
||||||
|
|
||||||
class NotifyMatterMost(NotifyBase):
|
class NotifyMattermost(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper for MatterMost Notifications
|
A wrapper for Mattermost Notifications
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The default descriptive name associated with the Notification
|
# The default descriptive name associated with the Notification
|
||||||
service_name = 'MatterMost'
|
service_name = 'Mattermost'
|
||||||
|
|
||||||
# The services URL
|
# The services URL
|
||||||
service_url = 'https://mattermost.com/'
|
service_url = 'https://mattermost.com/'
|
||||||
|
@ -74,14 +84,14 @@ class NotifyMatterMost(NotifyBase):
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{host}/{authtoken}',
|
'{schema}://{host}/{token}',
|
||||||
'{schema}://{host}/{authtoken}:{port}',
|
'{schema}://{host}/{token}:{port}',
|
||||||
'{schema}://{botname}@{host}/{authtoken}',
|
'{schema}://{botname}@{host}/{token}',
|
||||||
'{schema}://{botname}@{host}:{port}/{authtoken}',
|
'{schema}://{botname}@{host}:{port}/{token}',
|
||||||
'{schema}://{host}/{fullpath}/{authtoken}',
|
'{schema}://{host}/{fullpath}/{token}',
|
||||||
'{schema}://{host}/{fullpath}{authtoken}:{port}',
|
'{schema}://{host}/{fullpath}{token}:{port}',
|
||||||
'{schema}://{botname}@{host}/{fullpath}/{authtoken}',
|
'{schema}://{botname}@{host}/{fullpath}/{token}',
|
||||||
'{schema}://{botname}@{host}:{port}/{fullpath}/{authtoken}',
|
'{schema}://{botname}@{host}:{port}/{fullpath}/{token}',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template tokens
|
# Define our template tokens
|
||||||
|
@ -91,10 +101,9 @@ class NotifyMatterMost(NotifyBase):
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
'authtoken': {
|
'token': {
|
||||||
'name': _('Access Key'),
|
'name': _('Webhook Token'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'regex': (r'^[a-z0-9]{24,32}$', 'i'),
|
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -132,12 +141,12 @@ class NotifyMatterMost(NotifyBase):
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, authtoken, fullpath=None, channels=None,
|
def __init__(self, token, fullpath=None, channels=None,
|
||||||
include_image=False, **kwargs):
|
include_image=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize MatterMost Object
|
Initialize Mattermost Object
|
||||||
"""
|
"""
|
||||||
super(NotifyMatterMost, self).__init__(**kwargs)
|
super(NotifyMattermost, self).__init__(**kwargs)
|
||||||
|
|
||||||
if self.secure:
|
if self.secure:
|
||||||
self.schema = 'https'
|
self.schema = 'https'
|
||||||
|
@ -150,16 +159,15 @@ class NotifyMatterMost(NotifyBase):
|
||||||
fullpath, six.string_types) else fullpath.strip()
|
fullpath, six.string_types) else fullpath.strip()
|
||||||
|
|
||||||
# Authorization Token (associated with project)
|
# Authorization Token (associated with project)
|
||||||
self.authtoken = validate_regex(
|
self.token = validate_regex(token)
|
||||||
authtoken, *self.template_tokens['authtoken']['regex'])
|
if not self.token:
|
||||||
if not self.authtoken:
|
msg = 'An invalid Mattermost Authorization Token ' \
|
||||||
msg = 'An invalid MatterMost Authorization Token ' \
|
'({}) was specified.'.format(token)
|
||||||
'({}) was specified.'.format(authtoken)
|
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Optional Channels
|
# Optional Channels (strip off any channel prefix entries if present)
|
||||||
self.channels = parse_list(channels)
|
self.channels = [x.lstrip('#') for x in parse_list(channels)]
|
||||||
|
|
||||||
if not self.port:
|
if not self.port:
|
||||||
self.port = self.default_port
|
self.port = self.default_port
|
||||||
|
@ -171,7 +179,7 @@ class NotifyMatterMost(NotifyBase):
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform MatterMost Notification
|
Perform Mattermost Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create a copy of our channels, otherwise place a dummy entry
|
# Create a copy of our channels, otherwise place a dummy entry
|
||||||
|
@ -211,12 +219,12 @@ class NotifyMatterMost(NotifyBase):
|
||||||
|
|
||||||
url = '{}://{}:{}{}/hooks/{}'.format(
|
url = '{}://{}:{}{}/hooks/{}'.format(
|
||||||
self.schema, self.host, self.port, self.fullpath,
|
self.schema, self.host, self.port, self.fullpath,
|
||||||
self.authtoken)
|
self.token)
|
||||||
|
|
||||||
self.logger.debug('MatterMost POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('Mattermost POST URL: %s (cert_verify=%r)' % (
|
||||||
url, self.verify_certificate,
|
url, self.verify_certificate,
|
||||||
))
|
))
|
||||||
self.logger.debug('MatterMost Payload: %s' % str(payload))
|
self.logger.debug('Mattermost Payload: %s' % str(payload))
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
# Always call throttle before any remote server i/o is made
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
@ -233,11 +241,11 @@ class NotifyMatterMost(NotifyBase):
|
||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyMatterMost.http_response_code_lookup(
|
NotifyMattermost.http_response_code_lookup(
|
||||||
r.status_code)
|
r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send MatterMost notification{}: '
|
'Failed to send Mattermost notification{}: '
|
||||||
'{}{}error={}.'.format(
|
'{}{}error={}.'.format(
|
||||||
'' if not channel
|
'' if not channel
|
||||||
else ' to channel {}'.format(channel),
|
else ' to channel {}'.format(channel),
|
||||||
|
@ -254,13 +262,13 @@ class NotifyMatterMost(NotifyBase):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'Sent MatterMost notification{}.'.format(
|
'Sent Mattermost notification{}.'.format(
|
||||||
'' if not channel
|
'' if not channel
|
||||||
else ' to channel {}'.format(channel)))
|
else ' to channel {}'.format(channel)))
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occurred sending MatterMost '
|
'A Connection error occurred sending Mattermost '
|
||||||
'notification{}.'.format(
|
'notification{}.'.format(
|
||||||
'' if not channel
|
'' if not channel
|
||||||
else ' to channel {}'.format(channel)))
|
else ' to channel {}'.format(channel)))
|
||||||
|
@ -290,7 +298,8 @@ class NotifyMatterMost(NotifyBase):
|
||||||
# historically the value only accepted one channel and is
|
# historically the value only accepted one channel and is
|
||||||
# therefore identified as 'channel'. Channels have always been
|
# therefore identified as 'channel'. Channels have always been
|
||||||
# optional, so that is why this setting is nested in an if block
|
# optional, so that is why this setting is nested in an if block
|
||||||
params['channel'] = ','.join(self.channels)
|
params['channel'] = ','.join(
|
||||||
|
[NotifyMattermost.quote(x, safe='') for x in self.channels])
|
||||||
|
|
||||||
default_port = 443 if self.secure else self.default_port
|
default_port = 443 if self.secure else self.default_port
|
||||||
default_schema = self.secure_protocol if self.secure else self.protocol
|
default_schema = self.secure_protocol if self.secure else self.protocol
|
||||||
|
@ -299,11 +308,11 @@ class NotifyMatterMost(NotifyBase):
|
||||||
botname = ''
|
botname = ''
|
||||||
if self.user:
|
if self.user:
|
||||||
botname = '{botname}@'.format(
|
botname = '{botname}@'.format(
|
||||||
botname=NotifyMatterMost.quote(self.user, safe=''),
|
botname=NotifyMattermost.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return \
|
return \
|
||||||
'{schema}://{botname}{hostname}{port}{fullpath}{authtoken}' \
|
'{schema}://{botname}{hostname}{port}{fullpath}{token}' \
|
||||||
'/?{params}'.format(
|
'/?{params}'.format(
|
||||||
schema=default_schema,
|
schema=default_schema,
|
||||||
botname=botname,
|
botname=botname,
|
||||||
|
@ -313,9 +322,9 @@ class NotifyMatterMost(NotifyBase):
|
||||||
port='' if not self.port or self.port == default_port
|
port='' if not self.port or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
fullpath='/' if not self.fullpath else '{}/'.format(
|
fullpath='/' if not self.fullpath else '{}/'.format(
|
||||||
NotifyMatterMost.quote(self.fullpath, safe='/')),
|
NotifyMattermost.quote(self.fullpath, safe='/')),
|
||||||
authtoken=self.pprint(self.authtoken, privacy, safe=''),
|
token=self.pprint(self.token, privacy, safe=''),
|
||||||
params=NotifyMatterMost.urlencode(params),
|
params=NotifyMattermost.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -330,11 +339,11 @@ class NotifyMatterMost(NotifyBase):
|
||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Acquire our tokens; the last one will always be our authtoken
|
# Acquire our tokens; the last one will always be our token
|
||||||
# all entries before it will be our path
|
# all entries before it will be our path
|
||||||
tokens = NotifyMatterMost.split_path(results['fullpath'])
|
tokens = NotifyMattermost.split_path(results['fullpath'])
|
||||||
|
|
||||||
results['authtoken'] = None if not tokens else tokens.pop()
|
results['token'] = None if not tokens else tokens.pop()
|
||||||
|
|
||||||
# Store our path
|
# Store our path
|
||||||
results['fullpath'] = '' if not tokens \
|
results['fullpath'] = '' if not tokens \
|
||||||
|
@ -347,12 +356,12 @@ class NotifyMatterMost(NotifyBase):
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
# Allow the user to specify the channel to post to
|
# Allow the user to specify the channel to post to
|
||||||
results['channels'].append(
|
results['channels'].append(
|
||||||
NotifyMatterMost.parse_list(results['qsd']['to']))
|
NotifyMattermost.parse_list(results['qsd']['to']))
|
||||||
|
|
||||||
if 'channel' in results['qsd'] and len(results['qsd']['channel']):
|
if 'channel' in results['qsd'] and len(results['qsd']['channel']):
|
||||||
# Allow the user to specify the channel to post to
|
# Allow the user to specify the channel to post to
|
||||||
results['channels'].append(
|
results['channels'].append(
|
||||||
NotifyMatterMost.parse_list(results['qsd']['channel']))
|
NotifyMattermost.parse_list(results['qsd']['channel']))
|
||||||
|
|
||||||
# Image manipulation
|
# Image manipulation
|
||||||
results['include_image'] = \
|
results['include_image'] = \
|
|
@ -49,7 +49,7 @@ it easy to access:
|
||||||
|
|
||||||
Boxcar, ClickSend, Discord, E-Mail, Emby, Faast, FCM, Flock, Gitter, Google
|
Boxcar, ClickSend, Discord, E-Mail, Emby, Faast, FCM, Flock, Gitter, Google
|
||||||
Chat, Gotify, Growl, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, MacOSX,
|
Chat, Gotify, Growl, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, MacOSX,
|
||||||
Mailgun, MatterMost, Matrix, Microsoft Windows, Microsoft Teams, MessageBird,
|
Mailgun, Mattermost, Matrix, Microsoft Windows, Microsoft Teams, MessageBird,
|
||||||
MSG91, MyAndroid, Nexmo, Nextcloud, Notica, Notifico, Office365, OneSignal,
|
MSG91, MyAndroid, Nexmo, Nextcloud, Notica, Notifico, Office365, OneSignal,
|
||||||
Opsgenie, ParsePlatform, PopcornNotify, Prowl, Pushalot, PushBullet, Pushjet,
|
Opsgenie, ParsePlatform, PopcornNotify, Prowl, Pushalot, PushBullet, Pushjet,
|
||||||
Pushover, PushSafer, Rocket.Chat, SendGrid, SimplePush, Sinch, Slack, Spontit,
|
Pushover, PushSafer, Rocket.Chat, SendGrid, SimplePush, Sinch, Slack, Spontit,
|
||||||
|
|
|
@ -1780,7 +1780,7 @@ TEST_URLS = (
|
||||||
}),
|
}),
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
# NotifyMatterMost
|
# NotifyMattermost
|
||||||
##################################
|
##################################
|
||||||
('mmost://', {
|
('mmost://', {
|
||||||
'instance': None,
|
'instance': None,
|
||||||
|
@ -1796,64 +1796,64 @@ TEST_URLS = (
|
||||||
'instance': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
}),
|
}),
|
||||||
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?channel=test', {
|
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?channel=test', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
}),
|
}),
|
||||||
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', {
|
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
|
|
||||||
# Our expected url(privacy=True) startswith() response:
|
# Our expected url(privacy=True) startswith() response:
|
||||||
'privacy_url': 'mmost://user@localhost/3...4/',
|
'privacy_url': 'mmost://user@localhost/3...4/',
|
||||||
}),
|
}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4'
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4'
|
||||||
'?to=test&image=True', {
|
'?to=test&image=True', {
|
||||||
'instance': plugins.NotifyMatterMost}),
|
'instance': plugins.NotifyMattermost}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
|
||||||
'?to=test&image=False', {
|
'?to=test&image=False', {
|
||||||
'instance': plugins.NotifyMatterMost}),
|
'instance': plugins.NotifyMattermost}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
|
||||||
'?to=test&image=True', {
|
'?to=test&image=True', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
# don't include an image by default
|
# don't include an image by default
|
||||||
'include_image': False}),
|
'include_image': False}),
|
||||||
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
|
|
||||||
# Our expected url(privacy=True) startswith() response:
|
# Our expected url(privacy=True) startswith() response:
|
||||||
'privacy_url': 'mmost://localhost:8080/3...4/',
|
'privacy_url': 'mmost://localhost:8080/3...4/',
|
||||||
}),
|
}),
|
||||||
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
}),
|
}),
|
||||||
('mmost://localhost:invalid-port/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost:invalid-port/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': None,
|
'instance': None,
|
||||||
}),
|
}),
|
||||||
('mmosts://localhost/3ccdd113474722377935511fc85d3dd4', {
|
('mmosts://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
}),
|
}),
|
||||||
# Test our paths
|
# Test our paths
|
||||||
('mmosts://localhost/a/path/3ccdd113474722377935511fc85d3dd4', {
|
('mmosts://localhost/a/path/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
}),
|
}),
|
||||||
('mmosts://localhost/////3ccdd113474722377935511fc85d3dd4///', {
|
('mmosts://localhost/////3ccdd113474722377935511fc85d3dd4///', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
}),
|
}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
# force a failure
|
# force a failure
|
||||||
'response': False,
|
'response': False,
|
||||||
'requests_response_code': requests.codes.internal_server_error,
|
'requests_response_code': requests.codes.internal_server_error,
|
||||||
}),
|
}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
# throw a bizzare code forcing us to fail to look it up
|
# throw a bizzare code forcing us to fail to look it up
|
||||||
'response': False,
|
'response': False,
|
||||||
'requests_response_code': 999,
|
'requests_response_code': 999,
|
||||||
}),
|
}),
|
||||||
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
|
||||||
'instance': plugins.NotifyMatterMost,
|
'instance': plugins.NotifyMattermost,
|
||||||
# Throws a series of connection and transfer exceptions when this flag
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
# is set and tests that we gracfully handle them
|
# is set and tests that we gracfully handle them
|
||||||
'test_requests_exceptions': True,
|
'test_requests_exceptions': True,
|
||||||
|
@ -6362,7 +6362,7 @@ def test_notify_kumulos_plugin():
|
||||||
|
|
||||||
def test_notify_mattermost_plugin():
|
def test_notify_mattermost_plugin():
|
||||||
"""
|
"""
|
||||||
API: NotifyMatterMost() Extra Checks
|
API: NotifyMattermost() Extra Checks
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Disable Throttling to speed testing
|
# Disable Throttling to speed testing
|
||||||
|
@ -6370,9 +6370,9 @@ def test_notify_mattermost_plugin():
|
||||||
|
|
||||||
# Invalid Authorization Token
|
# Invalid Authorization Token
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
plugins.NotifyMatterMost(None)
|
plugins.NotifyMattermost(None)
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
plugins.NotifyMatterMost(" ")
|
plugins.NotifyMattermost(" ")
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
|
|
Loading…
Reference in New Issue