From a888b85f8e558a0b69383ddb146732bc2a6ecd7f Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 29 Aug 2021 11:29:30 -0400 Subject: [PATCH] Matrix HTML and Markdown Support (#431) --- apprise/plugins/NotifyMatrix.py | 78 ++++++++++++++++++++++++++------- test/test_matrix_plugin.py | 46 ++++++++++++++++++- test/test_rest_plugins.py | 2 +- 3 files changed, 108 insertions(+), 18 deletions(-) diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py index fb93bd9c..96301c53 100644 --- a/apprise/plugins/NotifyMatrix.py +++ b/apprise/plugins/NotifyMatrix.py @@ -30,6 +30,7 @@ import re import six import requests +from markdown import markdown from json import dumps from json import loads from time import time @@ -41,6 +42,7 @@ from ..common import NotifyImageSize from ..common import NotifyFormat from ..utils import parse_bool from ..utils import parse_list +from ..utils import apply_template from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ @@ -415,20 +417,31 @@ class NotifyMatrix(NotifyBase): payload = { 'displayName': self.user if self.user else self.app_id, - 'format': 'html', + 'format': 'plain' if self.notify_format == NotifyFormat.TEXT + else 'html', + 'text': '', } if self.notify_format == NotifyFormat.HTML: - payload['text'] = '{}{}'.format('' if not title else title, body) + # Add additional information to our content; use {{app_title}} + # to apply the title to the html body + tokens = { + 'app_title': NotifyMatrix.escape_html( + title, whitespace=False), + } + payload['text'] = apply_template(body, **tokens) - else: # TEXT or MARKDOWN + elif self.notify_format == NotifyFormat.MARKDOWN: + # Add additional information to our content; use {{app_title}} + # to apply the title to the html body + tokens = { + 'app_title': title, + } + payload['text'] = markdown(apply_template(body, **tokens)) - # Ensure our content is escaped - title = NotifyMatrix.escape_html(title) - body = NotifyMatrix.escape_html(body) - - payload['text'] = '{}{}'.format( - '' if not title else '

{}

'.format(title), body) + else: # NotifyFormat.TEXT + payload['text'] = \ + body if not title else '{}\r\n{}'.format(title, body) return payload @@ -497,11 +510,6 @@ class NotifyMatrix(NotifyBase): has_error = True continue - # We have our data cached at this point we can freely use it - msg = '{title}{body}'.format( - title='' if not title else '{}\r\n'.format(title), - body=body) - # Acquire our image url if we're configured to do so image_url = None if not self.include_image else \ self.image_url(notify_type) @@ -527,9 +535,35 @@ class NotifyMatrix(NotifyBase): # Define our payload payload = { 'msgtype': 'm.text', - 'body': msg, + 'body': '{title}{body}'.format( + title='' if not title else '{}\r\n'.format(title), + body=body), } + # Update our payload advance formatting for the services that + # support them. + if self.notify_format == NotifyFormat.HTML: + # Add additional information to our content; use {{app_title}} + # to apply the title to the html body + tokens = { + 'app_title': NotifyMatrix.escape_html( + title, whitespace=False), + } + + payload.update({ + 'format': 'org.matrix.custom.html', + 'formatted_body': apply_template(body, **tokens), + }) + + elif self.notify_format == NotifyFormat.MARKDOWN: + tokens = { + 'app_title': title, + } + payload.update({ + 'format': 'org.matrix.custom.html', + 'formatted_body': markdown(apply_template(body, **tokens)) + }) + # Build our path path = '/rooms/{}/send/m.room.message'.format( NotifyMatrix.quote(room_id)) @@ -697,7 +731,7 @@ class NotifyMatrix(NotifyBase): # Prepare our Join Payload payload = {} - # Not in cache, next step is to check if it's a room id... + # Check if it's a room id... result = IS_ROOM_ID.match(room) if result: # We detected ourselves the home_server @@ -710,11 +744,23 @@ class NotifyMatrix(NotifyBase): home_server, ) + # Check our cache for speed: + if room_id in self._room_cache: + # We're done as we've already joined the channel + return self._room_cache[room_id]['id'] + # Build our URL path = '/join/{}'.format(NotifyMatrix.quote(room_id)) # Make our query postokay, _ = self._fetch(path, payload=payload) + if postokay: + # Cache our entry for fast access later + self._room_cache[room_id] = { + 'id': room_id, + 'home_server': home_server, + } + return room_id if postokay else None # Try to see if it's an alias then... diff --git a/test/test_matrix_plugin.py b/test/test_matrix_plugin.py index acc139aa..eadef1d7 100644 --- a/test/test_matrix_plugin.py +++ b/test/test_matrix_plugin.py @@ -28,7 +28,6 @@ import mock import requests from apprise import plugins from apprise import AppriseAsset -# from apprise import Apprise from json import dumps # Disable logging for a cleaner testing output @@ -86,6 +85,31 @@ def test_notify_matrix_plugin_general(mock_post, mock_get): # Registration Successful assert obj.send(body="test") is True + # Test sending other format types + kwargs = plugins.NotifyMatrix.parse_url( + 'matrix://user:passwd@hostname/#abcd?format=html') + obj = plugins.NotifyMatrix(**kwargs) + assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj, plugins.NotifyMatrix) is True + obj.send(body="test") is True + obj.send(title="title", body="test") is True + + kwargs = plugins.NotifyMatrix.parse_url( + 'matrix://user:passwd@hostname/#abcd/#abcd:localhost?format=markdown') + obj = plugins.NotifyMatrix(**kwargs) + assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj, plugins.NotifyMatrix) is True + obj.send(body="test") is True + obj.send(title="title", body="test") is True + + kwargs = plugins.NotifyMatrix.parse_url( + 'matrix://user:passwd@hostname/#abcd/!abcd:localhost?format=text') + obj = plugins.NotifyMatrix(**kwargs) + assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj, plugins.NotifyMatrix) is True + obj.send(body="test") is True + obj.send(title="title", body="test") is True + # Force a failed login ro = response_obj.copy() del ro['access_token'] @@ -392,16 +416,36 @@ def test_notify_matrix_plugin_rooms(mock_post, mock_get): assert obj._register() is True assert obj.access_token is not None + + assert obj._room_join('!abc123') == response_obj['room_id'] + # Use cache to get same results + assert len(obj._room_cache) == 1 assert obj._room_join('!abc123') == response_obj['room_id'] obj._room_cache = {} assert obj._room_join('!abc123:localhost') == response_obj['room_id'] + # Use cache to get same results + assert len(obj._room_cache) == 1 + assert obj._room_join('!abc123:localhost') == response_obj['room_id'] + obj._room_cache = {} assert obj._room_join('abc123') == response_obj['room_id'] + # Use cache to get same results + assert len(obj._room_cache) == 1 + assert obj._room_join('abc123') == response_obj['room_id'] + obj._room_cache = {} assert obj._room_join('abc123:localhost') == response_obj['room_id'] + # Use cache to get same results + assert len(obj._room_cache) == 1 + assert obj._room_join('abc123:localhost') == response_obj['room_id'] + obj._room_cache = {} assert obj._room_join('#abc123:localhost') == response_obj['room_id'] + # Use cache to get same results + assert len(obj._room_cache) == 1 + assert obj._room_join('#abc123:localhost') == response_obj['room_id'] + obj._room_cache = {} assert obj._room_join('%') is None assert obj._room_join(None) is None diff --git a/test/test_rest_plugins.py b/test/test_rest_plugins.py index d1b3229d..684d4ae0 100644 --- a/test/test_rest_plugins.py +++ b/test/test_rest_plugins.py @@ -1948,7 +1948,7 @@ TEST_URLS = ( .format('a' * 64), { # user and token specified; image set to True 'instance': plugins.NotifyMatrix}), - ('matrix://user@{}?mode=t2bot&format=markdown&image=False' + ('matrix://user@{}?mode=t2bot&format=html&image=False' .format('z' * 64), { # user and token specified; image set to True 'instance': plugins.NotifyMatrix}),