mirror of
https://github.com/caronc/apprise.git
synced 2024-11-08 09:14:53 +01:00
Merged Matrix Webhook functionality with server config; refs #80
This commit is contained in:
parent
426092cf3f
commit
28e490ab67
@ -43,7 +43,7 @@ The table below identifies the services this tool supports and some example serv
|
||||
| [IFTTT](https://github.com/caronc/apprise/wiki/Notify_ifttt) | ifttt:// | (TCP) 443 | ifttt://webhooksID/Event<br />ifttt://webhooksID/Event1/Event2/EventN<br/>ifttt://webhooksID/Event1/?+Key=Value<br/>ifttt://webhooksID/Event1/?-Key=value1
|
||||
| [Join](https://github.com/caronc/apprise/wiki/Notify_join) | join:// | (TCP) 443 | join://apikey/device<br />join://apikey/device1/device2/deviceN/<br />join://apikey/group<br />join://apikey/groupA/groupB/groupN<br />join://apikey/DeviceA/groupA/groupN/DeviceN/
|
||||
| [KODI](https://github.com/caronc/apprise/wiki/Notify_kodi) | kodi:// or kodis:// | (TCP) 8080 or 443 | kodi://hostname<br />kodi://user@hostname<br />kodi://user:password@hostname:port
|
||||
| [Matrix](https://github.com/caronc/apprise/wiki/Notify_matrix) | matrix:// or matrixs:// | (TCP) 8008 or 8448 | matrix://hostname<br />matrix://user@hostname<br />matrixs://user:pass@hostname:port/#room_alias<br />matrixs://user:pass@hostname:port/!room_id<br />matrixs://user:pass@hostname:port/#room_alias/!room_id/#room2
|
||||
| [Matrix](https://github.com/caronc/apprise/wiki/Notify_matrix) | matrix:// or matrixs:// | (TCP) 80 or 443 | matrix://hostname<br />matrix://user@hostname<br />matrixs://user:pass@hostname:port/#room_alias<br />matrixs://user:pass@hostname:port/!room_id<br />matrixs://user:pass@hostname:port/#room_alias/!room_id/#room2<br />matrixs://token@hostname:port/?webhook=matrix<br />matrix://user:token@hostname/?webhook=slack&format=markdown
|
||||
| [Mattermost](https://github.com/caronc/apprise/wiki/Notify_mattermost) | mmost:// | (TCP) 8065 | mmost://hostname/authkey<br />mmost://hostname:80/authkey<br />mmost://user@hostname:80/authkey<br />mmost://hostname/authkey?channel=channel<br />mmosts://hostname/authkey<br />mmosts://user@hostname/authkey<br />
|
||||
| [Prowl](https://github.com/caronc/apprise/wiki/Notify_prowl) | prowl:// | (TCP) 443 | prowl://apikey<br />prowl://apikey/providerkey
|
||||
| [PushBullet](https://github.com/caronc/apprise/wiki/Notify_pushbullet) | pbul:// | (TCP) 443 | pbul://accesstoken<br />pbul://accesstoken/#channel<br/>pbul://accesstoken/A_DEVICE_ID<br />pbul://accesstoken/email@address.com<br />pbul://accesstoken/#channel/#channel2/email@address.net/DEVICE
|
||||
|
@ -32,14 +32,17 @@ import six
|
||||
import requests
|
||||
from json import dumps
|
||||
from json import loads
|
||||
from time import time
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyFormat
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Define default path
|
||||
MATRIX_V2_API_PATH = '/_matrix/client/r0'
|
||||
MATRIX_V1_WEBHOOK_PATH = '/api/v1/matrix/hook'
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
MATRIX_HTTP_ERROR_MAP = {
|
||||
@ -61,6 +64,24 @@ IS_ROOM_ID = re.compile(
|
||||
r'^\s*(!|!|%21)(?P<room>[a-z0-9-]+)((:|%3A)'
|
||||
r'(?P<home_server>[a-z0-9.-]+))?\s*$', re.I)
|
||||
|
||||
# Default User
|
||||
SLACK_DEFAULT_USER = 'apprise'
|
||||
|
||||
|
||||
class MatrixWebhookMode(object):
|
||||
# The default webhook mode is to just be set to Matrix
|
||||
MATRIX = "matrix"
|
||||
|
||||
# Support the slack webhook plugin
|
||||
SLACK = "slack"
|
||||
|
||||
|
||||
# webhook modes are placed ito this list for validation purposes
|
||||
MATRIX_WEBHOOK_MODES = (
|
||||
MatrixWebhookMode.MATRIX,
|
||||
MatrixWebhookMode.SLACK,
|
||||
)
|
||||
|
||||
|
||||
class NotifyMatrix(NotifyBase):
|
||||
"""
|
||||
@ -79,12 +100,6 @@ class NotifyMatrix(NotifyBase):
|
||||
# The default secure protocol
|
||||
secure_protocol = 'matrixs'
|
||||
|
||||
# Define the default secure port to use
|
||||
default_secure_port = 8448
|
||||
|
||||
# Define the default insecure port to use
|
||||
default_insecure_port = 8008
|
||||
|
||||
# A URL that takes you to the setup/help of the specific protocol
|
||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_matrix'
|
||||
|
||||
@ -105,7 +120,7 @@ class NotifyMatrix(NotifyBase):
|
||||
# the server doesn't remind us how long we shoul wait for
|
||||
default_wait_ms = 1000
|
||||
|
||||
def __init__(self, rooms=None, thumbnail=True, **kwargs):
|
||||
def __init__(self, rooms=None, webhook=None, thumbnail=True, **kwargs):
|
||||
"""
|
||||
Initialize Matrix Object
|
||||
"""
|
||||
@ -123,6 +138,13 @@ class NotifyMatrix(NotifyBase):
|
||||
else:
|
||||
self.rooms = []
|
||||
|
||||
self.webhook = None \
|
||||
if not isinstance(webhook, six.string_types) else webhook.lower()
|
||||
if self.webhook and self.webhook not in MATRIX_WEBHOOK_MODES:
|
||||
msg = 'The webhook specified ({}) is invalid.'.format(webhook)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# our home server gets populated after a login/registration
|
||||
self.home_server = None
|
||||
|
||||
@ -144,6 +166,172 @@ class NotifyMatrix(NotifyBase):
|
||||
Perform Matrix Notification
|
||||
"""
|
||||
|
||||
# Call the _send_ function applicable to whatever mode we're in
|
||||
# - calls _send_webhook_notification if the webhook variable is set
|
||||
# - calls _send_server_notification if the webhook variable is not set
|
||||
return getattr(self, '_send_{}_notification'.format(
|
||||
'webhook' if self.webhook else 'server'))(
|
||||
body=body, title=title, notify_type=notify_type, **kwargs)
|
||||
|
||||
def _send_webhook_notification(self, body, title='',
|
||||
notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Matrix Notification as a webhook
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
# Acquire our access token from our URL
|
||||
access_token = self.password if self.password else self.user
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
# Prepare our URL
|
||||
url = '{schema}://{hostname}:{port}/{token}{webhook_path}'.format(
|
||||
schema='https' if self.secure else 'http',
|
||||
hostname=self.host,
|
||||
port='' if self.port is None
|
||||
or self.port == default_port else self.port,
|
||||
token=access_token,
|
||||
webhook_path=MATRIX_V1_WEBHOOK_PATH,
|
||||
)
|
||||
|
||||
# Retrieve our payload
|
||||
payload = getattr(self, '_{}_webhook_payload'.format(self.webhook))(
|
||||
body=body, title=title, notify_type=notify_type, **kwargs)
|
||||
|
||||
self.logger.debug('Matrix POST URL: %s (cert_verify=%r)' % (
|
||||
url, self.verify_certificate,
|
||||
))
|
||||
self.logger.debug('Matrix Payload: %s' % str(payload))
|
||||
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
url,
|
||||
data=dumps(payload),
|
||||
headers=headers,
|
||||
verify=self.verify_certificate,
|
||||
)
|
||||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Matrix notification: '
|
||||
'{}{}error={}.'.format(
|
||||
status_str,
|
||||
', ' if status_str else '',
|
||||
r.status_code))
|
||||
|
||||
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
||||
|
||||
# Return; we're done
|
||||
return False
|
||||
|
||||
else:
|
||||
self.logger.info('Sent Matrix notification.')
|
||||
|
||||
except requests.RequestException as e:
|
||||
self.logger.warning(
|
||||
'A Connection error occured sending Matrix notification.'
|
||||
)
|
||||
self.logger.debug('Socket Exception: %s' % str(e))
|
||||
# Return; we're done
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _slack_webhook_payload(self, body, title='',
|
||||
notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Format the payload for a Slack based message
|
||||
|
||||
"""
|
||||
|
||||
if not hasattr(self, '_re_slack_formatting_rules'):
|
||||
# Prepare some one-time slack formating variables
|
||||
|
||||
self._re_slack_formatting_map = {
|
||||
# New lines must become the string version
|
||||
r'\r\*\n': '\\n',
|
||||
# Escape other special characters
|
||||
r'&': '&',
|
||||
r'<': '<',
|
||||
r'>': '>',
|
||||
}
|
||||
|
||||
# Iterate over above list and store content accordingly
|
||||
self._re_slack_formatting_rules = re.compile(
|
||||
r'(' + '|'.join(self._re_slack_formatting_map.keys()) + r')',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Perform Formatting
|
||||
title = self._re_slack_formatting_rules.sub( # pragma: no branch
|
||||
lambda x: self._re_slack_formatting_map[x.group()], title,
|
||||
)
|
||||
|
||||
body = self._re_slack_formatting_rules.sub( # pragma: no branch
|
||||
lambda x: self._re_slack_formatting_map[x.group()], body,
|
||||
)
|
||||
|
||||
# prepare JSON Object
|
||||
payload = {
|
||||
'username': self.user if self.user else SLACK_DEFAULT_USER,
|
||||
# Use Markdown language
|
||||
'mrkdwn': (self.notify_format == NotifyFormat.MARKDOWN),
|
||||
'attachments': [{
|
||||
'title': title,
|
||||
'text': body,
|
||||
'color': self.color(notify_type),
|
||||
'ts': time(),
|
||||
'footer': self.app_id,
|
||||
}],
|
||||
}
|
||||
|
||||
return payload
|
||||
|
||||
def _matrix_webhook_payload(self, body, title='',
|
||||
notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Format the payload for a Matrix based message
|
||||
|
||||
"""
|
||||
|
||||
payload = {
|
||||
'displayName':
|
||||
self.user if self.user else self.matrix_default_user,
|
||||
'format': 'html',
|
||||
}
|
||||
|
||||
if self.notify_format == NotifyFormat.HTML:
|
||||
payload['text'] = '{}{}'.format('' if not title else title, body)
|
||||
|
||||
else: # TEXT or MARKDOWN
|
||||
|
||||
# Ensure our content is escaped
|
||||
title = NotifyBase.escape_html(title)
|
||||
body = NotifyBase.escape_html(body)
|
||||
|
||||
payload['text'] = '{}{}'.format(
|
||||
'' if not title else '<h4>{}</h4>'.format(title), body)
|
||||
|
||||
return payload
|
||||
|
||||
def _send_server_notification(self, body, title='',
|
||||
notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Direct Matrix Server Notification (no webhook)
|
||||
"""
|
||||
|
||||
if self.access_token is None:
|
||||
# We need to register
|
||||
if not self._login():
|
||||
@ -573,14 +761,14 @@ class NotifyMatrix(NotifyBase):
|
||||
if self.access_token is not None:
|
||||
headers["Authorization"] = 'Bearer %s' % self.access_token
|
||||
|
||||
default_port = self.default_secure_port \
|
||||
if self.secure else self.default_insecure_port
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
url = \
|
||||
'{schema}://{hostname}:{port}{matrix_api}{path}'.format(
|
||||
schema='https' if self.secure else 'http',
|
||||
hostname=self.host,
|
||||
port=default_port if self.port is None else self.port,
|
||||
port='' if self.port is None
|
||||
or self.port == default_port else self.port,
|
||||
matrix_api=MATRIX_V2_API_PATH,
|
||||
path=path)
|
||||
|
||||
@ -691,6 +879,9 @@ class NotifyMatrix(NotifyBase):
|
||||
'overflow': self.overflow_mode,
|
||||
}
|
||||
|
||||
if self.webhook:
|
||||
args['webhook'] = self.webhook
|
||||
|
||||
# Determine Authentication method
|
||||
auth = ''
|
||||
if self.user and self.password:
|
||||
@ -704,15 +895,14 @@ class NotifyMatrix(NotifyBase):
|
||||
user=self.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = self.default_secure_port \
|
||||
if self.secure else self.default_insecure_port
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}/{rooms}?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
port='' if self.port is None
|
||||
or self.port == default_port else ':{}'.format(self.port),
|
||||
rooms=self.quote('/'.join(self.rooms)),
|
||||
args=self.urlencode(args),
|
||||
)
|
||||
@ -739,4 +929,7 @@ class NotifyMatrix(NotifyBase):
|
||||
results['thumbnail'] = \
|
||||
parse_bool(results['qsd'].get('thumbnail', False))
|
||||
|
||||
# Webhook
|
||||
results['webhook'] = results['qsd'].get('webhook')
|
||||
|
||||
return results
|
||||
|
@ -583,6 +583,47 @@ TEST_URLS = (
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
|
||||
# Matrix supports webhooks too; the following tests this now:
|
||||
('matrix://user:token@localhost?webhook=matrix&format=text', {
|
||||
# user and token correctly specified with webhook
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://user:token@localhost?webhook=matrix&format=html', {
|
||||
# user and token correctly specified with webhook
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://user:token@localhost?webhook=slack&format=text', {
|
||||
# user and token correctly specified with webhook
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrixs://user:token@localhost?webhook=SLACK&format=markdown', {
|
||||
# user and token specified; slack webhook still detected
|
||||
# despite uppercase characters
|
||||
'instance': plugins.NotifyMatrix,
|
||||
}),
|
||||
('matrix://user:token@localhost?webhook=On', {
|
||||
# invalid webhook specified (unexpected boolean)
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('matrix://token@localhost/?webhook=Matrix', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('matrix://user:token@localhost/webhook=matrix', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('matrix://token@localhost:8080/?webhook=slack', {
|
||||
'instance': plugins.NotifyMatrix,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
|
||||
|
||||
##################################
|
||||
# NotifyMatterMost
|
||||
##################################
|
||||
|
Loading…
Reference in New Issue
Block a user