Skip to content

Commit

Permalink
Added support for interpretable escapes via CLI (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc authored Jan 31, 2021
1 parent c7f015b commit fec6de1
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 13 deletions.
40 changes: 39 additions & 1 deletion apprise/Apprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ def find(self, tag=MATCH_ALL_TAG):
return

def notify(self, body, title='', notify_type=NotifyType.INFO,
body_format=None, tag=MATCH_ALL_TAG, attach=None):
body_format=None, tag=MATCH_ALL_TAG, attach=None,
interpret_escapes=None):
"""
Send a notification to all of the plugins previously loaded.
Expand All @@ -313,6 +314,9 @@ def notify(self, body, title='', notify_type=NotifyType.INFO,
Attach can contain a list of attachment URLs. attach can also be
represented by a an AttachBase() (or list of) object(s). This
identifies the products you wish to notify
Set interpret_escapes to True if you want to pre-escape a string
such as turning a \n into an actual new line, etc.
"""

if len(self) == 0:
Expand Down Expand Up @@ -343,6 +347,10 @@ def notify(self, body, title='', notify_type=NotifyType.INFO,
body_format = self.asset.body_format \
if body_format is None else body_format

# Allow Asset default value
interpret_escapes = self.asset.interpret_escapes \
if interpret_escapes is None else interpret_escapes

# for asyncio support; we track a list of our servers to notify
coroutines = []

Expand Down Expand Up @@ -403,6 +411,36 @@ def notify(self, body, title='', notify_type=NotifyType.INFO,
# Store entry directly
conversion_map[server.notify_format] = body

if interpret_escapes:
#
# Escape our content
#

try:
# Added overhead requrired due to Python 3 Encoding Bug
# idenified here: https://bugs.python.org/issue21331
conversion_map[server.notify_format] = \
conversion_map[server.notify_format]\
.encode('ascii', 'backslashreplace')\
.decode('unicode-escape')

except AttributeError:
# Must be of string type
logger.error('Failed to escape message body')
return False

try:
# Added overhead requrired due to Python 3 Encoding Bug
# idenified here: https://bugs.python.org/issue21331
title = title\
.encode('ascii', 'backslashreplace')\
.decode('unicode-escape')

except AttributeError:
# Must be of string type
logger.error('Failed to escape message title')
return False

if ASYNCIO_SUPPORT and server.asset.async_mode:
# Build a list of servers requiring notification
# that will be triggered asynchronously afterwards
Expand Down
5 changes: 5 additions & 0 deletions apprise/AppriseAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ class AppriseAsset(object):
# notifications are sent sequentially (one after another)
async_mode = True

# Whether or not to interpret escapes found within the input text prior
# to passing it upstream. Such as converting \t to an actual tab and \n
# to a new line.
interpret_escapes = False

def __init__(self, **kwargs):
"""
Asset Initialization
Expand Down
7 changes: 6 additions & 1 deletion apprise/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,16 @@ def print_version_msg():
@click.option('--verbose', '-v', count=True,
help='Makes the operation more talkative. Use multiple v to '
'increase the verbosity. I.e.: -vvvv')
@click.option('--interpret-escapes', '-e', is_flag=True,
help='Enable interpretation of backslash escapes')
@click.option('--debug', '-D', is_flag=True, help='Debug mode')
@click.option('--version', '-V', is_flag=True,
help='Display the apprise version and exit.')
@click.argument('urls', nargs=-1,
metavar='SERVER_URL [SERVER_URL2 [SERVER_URL3]]',)
def main(body, title, config, attach, urls, notification_type, theme, tag,
input_format, dry_run, recursion_depth, verbose, disable_async,
debug, version):
interpret_escapes, debug, version):
"""
Send a notification to all of the specified servers identified by their
URLs the content provided within the title, body and notification-type.
Expand Down Expand Up @@ -230,6 +232,9 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
# Our body format
body_format=input_format,

# Interpret Escapes
interpret_escapes=interpret_escapes,

# Set the theme
theme=theme,

Expand Down
4 changes: 3 additions & 1 deletion apprise/config/ConfigBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,9 @@ def config_parse_yaml(content, asset=None):
# Load our data (safely)
result = yaml.load(content, Loader=yaml.SafeLoader)

except (AttributeError, yaml.error.MarkedYAMLError) as e:
except (AttributeError,
yaml.parser.ParserError,
yaml.error.MarkedYAMLError) as e:
# Invalid content
ConfigBase.logger.error(
'Invalid Apprise YAML data specified.')
Expand Down
31 changes: 21 additions & 10 deletions test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ def parse_url(url, *args, **kwargs):
assert a.add('bad://localhost') is False
assert len(a) == 0

# We'll fail because we've got nothing to notify
assert a.notify(
title="my title", body="my body") is False

# Clear our server listings again
a.clear()

assert a.add('good://localhost') is True
assert len(a) == 1

Expand Down Expand Up @@ -252,9 +259,6 @@ def parse_url(url, *args, **kwargs):
body='body', title='test', notify_type=NotifyType.INFO,
attach='invalid://') is False

# Clear our server listings again
a.clear()

class ThrowNotification(NotifyBase):
def notify(self, **kwargs):
# Pretend everything is okay
Expand Down Expand Up @@ -292,14 +296,21 @@ def url(self):
# Store our good notification in our schema map
SCHEMA_MAP['runtime'] = RuntimeNotification

assert a.add('runtime://localhost') is True
assert a.add('throw://localhost') is True
assert a.add('fail://localhost') is True
assert len(a) == 3
for async_mode in (True, False):
# Create an Asset object
asset = AppriseAsset(theme='default', async_mode=async_mode)

# We can load the device using our asset
a = Apprise(asset=asset)

assert a.add('runtime://localhost') is True
assert a.add('throw://localhost') is True
assert a.add('fail://localhost') is True
assert len(a) == 3

# Test when our notify both throws an exception and or just
# simply returns False
assert a.notify(title="present", body="present") is False
# Test when our notify both throws an exception and or just
# simply returns False
assert a.notify(title="present", body="present") is False

# Create a Notification that throws an unexected exception
class ThrowInstantiateNotification(NotifyBase):
Expand Down
43 changes: 43 additions & 0 deletions test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from __future__ import print_function
import re
import mock
import requests
import json
from os.path import dirname
from os.path import join
from apprise import cli
Expand Down Expand Up @@ -112,6 +114,47 @@ def url(self, *args, **kwargs):
])
assert result.exit_code == 0

with mock.patch('requests.post') as mock_post:
# Prepare Mock
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok

result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body\\nsNewLine',
# Test using interpret escapes
'-e',
# Use our JSON query
'json://localhost',
])
assert result.exit_code == 0

# Test our call count
assert mock_post.call_count == 1

# Our string is now escaped correctly
json.loads(mock_post.call_args_list[0][1]['data'])\
.get('message', '') == 'test body\nsNewLine'

# Reset
mock_post.reset_mock()

result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body\\nsNewLine',
# No -e switch at all (so we don't escape the above)
# Use our JSON query
'json://localhost',
])
assert result.exit_code == 0

# Test our call count
assert mock_post.call_count == 1

# Our string is now escaped correctly
json.loads(mock_post.call_args_list[0][1]['data'])\
.get('message', '') == 'test body\\nsNewLine'

# Run in synchronous mode
result = runner.invoke(cli.main, [
'-t', 'test title',
Expand Down
Loading

0 comments on commit fec6de1

Please sign in to comment.