Skip to content

Commit

Permalink
Title & body not required if attachment specified (#916)
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc authored Aug 12, 2023
1 parent 236e67f commit 3d16cbf
Show file tree
Hide file tree
Showing 27 changed files with 317 additions and 125 deletions.
7 changes: 6 additions & 1 deletion apprise/Apprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def _create_notify_gen(self, body, title='',
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)
Expand Down Expand Up @@ -689,6 +689,11 @@ def details(self, lang=None, show_requirements=False, show_disabled=False):
# 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)
Expand Down
10 changes: 8 additions & 2 deletions apprise/plugins/NotifyAppriseAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -260,7 +263,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,

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:
Expand Down Expand Up @@ -310,7 +313,10 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,

if self.method == AppriseAPIMethod.JSON:
headers['Content-Type'] = 'application/json'
payload['attachments'] = attachments

if attachments:
payload['attachments'] = attachments

payload = dumps(payload)

if self.__tags:
Expand Down
36 changes: 35 additions & 1 deletion apprise/plugins/NotifyBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -316,7 +328,7 @@ async def do_send(**kwargs2):
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):
"""
Expand All @@ -339,6 +351,28 @@ def _build_send_calls(self, body, title=None,
# 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

Expand Down
145 changes: 76 additions & 69 deletions apprise/plugins/NotifyDiscord.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -255,61 +258,6 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
# 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
Expand All @@ -318,22 +266,81 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
# 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 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:
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({
Expand All @@ -356,7 +363,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
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

Expand Down
5 changes: 4 additions & 1 deletion apprise/plugins/NotifyEmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -770,7 +773,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
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
Expand Down
5 changes: 4 additions & 1 deletion apprise/plugins/NotifyForm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -345,7 +348,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,

# 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:
Expand Down
5 changes: 4 additions & 1 deletion apprise/plugins/NotifyJSON.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -289,7 +292,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,

# 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:
Expand Down
5 changes: 4 additions & 1 deletion apprise/plugins/NotifyMailgun.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -371,7 +374,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
# 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:
Expand Down
9 changes: 6 additions & 3 deletions apprise/plugins/NotifyMastodon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -414,11 +418,10 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
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
Expand Down Expand Up @@ -578,7 +581,7 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
_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))
Expand Down
Loading

0 comments on commit 3d16cbf

Please sign in to comment.