Improve split and truncate overflow methods (#1035)

pull/1042/head
Chris Caron 2023-12-29 18:52:39 -05:00 committed by GitHub
parent bb4acd0bdf
commit f3c699ab82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 157 additions and 7 deletions

View File

@ -494,25 +494,58 @@ class NotifyBase(URLBase):
# Truncate our Title # Truncate our Title
title = title[:self.title_maxlen] 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}) response.append({'body': body, 'title': title})
return response return response
if overflow == OverflowMode.TRUNCATE: if overflow == OverflowMode.TRUNCATE:
# Truncate our body and return # Truncate our body and return
response.append({ response.append({
'body': body[:self.body_maxlen], 'body': body[:body_maxlen],
'title': title, 'title': title,
}) })
# For truncate mode, we're done now # For truncate mode, we're done now
return response 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. # 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 # For here, we want to split the message as many times as we have to
# in order to fit it within the designated limits. # in order to fit it within the designated limits.
response = [{ response = [{
'body': body[i: i + self.body_maxlen], 'body': body[i: i + body_maxlen],
'title': title} for i in range(0, len(body), self.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 return response

View File

@ -40,6 +40,11 @@ from apprise import Apprise
from apprise import AppriseAttachment from apprise import AppriseAttachment
from apprise import NotifyType from apprise import NotifyType
from apprise import NotifyFormat 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 # Disable logging for a cleaner testing output
import logging import logging
@ -593,6 +598,63 @@ def test_plugin_discord_general(mock_post):
assert response['params'].get('thread_id') == '12345' 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') @mock.patch('requests.post')
def test_plugin_discord_markdown_extra(mock_post): def test_plugin_discord_markdown_extra(mock_post):
""" """

View File

@ -163,6 +163,7 @@ def test_notify_overflow_truncate():
# and that the body remains untouched # and that the body remains untouched
chunks = obj._apply_overflow(body=body, title=title) chunks = obj._apply_overflow(body=body, title=title)
assert len(chunks) == 1 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 body[0:TestNotification.body_maxlen] == chunks[0].get('body')
assert title == chunks[0].get('title') assert title == chunks[0].get('title')
@ -327,10 +328,26 @@ def test_notify_overflow_split():
chunks = obj._apply_overflow(body=body, title=title) chunks = obj._apply_overflow(body=body, title=title)
offset = 0 offset = 0
assert len(chunks) == 4 assert len(chunks) == 4
for chunk in chunks: for idx, chunk in enumerate(chunks, start=1):
# Our title never changes # Our title has a counter added to it
assert title == chunk.get('title') 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 # Our body is only broken up; not lost
_body = chunk.get('body') _body = chunk.get('body')
assert body[offset: len(_body) + offset].rstrip() == _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 assert bulk[offset: len(_body) + offset] == _body
offset += len(_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(): def test_notify_markdown_general():
""" """