diff --git a/apprise/plugins/NotifyJSON.py b/apprise/plugins/NotifyJSON.py
index bdd5362b..c14bbd36 100644
--- a/apprise/plugins/NotifyJSON.py
+++ b/apprise/plugins/NotifyJSON.py
@@ -122,9 +122,13 @@ class NotifyJSON(NotifyBase):
'name': _('HTTP Header'),
'prefix': '+',
},
+ 'payload': {
+ 'name': _('Payload Extras'),
+ 'prefix': ':',
+ },
}
- def __init__(self, headers=None, method=None, **kwargs):
+ def __init__(self, headers=None, method=None, payload=None, **kwargs):
"""
Initialize JSON Object
@@ -151,6 +155,11 @@ class NotifyJSON(NotifyBase):
# Store our extra headers
self.headers.update(headers)
+ self.payload_extras = {}
+ if payload:
+ # Store our extra payload entries
+ self.payload_extras.update(payload)
+
return
def url(self, privacy=False, *args, **kwargs):
@@ -169,6 +178,10 @@ class NotifyJSON(NotifyBase):
# Append our headers into our parameters
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
+ # Append our payload extra's into our parameters
+ params.update(
+ {':{}'.format(k): v for k, v in self.payload_extras.items()})
+
# Determine Authentication
auth = ''
if self.user and self.password:
@@ -252,6 +265,9 @@ class NotifyJSON(NotifyBase):
'type': notify_type,
}
+ # Apply any/all payload over-rides defined
+ payload.update(self.payload_extras)
+
auth = None
if self.user:
auth = (self.user, self.password)
@@ -340,6 +356,10 @@ class NotifyJSON(NotifyBase):
# We're done early as we couldn't load the results
return results
+ # store any additional payload extra's defined
+ results['payload'] = {NotifyJSON.unquote(x): NotifyJSON.unquote(y)
+ for x, y in results['qsd:'].items()}
+
# Add our headers that the user can potentially over-ride if they wish
# to to our returned result set
results['headers'] = results['qsd+']
diff --git a/apprise/plugins/NotifyXML.py b/apprise/plugins/NotifyXML.py
index 2d36d110..1f73f898 100644
--- a/apprise/plugins/NotifyXML.py
+++ b/apprise/plugins/NotifyXML.py
@@ -127,9 +127,13 @@ class NotifyXML(NotifyBase):
'name': _('HTTP Header'),
'prefix': '+',
},
+ 'payload': {
+ 'name': _('Payload Extras'),
+ 'prefix': ':',
+ },
}
- def __init__(self, headers=None, method=None, **kwargs):
+ def __init__(self, headers=None, method=None, payload=None, **kwargs):
"""
Initialize XML Object
@@ -145,12 +149,9 @@ class NotifyXML(NotifyBase):
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-
- {XSD_VER}
- {SUBJECT}
- {MESSAGE_TYPE}
- {MESSAGE}
- {ATTACHMENTS}
+
+ {{CORE}}
+ {{ATTACHMENTS}}
"""
@@ -172,6 +173,19 @@ class NotifyXML(NotifyBase):
# Store our extra headers
self.headers.update(headers)
+ self.payload_extras = {}
+ if payload:
+ # Store our extra payload entries (but tidy them up since they will
+ # become XML Keys (they can't contain certain characters
+ for k, v in payload.items():
+ key = re.sub(r'[^A-Za-z0-9_-]*', '', k)
+ if not key:
+ self.logger.warning(
+ 'Ignoring invalid XML Stanza element name({})'
+ .format(k))
+ continue
+ self.payload_extras[key] = v
+
return
def url(self, privacy=False, *args, **kwargs):
@@ -190,6 +204,10 @@ class NotifyXML(NotifyBase):
# Append our headers into our parameters
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
+ # Append our payload extra's into our parameters
+ params.update(
+ {':{}'.format(k): v for k, v in self.payload_extras.items()})
+
# Determine Authentication
auth = ''
if self.user and self.password:
@@ -235,7 +253,24 @@ class NotifyXML(NotifyBase):
# Our XML Attachmement subsitution
xml_attachments = ''
- # Track our potential attachments
+ # Our Payload Base
+ payload_base = {
+ 'Version': self.xsd_ver,
+ 'Subject': NotifyXML.escape_html(title, whitespace=False),
+ 'MessageType': NotifyXML.escape_html(
+ notify_type, whitespace=False),
+ 'Message': NotifyXML.escape_html(body, whitespace=False),
+ }
+
+ # Apply our payload extras
+ payload_base.update(
+ {k: NotifyXML.escape_html(v, whitespace=False)
+ for k, v in self.payload_extras.items()})
+
+ # Base Entres
+ xml_base = ''.join(
+ ['<{}>{}{}>'.format(k, v, k) for k, v in payload_base.items()])
+
attachments = []
if attach:
for attachment in attach:
@@ -274,13 +309,9 @@ class NotifyXML(NotifyBase):
''.join(attachments) + ''
re_map = {
- '{XSD_VER}': self.xsd_ver,
- '{XSD_URL}': self.xsd_url.format(version=self.xsd_ver),
- '{MESSAGE_TYPE}': NotifyXML.escape_html(
- notify_type, whitespace=False),
- '{SUBJECT}': NotifyXML.escape_html(title, whitespace=False),
- '{MESSAGE}': NotifyXML.escape_html(body, whitespace=False),
- '{ATTACHMENTS}': xml_attachments,
+ '{{XSD_URL}}': self.xsd_url.format(version=self.xsd_ver),
+ '{{ATTACHMENTS}}': xml_attachments,
+ '{{CORE}}': xml_base,
}
# Iterate over above list and store content accordingly
@@ -379,6 +410,10 @@ class NotifyXML(NotifyBase):
# We're done early as we couldn't load the results
return results
+ # store any additional payload extra's defined
+ results['payload'] = {NotifyXML.unquote(x): NotifyXML.unquote(y)
+ for x, y in results['qsd:'].items()}
+
# Add our headers that the user can potentially over-ride if they wish
# to to our returned result set
results['headers'] = results['qsd+']
diff --git a/test/test_custom_xml_plugin.py b/test/test_custom_xml_plugin.py
deleted file mode 100644
index b0b9c582..00000000
--- a/test/test_custom_xml_plugin.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2021 Chris Caron
-# All rights reserved.
-#
-# This code is licensed under the MIT License.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files(the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions :
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-import os
-import sys
-import mock
-import requests
-from apprise import plugins
-from apprise import Apprise
-from apprise import AppriseAttachment
-from apprise import NotifyType
-
-# Disable logging for a cleaner testing output
-import logging
-logging.disable(logging.CRITICAL)
-
-# Attachment Directory
-TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
-
-
-@mock.patch('requests.post')
-def test_notify_xml_plugin_attachments(mock_post):
- """
- NotifyXML() Attachments
-
- """
- # Disable Throttling to speed testing
- plugins.NotifyBase.request_rate_per_sec = 0
-
- okay_response = requests.Request()
- okay_response.status_code = requests.codes.ok
- okay_response.content = ""
-
- # Assign our mock object our return value
- mock_post.return_value = okay_response
-
- obj = Apprise.instantiate('xml://localhost.localdomain/')
- assert isinstance(obj, plugins.NotifyXML)
-
- # Test Valid Attachment
- path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
- attach = AppriseAttachment(path)
- assert obj.notify(
- body='body', title='title', notify_type=NotifyType.INFO,
- attach=attach) is True
-
- # Test invalid attachment
- path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
- assert obj.notify(
- body='body', title='title', notify_type=NotifyType.INFO,
- attach=path) is False
-
- # Get a appropriate "builtin" module name for pythons 2/3.
- if sys.version_info.major >= 3:
- builtin_open_function = 'builtins.open'
-
- else:
- builtin_open_function = '__builtin__.open'
-
- # Test Valid Attachment (load 3)
- path = (
- os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
- os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
- os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
- )
- attach = AppriseAttachment(path)
-
- # Return our good configuration
- mock_post.side_effect = None
- mock_post.return_value = okay_response
- with mock.patch(builtin_open_function, side_effect=OSError()):
- # We can't send the message we can't open the attachment for reading
- assert obj.notify(
- body='body', title='title', notify_type=NotifyType.INFO,
- attach=attach) is False
-
- # test the handling of our batch modes
- obj = Apprise.instantiate('xml://no-reply@example.com/')
- assert isinstance(obj, plugins.NotifyXML)
-
- # Now send an attachment normally without issues
- mock_post.reset_mock()
- assert obj.notify(
- body='body', title='title', notify_type=NotifyType.INFO,
- attach=attach) is True
- assert mock_post.call_count == 1
diff --git a/test/test_plugin_custom_json.py b/test/test_plugin_custom_json.py
index 7d2569f9..4fedfc47 100644
--- a/test/test_plugin_custom_json.py
+++ b/test/test_plugin_custom_json.py
@@ -182,7 +182,8 @@ def test_plugin_custom_json_edge_cases(mock_get, mock_post):
dataset = json.loads(details[1]['data'])
assert dataset['title'] == 'title'
assert 'message' in dataset
- assert dataset['message'] == 'body'
+ # message over-ride was provided
+ assert dataset['message'] == 'test'
assert instance.url(privacy=False).startswith(
'json://localhost:8080/command?')
diff --git a/test/test_plugin_custom_xml.py b/test/test_plugin_custom_xml.py
index 644fa0f5..db449e67 100644
--- a/test/test_plugin_custom_xml.py
+++ b/test/test_plugin_custom_xml.py
@@ -22,15 +22,25 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+import os
+import sys
+import re
import mock
import requests
from apprise import plugins
+from apprise import Apprise
+from apprise import AppriseAttachment
+from apprise import NotifyType
from helpers import AppriseURLTester
# Disable logging for a cleaner testing output
import logging
logging.disable(logging.CRITICAL)
+# Attachment Directory
+TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
+
+
# Our Testing URLs
apprise_url_tests = (
('xml://:@/', {
@@ -149,6 +159,74 @@ def test_plugin_custom_xml_urls():
AppriseURLTester(tests=apprise_url_tests).run_all()
+@mock.patch('requests.post')
+def test_notify_xml_plugin_attachments(mock_post):
+ """
+ NotifyXML() Attachments
+
+ """
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.request_rate_per_sec = 0
+
+ okay_response = requests.Request()
+ okay_response.status_code = requests.codes.ok
+ okay_response.content = ""
+
+ # Assign our mock object our return value
+ mock_post.return_value = okay_response
+
+ obj = Apprise.instantiate('xml://localhost.localdomain/')
+ assert isinstance(obj, plugins.NotifyXML)
+
+ # Test Valid Attachment
+ path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
+ attach = AppriseAttachment(path)
+ assert obj.notify(
+ body='body', title='title', notify_type=NotifyType.INFO,
+ attach=attach) is True
+
+ # Test invalid attachment
+ path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
+ assert obj.notify(
+ body='body', title='title', notify_type=NotifyType.INFO,
+ attach=path) is False
+
+ # Get a appropriate "builtin" module name for pythons 2/3.
+ if sys.version_info.major >= 3:
+ builtin_open_function = 'builtins.open'
+
+ else:
+ builtin_open_function = '__builtin__.open'
+
+ # Test Valid Attachment (load 3)
+ path = (
+ os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
+ os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
+ os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
+ )
+ attach = AppriseAttachment(path)
+
+ # Return our good configuration
+ mock_post.side_effect = None
+ mock_post.return_value = okay_response
+ with mock.patch(builtin_open_function, side_effect=OSError()):
+ # We can't send the message we can't open the attachment for reading
+ assert obj.notify(
+ body='body', title='title', notify_type=NotifyType.INFO,
+ attach=attach) is False
+
+ # test the handling of our batch modes
+ obj = Apprise.instantiate('xml://no-reply@example.com/')
+ assert isinstance(obj, plugins.NotifyXML)
+
+ # Now send an attachment normally without issues
+ mock_post.reset_mock()
+ assert obj.notify(
+ body='body', title='title', notify_type=NotifyType.INFO,
+ attach=attach) is True
+ assert mock_post.call_count == 1
+
+
@mock.patch('requests.post')
@mock.patch('requests.get')
def test_plugin_custom_xml_edge_cases(mock_get, mock_post):
@@ -168,7 +246,8 @@ def test_plugin_custom_xml_edge_cases(mock_get, mock_post):
mock_get.return_value = response
results = plugins.NotifyXML.parse_url(
- 'xml://localhost:8080/command?:message=test&method=GET')
+ 'xml://localhost:8080/command?:Message=test&method=GET'
+ '&:Key=value&:,=invalid')
assert isinstance(results, dict)
assert results['user'] is None
@@ -181,7 +260,9 @@ def test_plugin_custom_xml_edge_cases(mock_get, mock_post):
assert results['schema'] == 'xml'
assert results['url'] == 'xml://localhost:8080/command'
assert isinstance(results['qsd:'], dict) is True
- assert results['qsd:']['message'] == 'test'
+ assert results['qsd:']['Message'] == 'test'
+ assert results['qsd:']['Key'] == 'value'
+ assert results['qsd:'][','] == 'invalid'
instance = plugins.NotifyXML(**results)
assert isinstance(instance, plugins.NotifyXML)
@@ -201,3 +282,12 @@ def test_plugin_custom_xml_edge_cases(mock_get, mock_post):
for k in ('user', 'password', 'port', 'host', 'fullpath', 'path', 'query',
'schema', 'url', 'method'):
assert new_results[k] == results[k]
+
+ # Test our data set for our key/value pair
+ assert re.search('[1-9]+\.[0-9]+', details[1]['data'])
+ assert re.search('info', details[1]['data'])
+ assert re.search('title', details[1]['data'])
+ # Custom entry Message acts as Over-ride and kicks in here
+ assert re.search('test', details[1]['data'])
+ # Custom entry
+ assert re.search('value', details[1]['data'])