Browse Source

Improve split and truncate overflow methods (#1035)

pull/1042/head
Chris Caron 11 months ago committed by GitHub
parent
commit
f3c699ab82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      apprise/plugins/NotifyBase.py
  2. 62
      test/test_plugin_discord.py
  3. 61
      test/test_rest_plugins.py

41
apprise/plugins/NotifyBase.py

@ -494,25 +494,58 @@ class NotifyBase(URLBase):
# 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

62
test/test_plugin_discord.py

@ -40,6 +40,11 @@ from apprise import Apprise
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
@ -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):
"""

61
test/test_rest_plugins.py

@ -163,6 +163,7 @@ def test_notify_overflow_truncate():
# 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')
@ -327,10 +328,26 @@ def test_notify_overflow_split():
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
@ -386,6 +403,44 @@ def test_notify_overflow_split():
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():
"""

Loading…
Cancel
Save