mirror of https://github.com/caronc/apprise
Title & body not required if attachment specified (#916)
parent
236e67f497
commit
3d16cbf3d3
|
@ -458,7 +458,7 @@ class Apprise:
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not (title or body):
|
if not (title or body or attach):
|
||||||
msg = "No message content specified to deliver"
|
msg = "No message content specified to deliver"
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
@ -689,6 +689,11 @@ class Apprise:
|
||||||
# Placeholder - populated below
|
# Placeholder - populated below
|
||||||
'details': None,
|
'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
|
# Differentiat between what is a custom loaded plugin and
|
||||||
# which is native.
|
# which is native.
|
||||||
'category': getattr(plugin, 'category', None)
|
'category': getattr(plugin, 'category', None)
|
||||||
|
|
|
@ -77,6 +77,9 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_apprise_api'
|
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
|
# Depending on the number of transactions/notifications taking place, this
|
||||||
# could take a while. 30 seconds should be enough to perform the task
|
# could take a while. 30 seconds should be enough to perform the task
|
||||||
socket_read_timeout = 30.0
|
socket_read_timeout = 30.0
|
||||||
|
@ -260,7 +263,7 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
files = []
|
files = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for no, attachment in enumerate(attach, start=1):
|
for no, attachment in enumerate(attach, start=1):
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -310,7 +313,10 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
|
|
||||||
if self.method == AppriseAPIMethod.JSON:
|
if self.method == AppriseAPIMethod.JSON:
|
||||||
headers['Content-Type'] = 'application/json'
|
headers['Content-Type'] = 'application/json'
|
||||||
|
|
||||||
|
if attachments:
|
||||||
payload['attachments'] = attachments
|
payload['attachments'] = attachments
|
||||||
|
|
||||||
payload = dumps(payload)
|
payload = dumps(payload)
|
||||||
|
|
||||||
if self.__tags:
|
if self.__tags:
|
||||||
|
|
|
@ -139,6 +139,18 @@ class NotifyBase(URLBase):
|
||||||
# Default Overflow Mode
|
# Default Overflow Mode
|
||||||
overflow_mode = OverflowMode.UPSTREAM
|
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
|
# Default Title HTML Tagging
|
||||||
# When a title is specified for a notification service that doesn't accept
|
# 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
|
# 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)
|
the_cors = (do_send(**kwargs2) for kwargs2 in send_calls)
|
||||||
return all(await asyncio.gather(*the_cors))
|
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,
|
notify_type=NotifyType.INFO, overflow=None,
|
||||||
attach=None, body_format=None, **kwargs):
|
attach=None, body_format=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -339,6 +351,28 @@ class NotifyBase(URLBase):
|
||||||
# bad attachments
|
# bad attachments
|
||||||
raise
|
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
|
# Handle situations where the title is None
|
||||||
title = '' if not title else title
|
title = '' if not title else title
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,9 @@ class NotifyDiscord(NotifyBase):
|
||||||
# Discord Webhook
|
# Discord Webhook
|
||||||
notify_url = 'https://discord.com/api/webhooks'
|
notify_url = 'https://discord.com/api/webhooks'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_256
|
image_size = NotifyImageSize.XY_256
|
||||||
|
|
||||||
|
@ -255,6 +258,18 @@ class NotifyDiscord(NotifyBase):
|
||||||
# Acquire image_url
|
# Acquire image_url
|
||||||
image_url = self.image_url(notify_type)
|
image_url = self.image_url(notify_type)
|
||||||
|
|
||||||
|
if self.avatar and (image_url or self.avatar_url):
|
||||||
|
payload['avatar_url'] = \
|
||||||
|
self.avatar_url if self.avatar_url else image_url
|
||||||
|
|
||||||
|
if self.user:
|
||||||
|
# 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 body:
|
||||||
# our fields variable
|
# our fields variable
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
@ -298,7 +313,8 @@ class NotifyDiscord(NotifyBase):
|
||||||
# Swap first entry for description
|
# Swap first entry for description
|
||||||
payload['embeds'][0]['description'] = description
|
payload['embeds'][0]['description'] = description
|
||||||
if fields:
|
if fields:
|
||||||
# Apply our additional parsing for a better presentation
|
# Apply our additional parsing for a better
|
||||||
|
# presentation
|
||||||
payload['embeds'][0]['fields'] = \
|
payload['embeds'][0]['fields'] = \
|
||||||
fields[:self.discord_max_fields]
|
fields[:self.discord_max_fields]
|
||||||
|
|
||||||
|
@ -310,15 +326,6 @@ class NotifyDiscord(NotifyBase):
|
||||||
payload['content'] = \
|
payload['content'] = \
|
||||||
body if not title else "{}\r\n{}".format(title, body)
|
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
|
|
||||||
|
|
||||||
if self.user:
|
|
||||||
# Optionally override the default username of the webhook
|
|
||||||
payload['username'] = self.user
|
|
||||||
|
|
||||||
params = {'thread_id': self.thread_id} if self.thread_id else None
|
|
||||||
if not self._send(payload, params=params):
|
if not self._send(payload, params=params):
|
||||||
# We failed to post our message
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
@ -333,7 +340,7 @@ class NotifyDiscord(NotifyBase):
|
||||||
# We failed to post our message
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# Update our payload; the idea is to preserve it's other detected
|
# Update our payload; the idea is to preserve it's other detected
|
||||||
# and assigned values for re-use here too
|
# and assigned values for re-use here too
|
||||||
payload.update({
|
payload.update({
|
||||||
|
@ -356,7 +363,7 @@ class NotifyDiscord(NotifyBase):
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'Posting Discord Attachment {}'.format(attachment.name))
|
'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
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -341,6 +341,9 @@ class NotifyEmail(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_email'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_email'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Default Notify Format
|
# Default Notify Format
|
||||||
notify_format = NotifyFormat.HTML
|
notify_format = NotifyFormat.HTML
|
||||||
|
|
||||||
|
@ -770,7 +773,7 @@ class NotifyEmail(NotifyBase):
|
||||||
else:
|
else:
|
||||||
base = MIMEText(body, 'plain', 'utf-8')
|
base = MIMEText(body, 'plain', 'utf-8')
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
mixed = MIMEMultipart("mixed")
|
mixed = MIMEMultipart("mixed")
|
||||||
mixed.attach(base)
|
mixed.attach(base)
|
||||||
# Now store our attachments
|
# Now store our attachments
|
||||||
|
|
|
@ -99,6 +99,9 @@ class NotifyForm(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_Form'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_Form'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
@ -345,7 +348,7 @@ class NotifyForm(NotifyBase):
|
||||||
|
|
||||||
# Track our potential attachments
|
# Track our potential attachments
|
||||||
files = []
|
files = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for no, attachment in enumerate(attach, start=1):
|
for no, attachment in enumerate(attach, start=1):
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
|
|
@ -80,6 +80,9 @@ class NotifyJSON(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_JSON'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_JSON'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
@ -289,7 +292,7 @@ class NotifyJSON(NotifyBase):
|
||||||
|
|
||||||
# Track our potential attachments
|
# Track our potential attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
|
|
@ -121,6 +121,9 @@ class NotifyMailgun(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mailgun'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mailgun'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Default Notify Format
|
# Default Notify Format
|
||||||
notify_format = NotifyFormat.HTML
|
notify_format = NotifyFormat.HTML
|
||||||
|
|
||||||
|
@ -371,7 +374,7 @@ class NotifyMailgun(NotifyBase):
|
||||||
# Track our potential files
|
# Track our potential files
|
||||||
files = {}
|
files = {}
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for idx, attachment in enumerate(attach):
|
for idx, attachment in enumerate(attach):
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
|
|
@ -111,6 +111,10 @@ class NotifyMastodon(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mastodon'
|
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
|
# Allows the user to specify the NotifyImageSize object; this is supported
|
||||||
# through the webhook
|
# through the webhook
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
@ -414,11 +418,10 @@ class NotifyMastodon(NotifyBase):
|
||||||
else:
|
else:
|
||||||
targets.add(myself)
|
targets.add(myself)
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
# We could not access the attachment
|
# We could not access the attachment
|
||||||
|
@ -578,7 +581,7 @@ class NotifyMastodon(NotifyBase):
|
||||||
_payload = deepcopy(payload)
|
_payload = deepcopy(payload)
|
||||||
_payload['media_ids'] = media_ids
|
_payload['media_ids'] = media_ids
|
||||||
|
|
||||||
if no:
|
if no or not body:
|
||||||
# strip text and replace it with the image representation
|
# strip text and replace it with the image representation
|
||||||
_payload['status'] = \
|
_payload['status'] = \
|
||||||
'{:02d}/{:02d}'.format(no + 1, len(batches))
|
'{:02d}/{:02d}'.format(no + 1, len(batches))
|
||||||
|
|
|
@ -172,6 +172,9 @@ class NotifyNtfy(NotifyBase):
|
||||||
# Default upstream/cloud host if none is defined
|
# Default upstream/cloud host if none is defined
|
||||||
cloud_notify_url = 'https://ntfy.sh'
|
cloud_notify_url = 'https://ntfy.sh'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_256
|
image_size = NotifyImageSize.XY_256
|
||||||
|
|
||||||
|
@ -405,14 +408,14 @@ class NotifyNtfy(NotifyBase):
|
||||||
# Retrieve our topic
|
# Retrieve our topic
|
||||||
topic = topics.pop()
|
topic = topics.pop()
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for no, attachment in enumerate(attach):
|
for no, attachment in enumerate(attach):
|
||||||
|
|
||||||
# First message only includes the text
|
# First message only includes the text (if defined)
|
||||||
_body = body if not no else None
|
_body = body if not no and body else None
|
||||||
_title = title if not no else None
|
_title = title if not no and title else None
|
||||||
|
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -543,11 +546,8 @@ class NotifyNtfy(NotifyBase):
|
||||||
# Default response type
|
# Default response type
|
||||||
response = None
|
response = None
|
||||||
|
|
||||||
if data:
|
if not attach:
|
||||||
data = data if attach else dumps(data)
|
data = dumps(data)
|
||||||
|
|
||||||
else: # not data:
|
|
||||||
data = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
|
|
|
@ -75,6 +75,9 @@ class NotifyPushBullet(NotifyBase):
|
||||||
# PushBullet uses the http protocol with JSON requests
|
# PushBullet uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.pushbullet.com/v2/{}'
|
notify_url = 'https://api.pushbullet.com/v2/{}'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{accesstoken}',
|
'{schema}://{accesstoken}',
|
||||||
|
@ -150,7 +153,7 @@ class NotifyPushBullet(NotifyBase):
|
||||||
# Build a list of our attachments
|
# Build a list of our attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
@ -261,6 +264,7 @@ class NotifyPushBullet(NotifyBase):
|
||||||
"PushBullet recipient {} parsed as a device"
|
"PushBullet recipient {} parsed as a device"
|
||||||
.format(recipient))
|
.format(recipient))
|
||||||
|
|
||||||
|
if body:
|
||||||
okay, response = self._send(
|
okay, response = self._send(
|
||||||
self.notify_url.format('pushes'), payload)
|
self.notify_url.format('pushes'), payload)
|
||||||
if not okay:
|
if not okay:
|
||||||
|
|
|
@ -336,6 +336,9 @@ class NotifyPushSafer(NotifyBase):
|
||||||
# The default secure protocol
|
# The default secure protocol
|
||||||
secure_protocol = 'psafers'
|
secure_protocol = 'psafers'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Number of requests to a allow per second
|
# Number of requests to a allow per second
|
||||||
request_rate_per_sec = 1.2
|
request_rate_per_sec = 1.2
|
||||||
|
|
||||||
|
@ -546,7 +549,7 @@ class NotifyPushSafer(NotifyBase):
|
||||||
# Initialize our list of attachments
|
# Initialize our list of attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
|
|
@ -164,6 +164,9 @@ class NotifyPushover(NotifyBase):
|
||||||
# Pushover uses the http protocol with JSON requests
|
# Pushover uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.pushover.net/1/messages.json'
|
notify_url = 'https://api.pushover.net/1/messages.json'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 1024
|
body_maxlen = 1024
|
||||||
|
|
||||||
|
@ -381,22 +384,25 @@ class NotifyPushover(NotifyBase):
|
||||||
if self.priority == PushoverPriority.EMERGENCY:
|
if self.priority == PushoverPriority.EMERGENCY:
|
||||||
payload.update({'retry': self.retry, 'expire': self.expire})
|
payload.update({'retry': self.retry, 'expire': self.expire})
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# Create a copy of our payload
|
# Create a copy of our payload
|
||||||
_payload = payload.copy()
|
_payload = payload.copy()
|
||||||
|
|
||||||
# Send with attachments
|
# Send with attachments
|
||||||
for attachment in attach:
|
for no, attachment in enumerate(attach):
|
||||||
# Simple send
|
if no or not body:
|
||||||
|
# To handle multiple attachments, clean up our message
|
||||||
|
_payload['message'] = attachment.name
|
||||||
|
|
||||||
if not self._send(_payload, attachment):
|
if not self._send(_payload, attachment):
|
||||||
# Mark our failure
|
# Mark our failure
|
||||||
has_error = True
|
has_error = True
|
||||||
# clean exit from our attachment loop
|
# clean exit from our attachment loop
|
||||||
break
|
break
|
||||||
|
|
||||||
# To handle multiple attachments, clean up our message
|
# Clear our title if previously set
|
||||||
_payload['title'] = '...'
|
_payload['title'] = ''
|
||||||
_payload['message'] = attachment.name
|
|
||||||
# No need to alarm for each consecutive attachment uploaded
|
# No need to alarm for each consecutive attachment uploaded
|
||||||
# afterwards
|
# afterwards
|
||||||
_payload['sound'] = PushoverSound.NONE
|
_payload['sound'] = PushoverSound.NONE
|
||||||
|
|
|
@ -136,6 +136,9 @@ class NotifySES(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_ses'
|
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
|
# AWS is pretty good for handling data load so request limits
|
||||||
# can occur in much shorter bursts
|
# can occur in much shorter bursts
|
||||||
request_rate_per_sec = 2.5
|
request_rate_per_sec = 2.5
|
||||||
|
@ -427,7 +430,8 @@ class NotifySES(NotifyBase):
|
||||||
content = MIMEText(body, 'plain', 'utf-8')
|
content = MIMEText(body, 'plain', 'utf-8')
|
||||||
|
|
||||||
# Create a Multipart container if there is an attachment
|
# 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`?
|
# TODO: Deduplicate with `NotifyEmail`?
|
||||||
base['Subject'] = Header(title, 'utf-8')
|
base['Subject'] = Header(title, 'utf-8')
|
||||||
|
@ -443,7 +447,7 @@ class NotifySES(NotifyBase):
|
||||||
timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")
|
timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||||
base['X-Application'] = self.app_id
|
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
|
# First attach our body to our content as the first element
|
||||||
base.attach(content)
|
base.attach(content)
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,9 @@ class NotifySMSEagle(NotifyBase):
|
||||||
# The path we send our notification to
|
# The path we send our notification to
|
||||||
notify_path = '/jsonrpc/sms'
|
notify_path = '/jsonrpc/sms'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# The maxumum length of the text message
|
# The maxumum length of the text message
|
||||||
# The actual limit is 160 but SMSEagle looks after the handling
|
# The actual limit is 160 but SMSEagle looks after the handling
|
||||||
# of large messages in it's upstream service
|
# of large messages in it's upstream service
|
||||||
|
@ -340,7 +343,7 @@ class NotifySMSEagle(NotifyBase):
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
|
|
@ -91,6 +91,9 @@ class NotifySMTP2Go(NotifyBase):
|
||||||
# Notify URL
|
# Notify URL
|
||||||
notify_url = 'https://api.smtp2go.com/v3/email/send'
|
notify_url = 'https://api.smtp2go.com/v3/email/send'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Default Notify Format
|
# Default Notify Format
|
||||||
notify_format = NotifyFormat.HTML
|
notify_format = NotifyFormat.HTML
|
||||||
|
|
||||||
|
@ -294,8 +297,8 @@ class NotifySMTP2Go(NotifyBase):
|
||||||
# Track our potential attachments
|
# Track our potential attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for idx, attachment in enumerate(attach):
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
# We could not access the attachment
|
# We could not access the attachment
|
||||||
|
|
|
@ -68,6 +68,9 @@ class NotifySignalAPI(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_signal'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_signal'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# The maximum targets to include when doing batch transfers
|
# The maximum targets to include when doing batch transfers
|
||||||
default_batch_size = 10
|
default_batch_size = 10
|
||||||
|
|
||||||
|
@ -234,7 +237,7 @@ class NotifySignalAPI(NotifyBase):
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -281,7 +284,7 @@ class NotifySignalAPI(NotifyBase):
|
||||||
payload = {
|
payload = {
|
||||||
'message': "{}{}".format(
|
'message': "{}{}".format(
|
||||||
'' if not self.status else '{} '.format(
|
'' if not self.status else '{} '.format(
|
||||||
self.asset.ascii(notify_type)), body),
|
self.asset.ascii(notify_type)), body).rstrip(),
|
||||||
"number": self.source,
|
"number": self.source,
|
||||||
"recipients": []
|
"recipients": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,10 @@ class NotifySlack(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_slack'
|
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
|
# Slack Webhook URL
|
||||||
webhook_url = 'https://hooks.slack.com/services'
|
webhook_url = 'https://hooks.slack.com/services'
|
||||||
|
|
||||||
|
@ -522,7 +526,8 @@ class NotifySlack(NotifyBase):
|
||||||
# Include the footer only if specified to do so
|
# Include the footer only if specified to do so
|
||||||
payload['attachments'][0]['footer'] = self.app_id
|
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
|
# Be friendly; let the user know why they can't send their
|
||||||
# attachments if using the Webhook mode
|
# attachments if using the Webhook mode
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
|
@ -600,7 +605,8 @@ class NotifySlack(NotifyBase):
|
||||||
' to {}'.format(channel)
|
' to {}'.format(channel)
|
||||||
if channel is not None else ''))
|
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)
|
# Send our attachments (can only be done in bot mode)
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,9 @@ class NotifySparkPost(NotifyBase):
|
||||||
# The services URL
|
# The services URL
|
||||||
service_url = 'https://sparkpost.com/'
|
service_url = 'https://sparkpost.com/'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# All notification requests are secure
|
# All notification requests are secure
|
||||||
secure_protocol = 'sparkpost'
|
secure_protocol = 'sparkpost'
|
||||||
|
|
||||||
|
@ -543,7 +546,7 @@ class NotifySparkPost(NotifyBase):
|
||||||
else:
|
else:
|
||||||
payload['content']['text'] = body
|
payload['content']['text'] = body
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# Prepare ourselves an attachment object
|
# Prepare ourselves an attachment object
|
||||||
payload['content']['attachments'] = []
|
payload['content']['attachments'] = []
|
||||||
|
|
||||||
|
|
|
@ -277,8 +277,7 @@ class NotifyStreamlabs(NotifyBase):
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
**kwargs):
|
|
||||||
"""
|
"""
|
||||||
Perform Streamlabs notification call (either donation or alert)
|
Perform Streamlabs notification call (either donation or alert)
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -123,6 +123,9 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Telegram uses the http protocol with JSON requests
|
# Telegram uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.telegram.org/bot'
|
notify_url = 'https://api.telegram.org/bot'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_256
|
image_size = NotifyImageSize.XY_256
|
||||||
|
|
||||||
|
@ -715,6 +718,10 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Prepare our payload based on HTML or TEXT
|
# Prepare our payload based on HTML or TEXT
|
||||||
payload['text'] = body
|
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
|
# Create a copy of the chat_ids list
|
||||||
targets = list(self.targets)
|
targets = list(self.targets)
|
||||||
while len(targets):
|
while len(targets):
|
||||||
|
@ -748,7 +755,8 @@ class NotifyTelegram(NotifyBase):
|
||||||
'Failed to send Telegram type image to {}.',
|
'Failed to send Telegram type image to {}.',
|
||||||
payload['chat_id'])
|
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)
|
# Send our attachments now (if specified and if it exists)
|
||||||
if not self._send_attachments(
|
if not self._send_attachments(
|
||||||
chat_id=payload['chat_id'], notify_type=notify_type,
|
chat_id=payload['chat_id'], notify_type=notify_type,
|
||||||
|
@ -757,6 +765,10 @@ class NotifyTelegram(NotifyBase):
|
||||||
has_error = True
|
has_error = True
|
||||||
continue
|
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;
|
# Always call throttle before any remote server i/o is made;
|
||||||
# Telegram throttles to occur before sending the image so that
|
# Telegram throttles to occur before sending the image so that
|
||||||
# content can arrive together.
|
# content can arrive together.
|
||||||
|
@ -819,7 +831,8 @@ class NotifyTelegram(NotifyBase):
|
||||||
|
|
||||||
self.logger.info('Sent Telegram notification.')
|
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
|
# Send our attachments now (if specified and if it exists) as
|
||||||
# it was identified to send the content before the attachments
|
# it was identified to send the content before the attachments
|
||||||
# which is now done.
|
# which is now done.
|
||||||
|
|
|
@ -88,6 +88,9 @@ class NotifyTwitter(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_twitter'
|
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
|
# 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
|
# since the length varies depending if we are doing a direct message
|
||||||
# or a tweet
|
# or a tweet
|
||||||
|
@ -285,7 +288,7 @@ class NotifyTwitter(NotifyBase):
|
||||||
# Build a list of our attachments
|
# Build a list of our attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
@ -414,7 +417,7 @@ class NotifyTwitter(NotifyBase):
|
||||||
_payload = deepcopy(payload)
|
_payload = deepcopy(payload)
|
||||||
_payload['media_ids'] = media_ids
|
_payload['media_ids'] = media_ids
|
||||||
|
|
||||||
if no:
|
if no or not body:
|
||||||
# strip text and replace it with the image representation
|
# strip text and replace it with the image representation
|
||||||
_payload['status'] = \
|
_payload['status'] = \
|
||||||
'{:02d}/{:02d}'.format(no + 1, len(batches))
|
'{:02d}/{:02d}'.format(no + 1, len(batches))
|
||||||
|
@ -514,7 +517,7 @@ class NotifyTwitter(NotifyBase):
|
||||||
'additional_owners':
|
'additional_owners':
|
||||||
','.join([str(x) for x in targets.values()])
|
','.join([str(x) for x in targets.values()])
|
||||||
}
|
}
|
||||||
if no:
|
if no or not body:
|
||||||
# strip text and replace it with the image representation
|
# strip text and replace it with the image representation
|
||||||
_data['text'] = \
|
_data['text'] = \
|
||||||
'{:02d}/{:02d}'.format(no + 1, len(attachments))
|
'{:02d}/{:02d}'.format(no + 1, len(attachments))
|
||||||
|
|
|
@ -79,6 +79,9 @@ class NotifyXML(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_XML'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_XML'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
@ -340,7 +343,7 @@ class NotifyXML(NotifyBase):
|
||||||
['<{}>{}</{}>'.format(k, v, k) for k, v in payload_base.items()])
|
['<{}>{}</{}>'.format(k, v, k) for k, v in payload_base.items()])
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
|
|
@ -14,6 +14,13 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/apprise
|
- ./:/apprise
|
||||||
|
|
||||||
|
test.py311:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.py311
|
||||||
|
volumes:
|
||||||
|
- ./:/apprise
|
||||||
|
|
||||||
rpmbuild.el8:
|
rpmbuild.el8:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
@ -36,6 +43,17 @@ services:
|
||||||
- ./:/apprise
|
- ./:/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
|
# Connect to web and create a new project using the manage script
|
||||||
# -> docker-compose run --rm test.py36 bash
|
# -> docker-compose run --rm test.py36 bash
|
||||||
# bin/apprise -
|
# bin/apprise -
|
||||||
|
|
|
@ -477,6 +477,52 @@ class AppriseURLTester:
|
||||||
notify_type=notify_type,
|
notify_type=notify_type,
|
||||||
attach=attach) == attach_response
|
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:
|
else:
|
||||||
|
|
||||||
for _exception in self.req_exceptions:
|
for _exception in self.req_exceptions:
|
||||||
|
|
|
@ -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=object(), body=b'bytes') is False
|
||||||
assert do_notify(a, title=b"bytes", body=object()) 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=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
|
assert do_notify(a, title="present", body="present") is True
|
||||||
|
|
||||||
# Send Attachment with success
|
# Send Attachment with success
|
||||||
|
|
|
@ -170,7 +170,13 @@ def test_plugin_boxcar_edge_cases(mock_post, mock_get):
|
||||||
# Test notifications without a body or a title
|
# Test notifications without a body or a title
|
||||||
p = NotifyBoxcar(access=access, secret=secret, targets=None)
|
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
|
# Test comma, separate values
|
||||||
device = 'a' * 64
|
device = 'a' * 64
|
||||||
|
|
Loading…
Reference in New Issue