Title & body not required if attachment specified (#916)

This commit is contained in:
Chris Caron 2023-08-12 16:32:07 -04:00 committed by GitHub
parent 236e67f497
commit 3d16cbf3d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 317 additions and 125 deletions

View File

@ -458,7 +458,7 @@ class Apprise:
logger.error(msg)
raise TypeError(msg)
if not (title or body):
if not (title or body or attach):
msg = "No message content specified to deliver"
logger.error(msg)
raise TypeError(msg)
@ -689,6 +689,11 @@ class Apprise:
# Placeholder - populated below
'details': None,
# Let upstream service know of the plugins that support
# attachments
'attachment_support': getattr(
plugin, 'attachment_support', False),
# Differentiat between what is a custom loaded plugin and
# which is native.
'category': getattr(plugin, 'category', None)

View File

@ -77,6 +77,9 @@ class NotifyAppriseAPI(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_apprise_api'
# Support attachments
attachment_support = True
# Depending on the number of transactions/notifications taking place, this
# could take a while. 30 seconds should be enough to perform the task
socket_read_timeout = 30.0
@ -260,7 +263,7 @@ class NotifyAppriseAPI(NotifyBase):
attachments = []
files = []
if attach:
if attach and self.attachment_support:
for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
@ -310,7 +313,10 @@ class NotifyAppriseAPI(NotifyBase):
if self.method == AppriseAPIMethod.JSON:
headers['Content-Type'] = 'application/json'
payload['attachments'] = attachments
if attachments:
payload['attachments'] = attachments
payload = dumps(payload)
if self.__tags:

View File

@ -139,6 +139,18 @@ class NotifyBase(URLBase):
# Default Overflow Mode
overflow_mode = OverflowMode.UPSTREAM
# Support Attachments; this defaults to being disabled.
# Since apprise allows you to send attachments without a body or title
# defined, by letting Apprise know the plugin won't support attachments
# up front, it can quickly pass over and ignore calls to these end points.
# You must set this to true if your application can handle attachments.
# You must also consider a flow change to your notification if this is set
# to True as well as now there will be cases where both the body and title
# may not be set. There will never be a case where a body, or attachment
# isn't set in the same call to your notify() function.
attachment_support = False
# Default Title HTML Tagging
# When a title is specified for a notification service that doesn't accept
# titles, by default apprise tries to give a plesant view and convert the
@ -316,7 +328,7 @@ class NotifyBase(URLBase):
the_cors = (do_send(**kwargs2) for kwargs2 in send_calls)
return all(await asyncio.gather(*the_cors))
def _build_send_calls(self, body, title=None,
def _build_send_calls(self, body=None, title=None,
notify_type=NotifyType.INFO, overflow=None,
attach=None, body_format=None, **kwargs):
"""
@ -339,6 +351,28 @@ class NotifyBase(URLBase):
# bad attachments
raise
# Handle situations where the body is None
body = '' if not body else body
elif not (body or attach):
# If there is not an attachment at the very least, a body must be
# present
msg = "No message body or attachment was specified."
self.logger.warning(msg)
raise TypeError(msg)
if not body and not self.attachment_support:
# If no body was specified, then we know that an attachment
# was. This is logic checked earlier in the code.
#
# Knowing this, if the plugin itself doesn't support sending
# attachments, there is nothing further to do here, just move
# along.
msg = f"{self.service_name} does not support attachments; " \
" service skipped"
self.logger.warning(msg)
raise TypeError(msg)
# Handle situations where the title is None
title = '' if not title else title

View File

@ -84,6 +84,9 @@ class NotifyDiscord(NotifyBase):
# Discord Webhook
notify_url = 'https://discord.com/api/webhooks'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_256
@ -255,61 +258,6 @@ class NotifyDiscord(NotifyBase):
# Acquire image_url
image_url = self.image_url(notify_type)
# our fields variable
fields = []
if self.notify_format == NotifyFormat.MARKDOWN:
# Use embeds for payload
payload['embeds'] = [{
'author': {
'name': self.app_id,
'url': self.app_url,
},
'title': title,
'description': body,
# Our color associated with our notification
'color': self.color(notify_type, int),
}]
if self.footer:
# Acquire logo URL
logo_url = self.image_url(notify_type, logo=True)
# Set Footer text to our app description
payload['embeds'][0]['footer'] = {
'text': self.app_desc,
}
if self.footer_logo and logo_url:
payload['embeds'][0]['footer']['icon_url'] = logo_url
if self.include_image and image_url:
payload['embeds'][0]['thumbnail'] = {
'url': image_url,
'height': 256,
'width': 256,
}
if self.fields:
# Break titles out so that we can sort them in embeds
description, fields = self.extract_markdown_sections(body)
# Swap first entry for description
payload['embeds'][0]['description'] = description
if fields:
# Apply our additional parsing for a better presentation
payload['embeds'][0]['fields'] = \
fields[:self.discord_max_fields]
# Remove entry from head of fields
fields = fields[self.discord_max_fields:]
else:
# not markdown
payload['content'] = \
body if not title else "{}\r\n{}".format(title, body)
if self.avatar and (image_url or self.avatar_url):
payload['avatar_url'] = \
self.avatar_url if self.avatar_url else image_url
@ -318,22 +266,81 @@ class NotifyDiscord(NotifyBase):
# Optionally override the default username of the webhook
payload['username'] = self.user
# Associate our thread_id with our message
params = {'thread_id': self.thread_id} if self.thread_id else None
if not self._send(payload, params=params):
# We failed to post our message
return False
# Process any remaining fields IF set
if fields:
payload['embeds'][0]['description'] = ''
for i in range(0, len(fields), self.discord_max_fields):
payload['embeds'][0]['fields'] = \
fields[i:i + self.discord_max_fields]
if not self._send(payload):
# We failed to post our message
return False
if body:
# our fields variable
fields = []
if attach:
if self.notify_format == NotifyFormat.MARKDOWN:
# Use embeds for payload
payload['embeds'] = [{
'author': {
'name': self.app_id,
'url': self.app_url,
},
'title': title,
'description': body,
# Our color associated with our notification
'color': self.color(notify_type, int),
}]
if self.footer:
# Acquire logo URL
logo_url = self.image_url(notify_type, logo=True)
# Set Footer text to our app description
payload['embeds'][0]['footer'] = {
'text': self.app_desc,
}
if self.footer_logo and logo_url:
payload['embeds'][0]['footer']['icon_url'] = logo_url
if self.include_image and image_url:
payload['embeds'][0]['thumbnail'] = {
'url': image_url,
'height': 256,
'width': 256,
}
if self.fields:
# Break titles out so that we can sort them in embeds
description, fields = self.extract_markdown_sections(body)
# Swap first entry for description
payload['embeds'][0]['description'] = description
if fields:
# Apply our additional parsing for a better
# presentation
payload['embeds'][0]['fields'] = \
fields[:self.discord_max_fields]
# Remove entry from head of fields
fields = fields[self.discord_max_fields:]
else:
# not markdown
payload['content'] = \
body if not title else "{}\r\n{}".format(title, body)
if not self._send(payload, params=params):
# We failed to post our message
return False
# Process any remaining fields IF set
if fields:
payload['embeds'][0]['description'] = ''
for i in range(0, len(fields), self.discord_max_fields):
payload['embeds'][0]['fields'] = \
fields[i:i + self.discord_max_fields]
if not self._send(payload):
# We failed to post our message
return False
if attach and self.attachment_support:
# Update our payload; the idea is to preserve it's other detected
# and assigned values for re-use here too
payload.update({
@ -356,7 +363,7 @@ class NotifyDiscord(NotifyBase):
for attachment in attach:
self.logger.info(
'Posting Discord Attachment {}'.format(attachment.name))
if not self._send(payload, attach=attachment):
if not self._send(payload, params=params, attach=attachment):
# We failed to post our message
return False

View File

@ -341,6 +341,9 @@ class NotifyEmail(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_email'
# Support attachments
attachment_support = True
# Default Notify Format
notify_format = NotifyFormat.HTML
@ -770,7 +773,7 @@ class NotifyEmail(NotifyBase):
else:
base = MIMEText(body, 'plain', 'utf-8')
if attach:
if attach and self.attachment_support:
mixed = MIMEMultipart("mixed")
mixed.attach(base)
# Now store our attachments

View File

@ -99,6 +99,9 @@ class NotifyForm(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_Form'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_128
@ -345,7 +348,7 @@ class NotifyForm(NotifyBase):
# Track our potential attachments
files = []
if attach:
if attach and self.attachment_support:
for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:

View File

@ -80,6 +80,9 @@ class NotifyJSON(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_JSON'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_128
@ -289,7 +292,7 @@ class NotifyJSON(NotifyBase):
# Track our potential attachments
attachments = []
if attach:
if attach and self.attachment_support:
for attachment in attach:
# Perform some simple error checking
if not attachment:

View File

@ -121,6 +121,9 @@ class NotifyMailgun(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mailgun'
# Support attachments
attachment_support = True
# Default Notify Format
notify_format = NotifyFormat.HTML
@ -371,7 +374,7 @@ class NotifyMailgun(NotifyBase):
# Track our potential files
files = {}
if attach:
if attach and self.attachment_support:
for idx, attachment in enumerate(attach):
# Perform some simple error checking
if not attachment:

View File

@ -111,6 +111,10 @@ class NotifyMastodon(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mastodon'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
# Allows the user to specify the NotifyImageSize object; this is supported
# through the webhook
image_size = NotifyImageSize.XY_128
@ -414,11 +418,10 @@ class NotifyMastodon(NotifyBase):
else:
targets.add(myself)
if attach:
if attach and self.attachment_support:
# We need to upload our payload first so that we can source it
# in remaining messages
for attachment in attach:
# Perform some simple error checking
if not attachment:
# We could not access the attachment
@ -578,7 +581,7 @@ class NotifyMastodon(NotifyBase):
_payload = deepcopy(payload)
_payload['media_ids'] = media_ids
if no:
if no or not body:
# strip text and replace it with the image representation
_payload['status'] = \
'{:02d}/{:02d}'.format(no + 1, len(batches))

View File

@ -172,6 +172,9 @@ class NotifyNtfy(NotifyBase):
# Default upstream/cloud host if none is defined
cloud_notify_url = 'https://ntfy.sh'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_256
@ -405,14 +408,14 @@ class NotifyNtfy(NotifyBase):
# Retrieve our topic
topic = topics.pop()
if attach:
if attach and self.attachment_support:
# We need to upload our payload first so that we can source it
# in remaining messages
for no, attachment in enumerate(attach):
# First message only includes the text
_body = body if not no else None
_title = title if not no else None
# First message only includes the text (if defined)
_body = body if not no and body else None
_title = title if not no and title else None
# Perform some simple error checking
if not attachment:
@ -543,11 +546,8 @@ class NotifyNtfy(NotifyBase):
# Default response type
response = None
if data:
data = data if attach else dumps(data)
else: # not data:
data = None
if not attach:
data = dumps(data)
try:
r = requests.post(

View File

@ -75,6 +75,9 @@ class NotifyPushBullet(NotifyBase):
# PushBullet uses the http protocol with JSON requests
notify_url = 'https://api.pushbullet.com/v2/{}'
# Support attachments
attachment_support = True
# Define object templates
templates = (
'{schema}://{accesstoken}',
@ -150,7 +153,7 @@ class NotifyPushBullet(NotifyBase):
# Build a list of our attachments
attachments = []
if attach:
if attach and self.attachment_support:
# We need to upload our payload first so that we can source it
# in remaining messages
for attachment in attach:
@ -261,14 +264,15 @@ class NotifyPushBullet(NotifyBase):
"PushBullet recipient {} parsed as a device"
.format(recipient))
okay, response = self._send(
self.notify_url.format('pushes'), payload)
if not okay:
has_error = True
continue
if body:
okay, response = self._send(
self.notify_url.format('pushes'), payload)
if not okay:
has_error = True
continue
self.logger.info(
'Sent PushBullet notification to "%s".' % (recipient))
self.logger.info(
'Sent PushBullet notification to "%s".' % (recipient))
for attach_payload in attachments:
# Send our attachments to our same user (already prepared as

View File

@ -336,6 +336,9 @@ class NotifyPushSafer(NotifyBase):
# The default secure protocol
secure_protocol = 'psafers'
# Support attachments
attachment_support = True
# Number of requests to a allow per second
request_rate_per_sec = 1.2
@ -546,7 +549,7 @@ class NotifyPushSafer(NotifyBase):
# Initialize our list of attachments
attachments = []
if attach:
if attach and self.attachment_support:
# We need to upload our payload first so that we can source it
# in remaining messages
for attachment in attach:

View File

@ -164,6 +164,9 @@ class NotifyPushover(NotifyBase):
# Pushover uses the http protocol with JSON requests
notify_url = 'https://api.pushover.net/1/messages.json'
# Support attachments
attachment_support = True
# The maximum allowable characters allowed in the body per message
body_maxlen = 1024
@ -381,22 +384,25 @@ class NotifyPushover(NotifyBase):
if self.priority == PushoverPriority.EMERGENCY:
payload.update({'retry': self.retry, 'expire': self.expire})
if attach:
if attach and self.attachment_support:
# Create a copy of our payload
_payload = payload.copy()
# Send with attachments
for attachment in attach:
# Simple send
for no, attachment in enumerate(attach):
if no or not body:
# To handle multiple attachments, clean up our message
_payload['message'] = attachment.name
if not self._send(_payload, attachment):
# Mark our failure
has_error = True
# clean exit from our attachment loop
break
# To handle multiple attachments, clean up our message
_payload['title'] = '...'
_payload['message'] = attachment.name
# Clear our title if previously set
_payload['title'] = ''
# No need to alarm for each consecutive attachment uploaded
# afterwards
_payload['sound'] = PushoverSound.NONE

View File

@ -136,6 +136,9 @@ class NotifySES(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_ses'
# Support attachments
attachment_support = True
# AWS is pretty good for handling data load so request limits
# can occur in much shorter bursts
request_rate_per_sec = 2.5
@ -427,7 +430,8 @@ class NotifySES(NotifyBase):
content = MIMEText(body, 'plain', 'utf-8')
# Create a Multipart container if there is an attachment
base = MIMEMultipart() if attach else content
base = MIMEMultipart() \
if attach and self.attachment_support else content
# TODO: Deduplicate with `NotifyEmail`?
base['Subject'] = Header(title, 'utf-8')
@ -443,7 +447,7 @@ class NotifySES(NotifyBase):
timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")
base['X-Application'] = self.app_id
if attach:
if attach and self.attachment_support:
# First attach our body to our content as the first element
base.attach(content)

View File

@ -112,6 +112,9 @@ class NotifySMSEagle(NotifyBase):
# The path we send our notification to
notify_path = '/jsonrpc/sms'
# Support attachments
attachment_support = True
# The maxumum length of the text message
# The actual limit is 160 but SMSEagle looks after the handling
# of large messages in it's upstream service
@ -340,7 +343,7 @@ class NotifySMSEagle(NotifyBase):
has_error = False
attachments = []
if attach:
if attach and self.attachment_support:
for attachment in attach:
# Perform some simple error checking
if not attachment:

View File

@ -91,6 +91,9 @@ class NotifySMTP2Go(NotifyBase):
# Notify URL
notify_url = 'https://api.smtp2go.com/v3/email/send'
# Support attachments
attachment_support = True
# Default Notify Format
notify_format = NotifyFormat.HTML
@ -294,8 +297,8 @@ class NotifySMTP2Go(NotifyBase):
# Track our potential attachments
attachments = []
if attach:
for idx, attachment in enumerate(attach):
if attach and self.attachment_support:
for attachment in attach:
# Perform some simple error checking
if not attachment:
# We could not access the attachment

View File

@ -68,6 +68,9 @@ class NotifySignalAPI(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_signal'
# Support attachments
attachment_support = True
# The maximum targets to include when doing batch transfers
default_batch_size = 10
@ -234,7 +237,7 @@ class NotifySignalAPI(NotifyBase):
has_error = False
attachments = []
if attach:
if attach and self.attachment_support:
for attachment in attach:
# Perform some simple error checking
if not attachment:
@ -281,7 +284,7 @@ class NotifySignalAPI(NotifyBase):
payload = {
'message': "{}{}".format(
'' if not self.status else '{} '.format(
self.asset.ascii(notify_type)), body),
self.asset.ascii(notify_type)), body).rstrip(),
"number": self.source,
"recipients": []
}

View File

@ -143,6 +143,10 @@ class NotifySlack(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_slack'
# Support attachments
attachment_support = True
# The maximum targets to include when doing batch transfers
# Slack Webhook URL
webhook_url = 'https://hooks.slack.com/services'
@ -522,7 +526,8 @@ class NotifySlack(NotifyBase):
# Include the footer only if specified to do so
payload['attachments'][0]['footer'] = self.app_id
if attach and self.mode is SlackMode.WEBHOOK:
if attach and self.attachment_support \
and self.mode is SlackMode.WEBHOOK:
# Be friendly; let the user know why they can't send their
# attachments if using the Webhook mode
self.logger.warning(
@ -600,7 +605,8 @@ class NotifySlack(NotifyBase):
' to {}'.format(channel)
if channel is not None else ''))
if attach and self.mode is SlackMode.BOT and attach_channel_list:
if attach and self.attachment_support and \
self.mode is SlackMode.BOT and attach_channel_list:
# Send our attachments (can only be done in bot mode)
for attachment in attach:

View File

@ -118,6 +118,9 @@ class NotifySparkPost(NotifyBase):
# The services URL
service_url = 'https://sparkpost.com/'
# Support attachments
attachment_support = True
# All notification requests are secure
secure_protocol = 'sparkpost'
@ -543,7 +546,7 @@ class NotifySparkPost(NotifyBase):
else:
payload['content']['text'] = body
if attach:
if attach and self.attachment_support:
# Prepare ourselves an attachment object
payload['content']['attachments'] = []

View File

@ -277,8 +277,7 @@ class NotifyStreamlabs(NotifyBase):
return
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
**kwargs):
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Streamlabs notification call (either donation or alert)
"""

View File

@ -123,6 +123,9 @@ class NotifyTelegram(NotifyBase):
# Telegram uses the http protocol with JSON requests
notify_url = 'https://api.telegram.org/bot'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_256
@ -715,6 +718,10 @@ class NotifyTelegram(NotifyBase):
# Prepare our payload based on HTML or TEXT
payload['text'] = body
# Handle payloads without a body specified (but an attachment present)
attach_content = \
TelegramContentPlacement.AFTER if not body else self.content
# Create a copy of the chat_ids list
targets = list(self.targets)
while len(targets):
@ -748,7 +755,8 @@ class NotifyTelegram(NotifyBase):
'Failed to send Telegram type image to {}.',
payload['chat_id'])
if attach and self.content == TelegramContentPlacement.AFTER:
if attach and self.attachment_support and \
attach_content == TelegramContentPlacement.AFTER:
# Send our attachments now (if specified and if it exists)
if not self._send_attachments(
chat_id=payload['chat_id'], notify_type=notify_type,
@ -757,6 +765,10 @@ class NotifyTelegram(NotifyBase):
has_error = True
continue
if not body:
# Nothing more to do; move along to the next attachment
continue
# Always call throttle before any remote server i/o is made;
# Telegram throttles to occur before sending the image so that
# content can arrive together.
@ -819,7 +831,8 @@ class NotifyTelegram(NotifyBase):
self.logger.info('Sent Telegram notification.')
if attach and self.content == TelegramContentPlacement.BEFORE:
if attach and self.attachment_support \
and attach_content == TelegramContentPlacement.BEFORE:
# Send our attachments now (if specified and if it exists) as
# it was identified to send the content before the attachments
# which is now done.

View File

@ -88,6 +88,9 @@ class NotifyTwitter(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_twitter'
# Support attachments
attachment_support = True
# Do not set body_maxlen as it is set in a property value below
# since the length varies depending if we are doing a direct message
# or a tweet
@ -285,7 +288,7 @@ class NotifyTwitter(NotifyBase):
# Build a list of our attachments
attachments = []
if attach:
if attach and self.attachment_support:
# We need to upload our payload first so that we can source it
# in remaining messages
for attachment in attach:
@ -414,7 +417,7 @@ class NotifyTwitter(NotifyBase):
_payload = deepcopy(payload)
_payload['media_ids'] = media_ids
if no:
if no or not body:
# strip text and replace it with the image representation
_payload['status'] = \
'{:02d}/{:02d}'.format(no + 1, len(batches))
@ -514,7 +517,7 @@ class NotifyTwitter(NotifyBase):
'additional_owners':
','.join([str(x) for x in targets.values()])
}
if no:
if no or not body:
# strip text and replace it with the image representation
_data['text'] = \
'{:02d}/{:02d}'.format(no + 1, len(attachments))

View File

@ -79,6 +79,9 @@ class NotifyXML(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_XML'
# Support attachments
attachment_support = True
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_128
@ -340,7 +343,7 @@ class NotifyXML(NotifyBase):
['<{}>{}</{}>'.format(k, v, k) for k, v in payload_base.items()])
attachments = []
if attach:
if attach and self.attachment_support:
for attachment in attach:
# Perform some simple error checking
if not attachment:

View File

@ -14,6 +14,13 @@ services:
volumes:
- ./:/apprise
test.py311:
build:
context: .
dockerfile: Dockerfile.py311
volumes:
- ./:/apprise
rpmbuild.el8:
build:
context: .
@ -36,6 +43,17 @@ services:
- ./:/apprise
#
# Every Day testing
#
# Connect to web and create a new project using the manage script
# -> docker-compose run --rm test.py311 bash
# bin/apprise -
# bin/checkdone.sh
#
# Other Testing
#
# Connect to web and create a new project using the manage script
# -> docker-compose run --rm test.py36 bash
# bin/apprise -

View File

@ -477,6 +477,52 @@ class AppriseURLTester:
notify_type=notify_type,
attach=attach) == attach_response
if obj.attachment_support:
#
# Services that support attachments should support
# sending a attachment (or more) without a body or
# title specified:
#
assert obj.notify(
body=None, title=None,
notify_type=notify_type,
attach=attach) == attach_response
# Turn off attachment support on the notifications
# that support it so we can test that any logic we
# have ot test against this flag is ran
obj.attachment_support = False
#
# Notifications should still transmit as normal if
# Attachment support is flipped off
#
assert obj.notify(
body=self.body, title=self.title,
notify_type=notify_type,
attach=attach) == notify_response
#
# We should not be able to send a message without a
# body or title in this circumstance
#
assert obj.notify(
body=None, title=None,
notify_type=notify_type,
attach=attach) is False
# Toggle Back
obj.attachment_support = True
else: # No Attachment support
#
# We should not be able to send a message without a
# body or title in this circumstance
#
assert obj.notify(
body=None, title=None,
notify_type=notify_type,
attach=attach) is False
else:
for _exception in self.req_exceptions:

View File

@ -257,9 +257,11 @@ def apprise_test(do_notify):
assert do_notify(a, title=object(), body=b'bytes') is False
assert do_notify(a, title=b"bytes", body=object()) is False
# As long as one is present, we're good
# A Body must be present
assert do_notify(a, title='present', body=None) is False
# Other combinations work fine
assert do_notify(a, title=None, body='present') is True
assert do_notify(a, title='present', body=None) is True
assert do_notify(a, title="present", body="present") is True
# Send Attachment with success

View File

@ -170,7 +170,13 @@ def test_plugin_boxcar_edge_cases(mock_post, mock_get):
# Test notifications without a body or a title
p = NotifyBoxcar(access=access, secret=secret, targets=None)
assert p.notify(body=None, title=None, notify_type=NotifyType.INFO) is True
# Neither a title or body was specified
assert p.notify(
body=None, title=None, notify_type=NotifyType.INFO) is False
# Acceptable when data is provided:
assert p.notify(
body="Test", title=None, notify_type=NotifyType.INFO) is True
# Test comma, separate values
device = 'a' * 64