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 # 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'] = \

View File

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

View File

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