diff options
Diffstat (limited to 'libs/apprise/Apprise.py')
-rw-r--r-- | libs/apprise/Apprise.py | 212 |
1 files changed, 112 insertions, 100 deletions
diff --git a/libs/apprise/Apprise.py b/libs/apprise/Apprise.py index 8930b2a77..30c936532 100644 --- a/libs/apprise/Apprise.py +++ b/libs/apprise/Apprise.py @@ -23,14 +23,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import re import os import six -from markdown import markdown from itertools import chain from .common import NotifyType -from .common import NotifyFormat from .common import MATCH_ALL_TAG +from .common import MATCH_ALWAYS_TAG +from .conversion import convert_between from .utils import is_exclusive_match from .utils import parse_list from .utils import parse_urls @@ -44,6 +43,7 @@ from .AppriseLocale import AppriseLocale from .config.ConfigBase import ConfigBase from .plugins.NotifyBase import NotifyBase + from . import plugins from . import __version__ @@ -305,7 +305,7 @@ class Apprise(object): """ self.servers[:] = [] - def find(self, tag=MATCH_ALL_TAG): + def find(self, tag=MATCH_ALL_TAG, match_always=True): """ Returns an list of all servers matching against the tag specified. @@ -321,6 +321,10 @@ class Apprise(object): # tag=[('tagA', 'tagC'), 'tagB'] = (tagA and tagC) or tagB # tag=[('tagB', 'tagC')] = tagB and tagC + # A match_always flag allows us to pick up on our 'any' keyword + # and notify these services under all circumstances + match_always = MATCH_ALWAYS_TAG if match_always else None + # Iterate over our loaded plugins for entry in self.servers: @@ -334,13 +338,14 @@ class Apprise(object): for server in servers: # Apply our tag matching based on our defined logic if is_exclusive_match( - logic=tag, data=server.tags, match_all=MATCH_ALL_TAG): + logic=tag, data=server.tags, match_all=MATCH_ALL_TAG, + match_always=match_always): yield server return def notify(self, body, title='', notify_type=NotifyType.INFO, - body_format=None, tag=MATCH_ALL_TAG, attach=None, - interpret_escapes=None): + body_format=None, tag=MATCH_ALL_TAG, match_always=True, + attach=None, interpret_escapes=None): """ Send a notification to all of the plugins previously loaded. @@ -370,7 +375,7 @@ class Apprise(object): self.async_notify( body, title, notify_type=notify_type, body_format=body_format, - tag=tag, attach=attach, + tag=tag, match_always=match_always, attach=attach, interpret_escapes=interpret_escapes, ), debug=self.debug @@ -468,8 +473,8 @@ class Apprise(object): return py3compat.asyncio.toasyncwrap(status) def _notifyall(self, handler, body, title='', notify_type=NotifyType.INFO, - body_format=None, tag=MATCH_ALL_TAG, attach=None, - interpret_escapes=None): + body_format=None, tag=MATCH_ALL_TAG, match_always=True, + attach=None, interpret_escapes=None): """ Creates notifications for all of the plugins loaded. @@ -480,22 +485,43 @@ class Apprise(object): if len(self) == 0: # Nothing to notify - raise TypeError("No service(s) to notify") + msg = "There are service(s) to notify" + logger.error(msg) + raise TypeError(msg) if not (title or body): - raise TypeError("No message content specified to deliver") - - if six.PY2: - # Python 2.7.x Unicode Character Handling - # Ensure we're working with utf-8 - if isinstance(title, unicode): # noqa: F821 - title = title.encode('utf-8') + msg = "No message content specified to deliver" + logger.error(msg) + raise TypeError(msg) - if isinstance(body, unicode): # noqa: F821 - body = body.encode('utf-8') + try: + if six.PY2: + # Python 2.7 encoding support isn't the greatest, so we try + # to ensure that we're ALWAYS dealing with unicode characters + # prior to entrying the next part. This is especially required + # for Markdown support + if title and isinstance(title, str): # noqa: F821 + title = title.decode(self.asset.encoding) + + if body and isinstance(body, str): # noqa: F821 + body = body.decode(self.asset.encoding) + + else: # Python 3+ + if title and isinstance(title, bytes): # noqa: F821 + title = title.decode(self.asset.encoding) + + if body and isinstance(body, bytes): # noqa: F821 + body = body.decode(self.asset.encoding) + + except UnicodeDecodeError: + msg = 'The content passed into Apprise was not of encoding ' \ + 'type: {}'.format(self.asset.encoding) + logger.error(msg) + raise TypeError(msg) # Tracks conversions - conversion_map = dict() + conversion_body_map = dict() + conversion_title_map = dict() # Prepare attachments if required if attach is not None and not isinstance(attach, AppriseAttachment): @@ -511,86 +537,45 @@ class Apprise(object): if interpret_escapes is None else interpret_escapes # Iterate over our loaded plugins - for server in self.find(tag): + for server in self.find(tag, match_always=match_always): # If our code reaches here, we either did not define a tag (it # was set to None), or we did define a tag and the logic above # determined we need to notify the service it's associated with - if server.notify_format not in conversion_map: - if body_format == NotifyFormat.MARKDOWN and \ - server.notify_format == NotifyFormat.HTML: - - # Apply Markdown - conversion_map[server.notify_format] = markdown(body) - - elif body_format == NotifyFormat.TEXT and \ - server.notify_format == NotifyFormat.HTML: - - # Basic TEXT to HTML format map; supports keys only - re_map = { - # Support Ampersand - r'&': '&', - - # Spaces to for formatting purposes since - # multiple spaces are treated as one an this may - # not be the callers intention - r' ': ' ', - - # Tab support - r'\t': ' ', - - # Greater than and Less than Characters - r'>': '>', - r'<': '<', - } - - # Compile our map - re_table = re.compile( - r'(' + '|'.join( - map(re.escape, re_map.keys())) + r')', - re.IGNORECASE, - ) - - # Execute our map against our body in addition to - # swapping out new lines and replacing them with <br/> - conversion_map[server.notify_format] = \ - re.sub(r'\r*\n', '<br/>\r\n', - re_table.sub( - lambda x: re_map[x.group()], body)) + if server.notify_format not in conversion_body_map: + # Perform Conversion + conversion_body_map[server.notify_format] = \ + convert_between( + body_format, server.notify_format, content=body) + + # Prepare our title + conversion_title_map[server.notify_format] = \ + '' if not title else title + + # Tidy Title IF required (hence it will become part of the + # body) + if server.title_maxlen <= 0 and \ + conversion_title_map[server.notify_format]: + + conversion_title_map[server.notify_format] = \ + convert_between( + body_format, server.notify_format, + content=conversion_title_map[server.notify_format]) + + if interpret_escapes: + # + # Escape our content + # - else: - # Store entry directly - conversion_map[server.notify_format] = body - - if interpret_escapes: - # - # Escape our content - # - - try: - # Added overhead required due to Python 3 Encoding Bug - # identified here: https://bugs.python.org/issue21331 - conversion_map[server.notify_format] = \ - conversion_map[server.notify_format]\ - .encode('ascii', 'backslashreplace')\ - .decode('unicode-escape') - - except UnicodeDecodeError: # pragma: no cover - # This occurs using a very old verion of Python 2.7 such - # as the one that ships with CentOS/RedHat 7.x (v2.7.5). - conversion_map[server.notify_format] = \ - conversion_map[server.notify_format] \ - .decode('string_escape') - - except AttributeError: - # Must be of string type - logger.error('Failed to escape message body') - raise TypeError - - if title: try: # Added overhead required due to Python 3 Encoding Bug # identified here: https://bugs.python.org/issue21331 - title = title\ + conversion_body_map[server.notify_format] = \ + conversion_body_map[server.notify_format]\ + .encode('ascii', 'backslashreplace')\ + .decode('unicode-escape') + + conversion_title_map[server.notify_format] = \ + conversion_title_map[server.notify_format]\ .encode('ascii', 'backslashreplace')\ .decode('unicode-escape') @@ -598,19 +583,46 @@ class Apprise(object): # This occurs using a very old verion of Python 2.7 # such as the one that ships with CentOS/RedHat 7.x # (v2.7.5). - title = title.decode('string_escape') + conversion_body_map[server.notify_format] = \ + conversion_body_map[server.notify_format] \ + .decode('string_escape') + + conversion_title_map[server.notify_format] = \ + conversion_title_map[server.notify_format] \ + .decode('string_escape') except AttributeError: # Must be of string type - logger.error('Failed to escape message title') - raise TypeError + msg = 'Failed to escape message body' + logger.error(msg) + raise TypeError(msg) + + if six.PY2: + # Python 2.7 strings must be encoded as utf-8 for + # consistency across all platforms + if conversion_body_map[server.notify_format] and \ + isinstance( + conversion_body_map[server.notify_format], + unicode): # noqa: F821 + conversion_body_map[server.notify_format] = \ + conversion_body_map[server.notify_format]\ + .encode('utf-8') + + if conversion_title_map[server.notify_format] and \ + isinstance( + conversion_title_map[server.notify_format], + unicode): # noqa: F821 + conversion_title_map[server.notify_format] = \ + conversion_title_map[server.notify_format]\ + .encode('utf-8') yield handler( server, - body=conversion_map[server.notify_format], - title=title, + body=conversion_body_map[server.notify_format], + title=conversion_title_map[server.notify_format], notify_type=notify_type, - attach=attach + attach=attach, + body_format=body_format, ) def details(self, lang=None, show_requirements=False, show_disabled=False): |