Cleaned up Mattermost Integration (#355)

pull/354/head
Chris Caron 2021-02-06 14:55:49 -05:00 committed by GitHub
parent f18b7c83ac
commit 5f945ceef7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 62 deletions

View File

@ -23,6 +23,16 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# 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 requests
from json import dumps
@ -40,13 +50,13 @@ from ..AppriseLocale import gettext_lazy as _
# - 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
service_name = 'MatterMost'
service_name = 'Mattermost'
# The services URL
service_url = 'https://mattermost.com/'
@ -74,14 +84,14 @@ class NotifyMatterMost(NotifyBase):
# Define object templates
templates = (
'{schema}://{host}/{authtoken}',
'{schema}://{host}/{authtoken}:{port}',
'{schema}://{botname}@{host}/{authtoken}',
'{schema}://{botname}@{host}:{port}/{authtoken}',
'{schema}://{host}/{fullpath}/{authtoken}',
'{schema}://{host}/{fullpath}{authtoken}:{port}',
'{schema}://{botname}@{host}/{fullpath}/{authtoken}',
'{schema}://{botname}@{host}:{port}/{fullpath}/{authtoken}',
'{schema}://{host}/{token}',
'{schema}://{host}/{token}:{port}',
'{schema}://{botname}@{host}/{token}',
'{schema}://{botname}@{host}:{port}/{token}',
'{schema}://{host}/{fullpath}/{token}',
'{schema}://{host}/{fullpath}{token}:{port}',
'{schema}://{botname}@{host}/{fullpath}/{token}',
'{schema}://{botname}@{host}:{port}/{fullpath}/{token}',
)
# Define our template tokens
@ -91,10 +101,9 @@ class NotifyMatterMost(NotifyBase):
'type': 'string',
'required': True,
},
'authtoken': {
'name': _('Access Key'),
'token': {
'name': _('Webhook Token'),
'type': 'string',
'regex': (r'^[a-z0-9]{24,32}$', 'i'),
'private': 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):
"""
Initialize MatterMost Object
Initialize Mattermost Object
"""
super(NotifyMatterMost, self).__init__(**kwargs)
super(NotifyMattermost, self).__init__(**kwargs)
if self.secure:
self.schema = 'https'
@ -150,16 +159,15 @@ class NotifyMatterMost(NotifyBase):
fullpath, six.string_types) else fullpath.strip()
# Authorization Token (associated with project)
self.authtoken = validate_regex(
authtoken, *self.template_tokens['authtoken']['regex'])
if not self.authtoken:
msg = 'An invalid MatterMost Authorization Token ' \
'({}) was specified.'.format(authtoken)
self.token = validate_regex(token)
if not self.token:
msg = 'An invalid Mattermost Authorization Token ' \
'({}) was specified.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
# Optional Channels
self.channels = parse_list(channels)
# Optional Channels (strip off any channel prefix entries if present)
self.channels = [x.lstrip('#') for x in parse_list(channels)]
if not self.port:
self.port = self.default_port
@ -171,7 +179,7 @@ class NotifyMatterMost(NotifyBase):
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
@ -211,12 +219,12 @@ class NotifyMatterMost(NotifyBase):
url = '{}://{}:{}{}/hooks/{}'.format(
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,
))
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
self.throttle()
@ -233,11 +241,11 @@ class NotifyMatterMost(NotifyBase):
if r.status_code != requests.codes.ok:
# We had a problem
status_str = \
NotifyMatterMost.http_response_code_lookup(
NotifyMattermost.http_response_code_lookup(
r.status_code)
self.logger.warning(
'Failed to send MatterMost notification{}: '
'Failed to send Mattermost notification{}: '
'{}{}error={}.'.format(
'' if not channel
else ' to channel {}'.format(channel),
@ -254,13 +262,13 @@ class NotifyMatterMost(NotifyBase):
else:
self.logger.info(
'Sent MatterMost notification{}.'.format(
'Sent Mattermost notification{}.'.format(
'' if not channel
else ' to channel {}'.format(channel)))
except requests.RequestException as e:
self.logger.warning(
'A Connection error occurred sending MatterMost '
'A Connection error occurred sending Mattermost '
'notification{}.'.format(
'' if not channel
else ' to channel {}'.format(channel)))
@ -290,7 +298,8 @@ class NotifyMatterMost(NotifyBase):
# historically the value only accepted one channel and is
# therefore identified as 'channel'. Channels have always been
# 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_schema = self.secure_protocol if self.secure else self.protocol
@ -299,11 +308,11 @@ class NotifyMatterMost(NotifyBase):
botname = ''
if self.user:
botname = '{botname}@'.format(
botname=NotifyMatterMost.quote(self.user, safe=''),
botname=NotifyMattermost.quote(self.user, safe=''),
)
return \
'{schema}://{botname}{hostname}{port}{fullpath}{authtoken}' \
'{schema}://{botname}{hostname}{port}{fullpath}{token}' \
'/?{params}'.format(
schema=default_schema,
botname=botname,
@ -313,9 +322,9 @@ class NotifyMatterMost(NotifyBase):
port='' if not self.port or self.port == default_port
else ':{}'.format(self.port),
fullpath='/' if not self.fullpath else '{}/'.format(
NotifyMatterMost.quote(self.fullpath, safe='/')),
authtoken=self.pprint(self.authtoken, privacy, safe=''),
params=NotifyMatterMost.urlencode(params),
NotifyMattermost.quote(self.fullpath, safe='/')),
token=self.pprint(self.token, privacy, safe=''),
params=NotifyMattermost.urlencode(params),
)
@staticmethod
@ -330,11 +339,11 @@ class NotifyMatterMost(NotifyBase):
# We're done early as we couldn't load the 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
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
results['fullpath'] = '' if not tokens \
@ -347,12 +356,12 @@ class NotifyMatterMost(NotifyBase):
if 'to' in results['qsd'] and len(results['qsd']['to']):
# Allow the user to specify the channel to post to
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']):
# Allow the user to specify the channel to post to
results['channels'].append(
NotifyMatterMost.parse_list(results['qsd']['channel']))
NotifyMattermost.parse_list(results['qsd']['channel']))
# Image manipulation
results['include_image'] = \

View File

@ -49,7 +49,7 @@ it easy to access:
Boxcar, ClickSend, Discord, E-Mail, Emby, Faast, FCM, Flock, Gitter, Google
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,
Opsgenie, ParsePlatform, PopcornNotify, Prowl, Pushalot, PushBullet, Pushjet,
Pushover, PushSafer, Rocket.Chat, SendGrid, SimplePush, Sinch, Slack, Spontit,

View File

@ -1780,7 +1780,7 @@ TEST_URLS = (
}),
##################################
# NotifyMatterMost
# NotifyMattermost
##################################
('mmost://', {
'instance': None,
@ -1796,64 +1796,64 @@ TEST_URLS = (
'instance': TypeError,
}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
}),
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?channel=test', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
}),
('mmost://user@localhost/3ccdd113474722377935511fc85d3dd4?to=test', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mmost://user@localhost/3...4/',
}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4'
'?to=test&image=True', {
'instance': plugins.NotifyMatterMost}),
'instance': plugins.NotifyMattermost}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
'?to=test&image=False', {
'instance': plugins.NotifyMatterMost}),
'instance': plugins.NotifyMattermost}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4' \
'?to=test&image=True', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
# don't include an image by default
'include_image': False}),
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mmost://localhost:8080/3...4/',
}),
('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
}),
('mmost://localhost:invalid-port/3ccdd113474722377935511fc85d3dd4', {
'instance': None,
}),
('mmosts://localhost/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
}),
# Test our paths
('mmosts://localhost/a/path/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
}),
('mmosts://localhost/////3ccdd113474722377935511fc85d3dd4///', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('mmost://localhost/3ccdd113474722377935511fc85d3dd4', {
'instance': plugins.NotifyMatterMost,
'instance': plugins.NotifyMattermost,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
@ -6362,7 +6362,7 @@ def test_notify_kumulos_plugin():
def test_notify_mattermost_plugin():
"""
API: NotifyMatterMost() Extra Checks
API: NotifyMattermost() Extra Checks
"""
# Disable Throttling to speed testing
@ -6370,9 +6370,9 @@ def test_notify_mattermost_plugin():
# Invalid Authorization Token
with pytest.raises(TypeError):
plugins.NotifyMatterMost(None)
plugins.NotifyMattermost(None)
with pytest.raises(TypeError):
plugins.NotifyMatterMost(" ")
plugins.NotifyMattermost(" ")
@mock.patch('requests.post')