From cdd72086ee8fa38ef5cb49fceb86642a6b5b5973 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sat, 16 Feb 2019 00:26:33 -0500 Subject: [PATCH] Implimented overflow support (upstream, truncate, and split) --- apprise/Apprise.py | 38 +- apprise/__init__.py | 6 +- apprise/common.py | 28 ++ apprise/plugins/NotifyBase.py | 103 +++++- apprise/plugins/NotifyBoxcar.py | 3 +- apprise/plugins/NotifyDBus.py | 17 +- apprise/plugins/NotifyDiscord.py | 1 + apprise/plugins/NotifyEmail.py | 1 + apprise/plugins/NotifyEmby.py | 1 + apprise/plugins/NotifyFaast.py | 1 + apprise/plugins/NotifyGnome.py | 19 +- apprise/plugins/NotifyGrowl/NotifyGrowl.py | 1 + apprise/plugins/NotifyIFTTT.py | 1 + apprise/plugins/NotifyJSON.py | 1 + apprise/plugins/NotifyJoin.py | 16 +- apprise/plugins/NotifyMatrix.py | 1 + apprise/plugins/NotifyMatterMost.py | 1 + apprise/plugins/NotifyProwl.py | 1 + apprise/plugins/NotifyPushBullet.py | 1 + apprise/plugins/NotifyPushed.py | 1 + .../plugins/NotifyPushjet/NotifyPushjet.py | 1 + apprise/plugins/NotifyPushover.py | 1 + apprise/plugins/NotifyRocketChat.py | 1 + apprise/plugins/NotifyRyver.py | 1 + apprise/plugins/NotifySNS.py | 5 + apprise/plugins/NotifySlack.py | 1 + apprise/plugins/NotifyTelegram.py | 1 + apprise/plugins/NotifyWindows.py | 11 +- apprise/plugins/NotifyXBMC.py | 12 +- apprise/plugins/NotifyXML.py | 1 + test/test_rest_plugins.py | 341 ++++++++++++++++++ 31 files changed, 551 insertions(+), 67 deletions(-) diff --git a/apprise/Apprise.py b/apprise/Apprise.py index 9c9b81de..32367a00 100644 --- a/apprise/Apprise.py +++ b/apprise/Apprise.py @@ -363,26 +363,30 @@ class Apprise(object): # Store entry directly conversion_map[server.notify_format] = body - try: - # Send notification - if not server.notify( - title=title, - body=conversion_map[server.notify_format], - notify_type=notify_type): + # Apply our overflow (if defined) + for chunk in server._apply_overflow( + body=conversion_map[server.notify_format], title=title): + try: + # Send notification + if not server.notify( + title=chunk['title'], + body=chunk['body'], + notify_type=notify_type): - # Toggle our return status flag + # Toggle our return status flag + status = False + + except TypeError: + # These our our internally thrown notifications + # TODO: Change this to a custom one such as + # AppriseNotifyError status = False - except TypeError: - # These our our internally thrown notifications - # TODO: Change this to a custom one such as AppriseNotifyError - status = False - - except Exception: - # A catch all so we don't have to abort early - # just because one of our plugins has a bug in it. - logging.exception("Notification Exception") - status = False + except Exception: + # A catch all so we don't have to abort early + # just because one of our plugins has a bug in it. + logging.exception("Notification Exception") + status = False return status diff --git a/apprise/__init__.py b/apprise/__init__.py index cf9e034e..674f68e7 100644 --- a/apprise/__init__.py +++ b/apprise/__init__.py @@ -37,6 +37,8 @@ from .common import NotifyImageSize from .common import NOTIFY_IMAGE_SIZES from .common import NotifyFormat from .common import NOTIFY_FORMATS +from .common import OverflowMode +from .common import OVERFLOW_MODES from .plugins.NotifyBase import NotifyBase from .Apprise import Apprise @@ -52,6 +54,6 @@ __all__ = [ 'Apprise', 'AppriseAsset', 'NotifyBase', # Reference - 'NotifyType', 'NotifyImageSize', 'NotifyFormat', 'NOTIFY_TYPES', - 'NOTIFY_IMAGE_SIZES', 'NOTIFY_FORMATS', + 'NotifyType', 'NotifyImageSize', 'NotifyFormat', 'OverflowMode', + 'NOTIFY_TYPES', 'NOTIFY_IMAGE_SIZES', 'NOTIFY_FORMATS', 'OVERFLOW_MODES', ] diff --git a/apprise/common.py b/apprise/common.py index 834c1928..75fcd481 100644 --- a/apprise/common.py +++ b/apprise/common.py @@ -77,3 +77,31 @@ NOTIFY_FORMATS = ( NotifyFormat.HTML, NotifyFormat.MARKDOWN, ) + + +class OverflowMode(object): + """ + A list of pre-defined modes of how to handle the text when it exceeds the + defined maximum message size. + """ + + # Send the data as is; untouched. Let the upstream server decide how the + # content is handled. Some upstream services might gracefully handle this + # with expected intentions; others might not. + UPSTREAM = 'upstream' + + # Always truncate the text when it exceeds the maximum message size and + # send it anyway + TRUNCATE = 'truncate' + + # Split the message into multiple smaller messages that fit within the + # limits of what is expected. The smaller messages are sent + SPLIT = 'split' + + +# Define our modes so we can verify if we need to +OVERFLOW_MODES = ( + OverflowMode.UPSTREAM, + OverflowMode.TRUNCATE, + OverflowMode.SPLIT, +) diff --git a/apprise/plugins/NotifyBase.py b/apprise/plugins/NotifyBase.py index 9e05a908..adaf5cb9 100644 --- a/apprise/plugins/NotifyBase.py +++ b/apprise/plugins/NotifyBase.py @@ -45,6 +45,8 @@ from ..utils import is_hostname from ..common import NOTIFY_TYPES from ..common import NotifyFormat from ..common import NOTIFY_FORMATS +from ..common import OverflowMode +from ..common import OVERFLOW_MODES from ..AppriseAsset import AppriseAsset @@ -120,14 +122,26 @@ class NotifyBase(object): image_size = None # The maximum allowable characters allowed in the body per message - body_maxlen = 32768 + # We set it to what would virtually be an infinite value really + # 2^63 - 1 = 9223372036854775807 + body_maxlen = 9223372036854775807 - # Defines the maximum allowable characters in the title + # Defines the maximum allowable characters in the title; set this to zero + # if a title can't be used. Titles that are not used but are defined are + # automatically placed into the body title_maxlen = 250 + # Set the maximum line count; if this is set to anything larger then zero + # the message (prior to it being sent) will be truncated to this number + # of lines. Setting this to zero disables this feature. + body_max_line_count = 0 + # Default Notify Format notify_format = NotifyFormat.TEXT + # Default Overflow Mode + overflow_mode = OverflowMode.UPSTREAM + # Maintain a set of tags to associate with this specific notification tags = set() @@ -177,6 +191,19 @@ class NotifyBase(object): # Provide override self.notify_format = notify_format + if 'overflow' in kwargs: + # Store the specified format if specified + overflow_mode = kwargs.get('overflow', '') + if overflow_mode.lower() not in OVERFLOW_MODES: + self.logger.error( + 'Invalid overflow method %s' % overflow_mode, + ) + raise TypeError( + 'Invalid overflow method %s' % overflow_mode, + ) + # Provide override + self.overflow_mode = overflow_mode + if 'tag' in kwargs: # We want to associate some tags with our notification service. # the code below gets the 'tag' argument if defined, otherwise @@ -260,6 +287,78 @@ class NotifyBase(object): color_type=color_type, ) + def _apply_overflow(self, body, title=None): + """ + Takes the message body and title as input. This function then + applies any defined overflow restrictions associated with the + notification service and may alter the message if/as required. + + The function will always return a list object in the following + structure: + [ + { + title: 'the title goes here', + body: 'the message body goes here', + }, + { + title: 'the title goes here', + body: 'the message body goes here', + }, + + ] + """ + + response = list() + + if self.title_maxlen <= 0: + # Content is appended to body + body = '{}\r\n{}'.format(title, body) + title = '' + + # Enforce the line count first always + if self.body_max_line_count > 0: + # Limit results to just the first 2 line otherwise + # there is just to much content to display + body = re.split('\r*\n', body) + body = '\r\n'.join(body[0:self.body_max_line_count]) + + if self.overflow_mode == OverflowMode.UPSTREAM: + # Nothing to do + response.append({ + 'body': body, + 'title': title, + }) + return response + + elif len(title) > self.title_maxlen: + # Truncate our Title + title = title[:self.title_maxlen] + + if self.body_maxlen > 0 and len(body) <= self.body_maxlen: + response.append({ + 'body': body, + 'title': title, + }) + return response + + if self.overflow_mode == OverflowMode.TRUNCATE: + # Truncate our body and return + response.append({ + 'body': body[:self.body_maxlen], + 'title': title, + }) + # For truncate mode, we're done now + return response + + # If we reach here, then we are in SPLIT mode. + # For here, we want to split the message as many times as we have to + # in order to fit it within the designated limits. + response = [{ + 'body': body[i: i + self.body_maxlen], + 'title': title} for i in range(0, len(body), self.body_maxlen)] + + return response + def url(self): """ Assembles the URL associated with the notification based on the diff --git a/apprise/plugins/NotifyBoxcar.py b/apprise/plugins/NotifyBoxcar.py index dd55a3a3..636a571e 100644 --- a/apprise/plugins/NotifyBoxcar.py +++ b/apprise/plugins/NotifyBoxcar.py @@ -280,7 +280,8 @@ class NotifyBoxcar(NotifyBase): # Define any arguments set args = { - 'format': self.notify_format + 'format': self.notify_format, + 'overflow': self.overflow_mode, } return '{schema}://{access}/{secret}/{recipients}/?{args}'.format( diff --git a/apprise/plugins/NotifyDBus.py b/apprise/plugins/NotifyDBus.py index 8083a4a6..a466ecd0 100644 --- a/apprise/plugins/NotifyDBus.py +++ b/apprise/plugins/NotifyDBus.py @@ -148,6 +148,14 @@ class NotifyDBus(NotifyBase): # The number of seconds to keep the message present for message_timeout_ms = 13000 + # Limit results to just the first 10 line otherwise there is just to much + # content to display + body_max_line_count = 10 + + # A title can not be used for SMS Messages. Setting this to zero will + # cause any title (if defined) to get placed into the message body. + title_maxlen = 0 + # This entry is a bit hacky, but it allows us to unit-test this library # in an environment that simply doesn't have the gnome packages # available to us. It also allows us to handle situations where the @@ -249,15 +257,6 @@ class NotifyDBus(NotifyBase): "Could not load Gnome notification icon ({}): {}" .format(icon_path, e)) - # Limit results to just the first 10 line otherwise - # there is just to much content to display - body = re.split('[\r\n]+', body) - if title: - # Place title on first line if it exists - body.insert(0, title) - - body = '\r\n'.join(body[0:10]) - try: dbus_iface.Notify( # Application Identifier diff --git a/apprise/plugins/NotifyDiscord.py b/apprise/plugins/NotifyDiscord.py index 2ce78bd0..76e1ec8d 100644 --- a/apprise/plugins/NotifyDiscord.py +++ b/apprise/plugins/NotifyDiscord.py @@ -249,6 +249,7 @@ class NotifyDiscord(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'tts': 'yes' if self.tts else 'no', 'avatar': 'yes' if self.avatar else 'no', 'footer': 'yes' if self.footer else 'no', diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py index bdc29a2d..c7ba1c1d 100644 --- a/apprise/plugins/NotifyEmail.py +++ b/apprise/plugins/NotifyEmail.py @@ -429,6 +429,7 @@ class NotifyEmail(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'to': self.to_addr, 'from': self.from_addr, 'name': self.from_name, diff --git a/apprise/plugins/NotifyEmby.py b/apprise/plugins/NotifyEmby.py index 4a514ee5..41fd6f4f 100644 --- a/apprise/plugins/NotifyEmby.py +++ b/apprise/plugins/NotifyEmby.py @@ -543,6 +543,7 @@ class NotifyEmby(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'modal': 'yes' if self.modal else 'no', } diff --git a/apprise/plugins/NotifyFaast.py b/apprise/plugins/NotifyFaast.py index 5fffd724..2bb41818 100644 --- a/apprise/plugins/NotifyFaast.py +++ b/apprise/plugins/NotifyFaast.py @@ -132,6 +132,7 @@ class NotifyFaast(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } return '{schema}://{authtoken}/?{args}'.format( diff --git a/apprise/plugins/NotifyGnome.py b/apprise/plugins/NotifyGnome.py index a0f20fcc..a380cc42 100644 --- a/apprise/plugins/NotifyGnome.py +++ b/apprise/plugins/NotifyGnome.py @@ -26,8 +26,6 @@ from __future__ import absolute_import from __future__ import print_function -import re - from .NotifyBase import NotifyBase from ..common import NotifyImageSize @@ -86,6 +84,14 @@ class NotifyGnome(NotifyBase): # Allows the user to specify the NotifyImageSize object image_size = NotifyImageSize.XY_128 + # Limit results to just the first 10 line otherwise there is just to much + # content to display + body_max_line_count = 10 + + # A title can not be used for SMS Messages. Setting this to zero will + # cause any title (if defined) to get placed into the message body. + title_maxlen = 0 + # This entry is a bit hacky, but it allows us to unit-test this library # in an environment that simply doesn't have the gnome packages # available to us. It also allows us to handle situations where the @@ -119,15 +125,6 @@ class NotifyGnome(NotifyBase): "Gnome Notifications are not supported by this system.") return False - # Limit results to just the first 10 line otherwise - # there is just to much content to display - body = re.split('[\r\n]+', body) - if title: - # Place title on first line if it exists - body.insert(0, title) - - body = '\r\n'.join(body[0:10]) - try: # App initialization Notify.init(self.app_id) diff --git a/apprise/plugins/NotifyGrowl/NotifyGrowl.py b/apprise/plugins/NotifyGrowl/NotifyGrowl.py index b88cbb4d..09f4f672 100644 --- a/apprise/plugins/NotifyGrowl/NotifyGrowl.py +++ b/apprise/plugins/NotifyGrowl/NotifyGrowl.py @@ -223,6 +223,7 @@ class NotifyGrowl(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'priority': _map[GrowlPriority.NORMAL] if self.priority not in _map else _map[self.priority], diff --git a/apprise/plugins/NotifyIFTTT.py b/apprise/plugins/NotifyIFTTT.py index 2f0bad50..7505ebd0 100644 --- a/apprise/plugins/NotifyIFTTT.py +++ b/apprise/plugins/NotifyIFTTT.py @@ -214,6 +214,7 @@ class NotifyIFTTT(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } return '{schema}://{webhook_id}@{events}/?{args}'.format( diff --git a/apprise/plugins/NotifyJSON.py b/apprise/plugins/NotifyJSON.py index c2903c43..982fa294 100644 --- a/apprise/plugins/NotifyJSON.py +++ b/apprise/plugins/NotifyJSON.py @@ -78,6 +78,7 @@ class NotifyJSON(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } # Determine Authentication diff --git a/apprise/plugins/NotifyJoin.py b/apprise/plugins/NotifyJoin.py index ed74c430..901e3940 100644 --- a/apprise/plugins/NotifyJoin.py +++ b/apprise/plugins/NotifyJoin.py @@ -90,6 +90,10 @@ class NotifyJoin(NotifyBase): # Allows the user to specify the NotifyImageSize object image_size = NotifyImageSize.XY_72 + # Limit results to just the first 2 line otherwise there is just to much + # content to display + body_max_line_count = 2 + # The maximum allowable characters allowed in the body per message body_maxlen = 1000 @@ -131,17 +135,6 @@ class NotifyJoin(NotifyBase): Perform Join Notification """ - try: - # Limit results to just the first 2 line otherwise - # there is just to much content to display - body = re.split('[\r\n]+', body) - body[0] = body[0].strip('#').strip() - body = '\r\n'.join(body[0:2]) - - except (AttributeError, TypeError): - # body was None or not of a type string - body = '' - headers = { 'User-Agent': self.app_id, 'Content-Type': 'application/x-www-form-urlencoded', @@ -241,6 +234,7 @@ class NotifyJoin(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } return '{schema}://{apikey}/{devices}/?{args}'.format( diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py index 27422d1b..36673c24 100644 --- a/apprise/plugins/NotifyMatrix.py +++ b/apprise/plugins/NotifyMatrix.py @@ -244,6 +244,7 @@ class NotifyMatrix(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'mode': self.mode, } diff --git a/apprise/plugins/NotifyMatterMost.py b/apprise/plugins/NotifyMatterMost.py index 02ed020c..0d46cb0e 100644 --- a/apprise/plugins/NotifyMatterMost.py +++ b/apprise/plugins/NotifyMatterMost.py @@ -187,6 +187,7 @@ class NotifyMatterMost(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } default_port = 443 if self.secure else self.default_port diff --git a/apprise/plugins/NotifyProwl.py b/apprise/plugins/NotifyProwl.py index c501d370..58e7e8ad 100644 --- a/apprise/plugins/NotifyProwl.py +++ b/apprise/plugins/NotifyProwl.py @@ -206,6 +206,7 @@ class NotifyProwl(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'priority': 'normal' if self.priority not in _map else _map[self.priority] } diff --git a/apprise/plugins/NotifyPushBullet.py b/apprise/plugins/NotifyPushBullet.py index e772d2eb..fc5f4963 100644 --- a/apprise/plugins/NotifyPushBullet.py +++ b/apprise/plugins/NotifyPushBullet.py @@ -190,6 +190,7 @@ class NotifyPushBullet(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } recipients = '/'.join([self.quote(x) for x in self.recipients]) diff --git a/apprise/plugins/NotifyPushed.py b/apprise/plugins/NotifyPushed.py index df5a72fe..037a37c2 100644 --- a/apprise/plugins/NotifyPushed.py +++ b/apprise/plugins/NotifyPushed.py @@ -265,6 +265,7 @@ class NotifyPushed(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format( diff --git a/apprise/plugins/NotifyPushjet/NotifyPushjet.py b/apprise/plugins/NotifyPushjet/NotifyPushjet.py index f4bf6062..b5461eec 100644 --- a/apprise/plugins/NotifyPushjet/NotifyPushjet.py +++ b/apprise/plugins/NotifyPushjet/NotifyPushjet.py @@ -95,6 +95,7 @@ class NotifyPushjet(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } default_port = 443 if self.secure else 80 diff --git a/apprise/plugins/NotifyPushover.py b/apprise/plugins/NotifyPushover.py index 72232f7b..4f6999be 100644 --- a/apprise/plugins/NotifyPushover.py +++ b/apprise/plugins/NotifyPushover.py @@ -253,6 +253,7 @@ class NotifyPushover(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'priority': _map[PushoverPriority.NORMAL] if self.priority not in _map else _map[self.priority], diff --git a/apprise/plugins/NotifyRocketChat.py b/apprise/plugins/NotifyRocketChat.py index 899c8196..ce95b15a 100644 --- a/apprise/plugins/NotifyRocketChat.py +++ b/apprise/plugins/NotifyRocketChat.py @@ -148,6 +148,7 @@ class NotifyRocketChat(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } # Determine Authentication diff --git a/apprise/plugins/NotifyRyver.py b/apprise/plugins/NotifyRyver.py index 49b17412..a0552df5 100644 --- a/apprise/plugins/NotifyRyver.py +++ b/apprise/plugins/NotifyRyver.py @@ -229,6 +229,7 @@ class NotifyRyver(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, 'webhook': self.webhook, } diff --git a/apprise/plugins/NotifySNS.py b/apprise/plugins/NotifySNS.py index b29327f0..750d2d4a 100644 --- a/apprise/plugins/NotifySNS.py +++ b/apprise/plugins/NotifySNS.py @@ -90,6 +90,10 @@ class NotifySNS(NotifyBase): # Source: https://docs.aws.amazon.com/sns/latest/api/API_Publish.html body_maxlen = 140 + # A title can not be used for SMS Messages. Setting this to zero will + # cause any title (if defined) to get placed into the message body. + title_maxlen = 0 + def __init__(self, access_key_id, secret_access_key, region_name, recipients=None, **kwargs): """ @@ -530,6 +534,7 @@ class NotifySNS(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\ diff --git a/apprise/plugins/NotifySlack.py b/apprise/plugins/NotifySlack.py index 430a33ff..aec32156 100644 --- a/apprise/plugins/NotifySlack.py +++ b/apprise/plugins/NotifySlack.py @@ -304,6 +304,7 @@ class NotifySlack(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } # Determine if there is a botname present diff --git a/apprise/plugins/NotifyTelegram.py b/apprise/plugins/NotifyTelegram.py index 094665fe..697c0040 100644 --- a/apprise/plugins/NotifyTelegram.py +++ b/apprise/plugins/NotifyTelegram.py @@ -498,6 +498,7 @@ class NotifyTelegram(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } # No need to check the user token because the user automatically gets diff --git a/apprise/plugins/NotifyWindows.py b/apprise/plugins/NotifyWindows.py index 86fd6dc8..c1251cb1 100644 --- a/apprise/plugins/NotifyWindows.py +++ b/apprise/plugins/NotifyWindows.py @@ -26,7 +26,6 @@ from __future__ import absolute_import from __future__ import print_function -import re from time import sleep from .NotifyBase import NotifyBase @@ -67,6 +66,10 @@ class NotifyWindows(NotifyBase): # Allows the user to specify the NotifyImageSize object image_size = NotifyImageSize.XY_128 + # Limit results to just the first 2 line otherwise there is just to much + # content to display + body_max_line_count = 2 + # This entry is a bit hacky, but it allows us to unit-test this library # in an environment that simply doesn't have the windows packages # available to us. It also allows us to handle situations where the @@ -110,12 +113,6 @@ class NotifyWindows(NotifyBase): "Windows Notifications are not supported by this system.") return False - # Limit results to just the first 2 line otherwise - # there is just to much content to display - body = re.split('[\r\n]+', body) - body[0] = body[0].strip('#').strip() - body = '\r\n'.join(body[0:2]) - try: # Register destruction callback message_map = {win32con.WM_DESTROY: self._on_destroy, } diff --git a/apprise/plugins/NotifyXBMC.py b/apprise/plugins/NotifyXBMC.py index fc46645f..32f9257f 100644 --- a/apprise/plugins/NotifyXBMC.py +++ b/apprise/plugins/NotifyXBMC.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import re import requests from json import dumps @@ -58,6 +57,10 @@ class NotifyXBMC(NotifyBase): # A URL that takes you to the setup/help of the specific protocol setup_url = 'https://github.com/caronc/apprise/wiki/Notify_kodi' + # Limit results to just the first 2 line otherwise there is just to much + # content to display + body_max_line_count = 2 + # XBMC uses the http protocol with JSON requests xbmc_default_port = 8080 @@ -159,12 +162,6 @@ class NotifyXBMC(NotifyBase): Perform XBMC/KODI Notification """ - # Limit results to just the first 2 line otherwise - # there is just to much content to display - body = re.split('[\r\n]+', body) - body[0] = body[0].strip('#').strip() - body = '\r\n'.join(body[0:2]) - if self.protocol == self.xbmc_remote_protocol: # XBMC v2.0 (headers, payload) = self._payload_20( @@ -237,6 +234,7 @@ class NotifyXBMC(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } # Determine Authentication diff --git a/apprise/plugins/NotifyXML.py b/apprise/plugins/NotifyXML.py index cad538db..cfbe97ac 100644 --- a/apprise/plugins/NotifyXML.py +++ b/apprise/plugins/NotifyXML.py @@ -93,6 +93,7 @@ class NotifyXML(NotifyBase): # Define any arguments set args = { 'format': self.notify_format, + 'overflow': self.overflow_mode, } # Determine Authentication diff --git a/test/test_rest_plugins.py b/test/test_rest_plugins.py index f0f6d29a..8c8a056e 100644 --- a/test/test_rest_plugins.py +++ b/test/test_rest_plugins.py @@ -25,12 +25,18 @@ from apprise import plugins from apprise import NotifyType +from apprise import NotifyBase from apprise import Apprise from apprise import AppriseAsset from apprise.utils import compat_is_basestring from apprise.common import NotifyFormat +from apprise.common import OverflowMode from json import dumps +from random import choice +from string import ascii_uppercase as str_alpha +from string import digits as str_num + import requests import mock @@ -2883,3 +2889,338 @@ def test_notify_telegram_plugin(mock_post, mock_get): except TypeError: # Exception should be thrown about the fact no token was specified assert(True) + + +def test_notify_overflow_truncate(): + """ + API: Overflow Truncate Functionality Testing + + """ + # + # A little preparation + # + + # Number of characters per line + row = 24 + + # Some variables we use to control the data we work with + body_len = 1024 + title_len = 1024 + + # Create a large body and title with random data + body = ''.join(choice(str_alpha + str_num + ' ') for _ in range(body_len)) + body = '\r\n'.join([body[i: i + row] for i in range(0, len(body), row)]) + + # the new lines add a large amount to our body; lets force the content + # back to being 1024 characters. + body = body[0:1024] + + # Create our title using random data + title = ''.join(choice(str_alpha + str_num) for _ in range(title_len)) + + # + # First Test: Truncated Title + # + class TestNotification(NotifyBase): + + # Test title max length + title_maxlen = 10 + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + try: + # Load our object + obj = TestNotification(overflow='invalid') + + # We should have thrown an exception because our specified overflow + # is wrong. + assert False + + except TypeError: + # Expected to be here + assert True + + # Load our object + obj = TestNotification(overflow=OverflowMode.TRUNCATE) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + assert len(chunks) == 1 + assert body == chunks[0].get('body') + assert title[0:TestNotification.title_maxlen] == chunks[0].get('title') + + # + # Next Test: Line Count Control + # + + class TestNotification(NotifyBase): + + # Test title max length + title_maxlen = 5 + + # Maximum number of lines + body_max_line_count = 5 + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.TRUNCATE) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + assert len(chunks) == 1 + assert len(chunks[0].get('body').split('\n')) == \ + TestNotification.body_max_line_count + assert title[0:TestNotification.title_maxlen] == chunks[0].get('title') + + # + # Next Test: Truncated body + # + + class TestNotification(NotifyBase): + + # Test title max length + title_maxlen = title_len + + # Enforce a body length of just 10 + body_maxlen = 10 + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.TRUNCATE) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + assert len(chunks) == 1 + assert body[0:TestNotification.body_maxlen] == chunks[0].get('body') + assert title == chunks[0].get('title') + + # + # Next Test: Append title to body + Truncated body + # + + class TestNotification(NotifyBase): + + # Enforce no title + title_maxlen = 0 + + # Enforce a body length of just 100 + body_maxlen = 100 + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.TRUNCATE) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + assert len(chunks) == 1 + + # The below line should be read carefully... We're actually testing to see + # that our title is matched against our body. Behind the scenes, the title + # was appended to the body. The body was then truncated to the maxlen. + # The thing is, since the title is so large, all of the body was lost + # and a good chunk of the title was too. The message sent will just be a + # small portion of the title + assert len(chunks[0].get('body')) == TestNotification.body_maxlen + assert title[0:TestNotification.body_maxlen] == chunks[0].get('body') + + +def test_notify_overflow_split(): + """ + API: Overflow Split Functionality Testing + + """ + + # + # A little preparation + # + + # Number of characters per line + row = 24 + + # Some variables we use to control the data we work with + body_len = 1024 + title_len = 1024 + + # Create a large body and title with random data + body = ''.join(choice(str_alpha + str_num + ' ') for _ in range(body_len)) + body = '\r\n'.join([body[i: i + row] for i in range(0, len(body), row)]) + + # the new lines add a large amount to our body; lets force the content + # back to being 1024 characters. + body = body[0:1024] + + # Create our title using random data + title = ''.join(choice(str_alpha + str_num) for _ in range(title_len)) + + # + # First Test: Truncated Title + # + class TestNotification(NotifyBase): + + # Test title max length + title_maxlen = 10 + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.SPLIT) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + assert len(chunks) == 1 + assert body == chunks[0].get('body') + assert title[0:TestNotification.title_maxlen] == chunks[0].get('title') + + # + # Next Test: Line Count Control + # + + class TestNotification(NotifyBase): + + # Test title max length + title_maxlen = 5 + + # Maximum number of lines + body_max_line_count = 5 + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.SPLIT) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + assert len(chunks) == 1 + assert len(chunks[0].get('body').split('\n')) == \ + TestNotification.body_max_line_count + assert title[0:TestNotification.title_maxlen] == chunks[0].get('title') + + # + # Next Test: Split body + # + + class TestNotification(NotifyBase): + + # Test title max length + title_maxlen = title_len + + # Enforce a body length + body_maxlen = (body_len / 4) + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.SPLIT) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + offset = 0 + assert len(chunks) == 4 + for chunk in chunks: + # Our title never changes + assert title == chunk.get('title') + + # Our body is only broken up; not lost + _body = chunk.get('body') + assert body[offset: len(_body) + offset] == _body + offset += len(_body) + + # + # Next Test: Append title to body + split body + # + + class TestNotification(NotifyBase): + + # Enforce no title + title_maxlen = 0 + + # Enforce a body length + body_maxlen = (title_len / 4) + + def __init__(self, *args, **kwargs): + super(TestNotification, self).__init__(**kwargs) + + def notify(self, *args, **kwargs): + # Pretend everything is okay + return True + + # Load our object + obj = TestNotification(overflow=OverflowMode.SPLIT) + assert obj is not None + + # Verify that we break the title to a max length of our title_max + # and that the body remains untouched + chunks = obj._apply_overflow(body=body, title=title) + + # Our final product is that our title has been appended to our body to + # create one great big body. As a result we'll get quite a few lines back + # now. + offset = 0 + + # Our body will look like this in small chunks at the end of the day + bulk = title + '\r\n' + body + + # Due to the new line added to the end + assert len(chunks) == ( + (len(bulk) / TestNotification.body_maxlen) + + (1 if len(bulk) % TestNotification.body_maxlen else 0)) + + for chunk in chunks: + # Our title is empty every time + assert chunk.get('title') == '' + + _body = chunk.get('body') + assert bulk[offset:len(_body)+offset] == _body + offset += len(_body)