Refactored (Split and Truncate) Overflow Engine (#1038)

This commit is contained in:
Chris Caron 2024-01-06 17:03:00 -05:00 committed by GitHub
parent f3c699ab82
commit 1da1d4800c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 3696 additions and 75 deletions

View File

@ -199,6 +199,54 @@ class NotifyBase(URLBase):
},
})
#
# Overflow Defaults / Configuration applicable to SPLIT mode only
#
# Display Count [X/X]
# ^^^^^^
# \\\\\\
# 6 characters (space + count)
# Display Count [XX/XX]
# ^^^^^^^^
# \\\\\\\\
# 8 characters (space + count)
# Display Count [XXX/XXX]
# ^^^^^^^^^^
# \\\\\\\\\\
# 10 characters (space + count)
# Display Count [XXXX/XXXX]
# ^^^^^^^^^^^^
# \\\\\\\\\\\\
# 12 characters (space + count)
#
# Given the above + some buffer we come up with the following:
# If this value is exceeded, display counts automatically shut off
overflow_max_display_count_width = 12
# The number of characters to reserver for whitespace buffering
# This is detected automatically, but you can enforce a value if
# you desire:
overflow_buffer = 0
# the min accepted length of a title to allow for a counter display
overflow_display_count_threshold = 130
# Whether or not when over-flow occurs, if the title should be repeated
# each time the message is split up
# - None: Detect
# - True: Always display title once
# - False: Display the title for each occurance
overflow_display_title_once = None
# If this is set to to True:
# The title_maxlen should be considered as a subset of the body_maxlen
# Hence: len(title) + len(body) should never be greater then body_maxlen
#
# If set to False, then there is no corrorlation between title_maxlen
# restrictions and that of body_maxlen
overflow_amalgamate_title = False
def __init__(self, **kwargs):
"""
Initialize some general configuration that will keep things consistent
@ -454,7 +502,6 @@ class NotifyBase(URLBase):
overflow = self.overflow_mode
if self.title_maxlen <= 0 and len(title) > 0:
if self.notify_format == NotifyFormat.HTML:
# Content is appended to body as html
body = '<{open_tag}>{title}</{close_tag}>' \
@ -490,17 +537,37 @@ class NotifyBase(URLBase):
response.append({'body': body, 'title': title})
return response
elif len(title) > self.title_maxlen:
# Truncate our Title
title = title[:self.title_maxlen]
# a value of '2' allows for the \r\n that is applied when
# amalgamating the title
overflow_buffer = max(2, self.overflow_buffer) \
if (self.title_maxlen == 0 and len(title)) \
else self.overflow_buffer
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
#
# If we reach here in our code, then we're using TRUNCATE, or SPLIT
# actions which require some math to handle the data
#
# Handle situations where our body and title are amalamated into one
# calculation
title_maxlen = self.title_maxlen \
if not self.overflow_amalgamate_title \
else min(len(title) + self.overflow_max_display_count_width,
self.title_maxlen, self.body_maxlen)
if len(title) > title_maxlen:
# Truncate our Title
title = title[:title_maxlen].rstrip()
if self.overflow_amalgamate_title and (
self.body_maxlen - overflow_buffer) >= title_maxlen:
body_maxlen = (self.body_maxlen if not title else (
self.body_maxlen - title_maxlen)) - overflow_buffer
else:
# status quo
body_maxlen = self.body_maxlen
body_maxlen = self.body_maxlen \
if not self.overflow_amalgamate_title else \
(self.body_maxlen - overflow_buffer)
if body_maxlen > 0 and len(body) <= body_maxlen:
response.append({'body': body, 'title': title})
@ -509,43 +576,109 @@ class NotifyBase(URLBase):
if overflow == OverflowMode.TRUNCATE:
# Truncate our body and return
response.append({
'body': body[:body_maxlen],
'body': body[:body_maxlen].lstrip('\r\n\x0b\x0c').rstrip(),
'title': title,
})
# For truncate mode, we're done now
return response
# Display Count [XX/XX]
# ^^^^^^^^
# \\\\\\\\
# 8 characters (space + count)
display_count_width = 8
if self.overflow_display_title_once is None:
# Detect if we only display our title once or not:
overflow_display_title_once = \
True if self.overflow_amalgamate_title and \
body_maxlen < self.overflow_display_count_threshold \
else False
else:
# Take on defined value
# 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]
overflow_display_title_once = self.overflow_display_title_once
# 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 + 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)]
if not overflow_display_title_once and not (
# edge case that can occur when overflow_display_title_once is
# forced off, but no body exists
self.overflow_amalgamate_title and body_maxlen <= 0):
show_counter = title and len(body) > body_maxlen and \
((self.overflow_amalgamate_title and
body_maxlen >= self.overflow_display_count_threshold) or
(not self.overflow_amalgamate_title and
title_maxlen > self.overflow_display_count_threshold)) and (
title_maxlen > (self.overflow_max_display_count_width +
overflow_buffer) and
self.title_maxlen >= self.overflow_display_count_threshold)
count = 0
template = ''
if show_counter:
# introduce padding
body_maxlen -= overflow_buffer
count = int(len(body) / body_maxlen) \
+ (1 if len(body) % body_maxlen else 0)
# Detect padding and prepare template
digits = len(str(count))
template = ' [{:0%d}/{:0%d}]' % (digits, digits)
# Update our counter
overflow_display_count_width = 4 + (digits * 2)
if overflow_display_count_width <= \
self.overflow_max_display_count_width:
if len(title) > \
title_maxlen - overflow_display_count_width:
# Truncate our title further
title = title[:title_maxlen -
overflow_display_count_width]
else: # Way to many messages to display
show_counter = False
response = [{
'body': body[i: i + body_maxlen]
.lstrip('\r\n\x0b\x0c').rstrip(),
'title': title + (
'' if not show_counter else
template.format(idx, count))} for idx, i in
enumerate(range(0, len(body), body_maxlen), start=1)]
else: # Display title once and move on
response = []
try:
i = range(0, len(body), body_maxlen)[0]
response.append({
'body': body[i: i + body_maxlen]
.lstrip('\r\n\x0b\x0c').rstrip(),
'title': title,
})
except (ValueError, IndexError):
# IndexError:
# - This happens if there simply was no body to display
# ValueError:
# - This happens when body_maxlen < 0 (due to title being
# so large)
# No worries; send title along
response.append({
'body': '',
'title': title,
})
# Ensure our start is set properly
body_maxlen = 0
# Now re-calculate based on the increased length
for i in range(body_maxlen, len(body), self.body_maxlen):
response.append({
'body': body[i: i + self.body_maxlen]
.lstrip('\r\n\x0b\x0c').rstrip(),
'title': '',
})
return response

View File

@ -105,6 +105,10 @@ class NotifyDiscord(NotifyBase):
# The maximum allowable characters allowed in the body per message
body_maxlen = 2000
# The 2000 characters above defined by the body_maxlen include that of the
# title. Setting this to True ensures overflow options behave properly
overflow_amalgamate_title = True
# Discord has a limit of the number of fields you can include in an
# embeds message. This value allows the discord message to safely
# break into multiple messages to handle these cases.

View File

@ -652,7 +652,7 @@ def test_plugin_discord_overflow(mock_post):
# 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
assert len(entry['title']) + len(entry['body']) <= instance.body_maxlen
@mock.patch('requests.post')

File diff suppressed because it is too large Load Diff