Skip to content

Commit

Permalink
Improve split and truncate overflow methods (#1035)
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc committed Dec 29, 2023
1 parent bb4acd0 commit f3c699a
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 7 deletions.
41 changes: 37 additions & 4 deletions apprise/plugins/NotifyBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,25 +494,58 @@ def _apply_overflow(self, body, title=None, overflow=None,
# Truncate our Title
title = title[:self.title_maxlen]

if self.body_maxlen > 0 and len(body) <= self.body_maxlen:
if self.body_maxlen > self.title_maxlen - 2:
# Combine title length into body if defined (2 for \r\n)
body_maxlen = self.body_maxlen \
if not title else self.body_maxlen - len(title) - 2
else:
# status quo
body_maxlen = self.body_maxlen

if body_maxlen > 0 and len(body) <= body_maxlen:
response.append({'body': body, 'title': title})
return response

if overflow == OverflowMode.TRUNCATE:
# Truncate our body and return
response.append({
'body': body[:self.body_maxlen],
'body': body[:body_maxlen],
'title': title,
})
# For truncate mode, we're done now
return response

# Display Count [XX/XX]
# ^^^^^^^^
# \\\\\\\\
# 8 characters (space + count)
display_count_width = 8

# the min accepted length of a title to allow for a counter display
display_count_threshold = 130

show_counter = title and len(body) > body_maxlen \
and self.title_maxlen > \
(display_count_threshold + display_count_width)

count = 0
if show_counter:
count = int(len(body) / body_maxlen) \
+ (1 if len(body) % body_maxlen else 0)

if len(title) > self.title_maxlen - display_count_width:
# Truncate our title further
title = title[:self.title_maxlen - display_count_width]

# 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)]
'body': body[i: i + body_maxlen],
'title': title + (
'' if not count else
' [{:02}/{:02}]'.format(idx, count))} for idx, i in
enumerate(range(0, len(body), body_maxlen), start=1)]

return response

Expand Down
62 changes: 62 additions & 0 deletions test/test_plugin_discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
from apprise import AppriseAttachment
from apprise import NotifyType
from apprise import NotifyFormat
from apprise.common import OverflowMode

from random import choice
from string import ascii_uppercase as str_alpha
from string import digits as str_num

# Disable logging for a cleaner testing output
import logging
Expand Down Expand Up @@ -593,6 +598,63 @@ def test_plugin_discord_general(mock_post):
assert response['params'].get('thread_id') == '12345'


@mock.patch('requests.post')
def test_plugin_discord_overflow(mock_post):
"""
NotifyDiscord() Overflow Checks
"""

# Initialize some generic (but valid) tokens
webhook_id = 'A' * 24
webhook_token = 'B' * 64

# Prepare Mock
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok

# Some variables we use to control the data we work with
body_len = 8000
title_len = 1024

# Number of characters per line
row = 24

# 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)])

# Create our title using random data
title = ''.join(choice(str_alpha + str_num) for _ in range(title_len))

results = NotifyDiscord.parse_url(
f'discord://{webhook_id}/{webhook_token}/?overflow=split')

assert isinstance(results, dict)
assert results['user'] is None
assert results['webhook_id'] == webhook_id
assert results['webhook_token'] == webhook_token
assert results['password'] is None
assert results['port'] is None
assert results['host'] == webhook_id
assert results['fullpath'] == f'/{webhook_token}/'
assert results['path'] == f'/{webhook_token}/'
assert results['query'] is None
assert results['schema'] == 'discord'
assert results['url'] == f'discord://{webhook_id}/{webhook_token}/'

instance = NotifyDiscord(**results)
assert isinstance(instance, NotifyDiscord)

results = instance._apply_overflow(
body, title=title, overflow=OverflowMode.SPLIT)

# Ensure we never exceed 2000 characters
for entry in results:
assert len(entry['title']) <= instance.title_maxlen
assert len(entry['title']) + len(entry['body']) < instance.body_maxlen


@mock.patch('requests.post')
def test_plugin_discord_markdown_extra(mock_post):
"""
Expand Down
61 changes: 58 additions & 3 deletions test/test_rest_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def notify(self, *args, **kwargs):
# and that the body remains untouched
chunks = obj._apply_overflow(body=body, title=title)
assert len(chunks) == 1
# -2 because \r\n are factored into calculation (safe whitespace)
assert body[0:TestNotification.body_maxlen] == chunks[0].get('body')
assert title == chunks[0].get('title')

Expand Down Expand Up @@ -327,10 +328,26 @@ def notify(self, *args, **kwargs):
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')
for idx, chunk in enumerate(chunks, start=1):
# Our title has a counter added to it
assert title[:-8] == chunk.get('title')[:-8]
assert chunk.get('title')[-8:] == \
' [{:02}/{:02}]'.format(idx, len(chunks))
# Our body is only broken up; not lost
_body = chunk.get('body')
assert body[offset: len(_body) + offset].rstrip() == _body
offset += len(_body)

# Another edge case where the title just isn't that long leaving
# a lot of space for the [xx/xx] entries (no truncation needed)
chunks = obj._apply_overflow(body=body, title=title[:20])
offset = 0
assert len(chunks) == 4
for idx, chunk in enumerate(chunks, start=1):
# Our title has a counter added to it
assert title[:20] == chunk.get('title')[:-8]
assert chunk.get('title')[-8:] == \
' [{:02}/{:02}]'.format(idx, len(chunks))
# Our body is only broken up; not lost
_body = chunk.get('body')
assert body[offset: len(_body) + offset].rstrip() == _body
Expand Down Expand Up @@ -386,6 +403,44 @@ def notify(self, *args, **kwargs):
assert bulk[offset: len(_body) + offset] == _body
offset += len(_body)

#
# Test case where our title_len is shorter then the value
# that would otherwise trigger the [XX/XX] elements
#

class TestNotification(NotifyBase):

# Set a small title length
title_maxlen = 100

# Enforce a body length. Make sure it's an int.
body_maxlen = int(body_len / 4)

def __init__(self, *args, **kwargs):
super().__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) == 7
for idx, chunk in enumerate(chunks, start=1):
# Our title is truncated and no counter added
assert title[:100] == chunk.get('title')

# Our body is only broken up; not lost
_body = chunk.get('body')
assert body[offset: len(_body) + offset].rstrip() == _body
offset += len(_body)


def test_notify_markdown_general():
"""
Expand Down

0 comments on commit f3c699a

Please sign in to comment.