diff --git a/apprise/attachment/base.py b/apprise/attachment/base.py
index 11b7f8c4..a0f74f9a 100644
--- a/apprise/attachment/base.py
+++ b/apprise/attachment/base.py
@@ -291,7 +291,7 @@ class AttachBase(URLBase):
return False if not retrieve_if_missing else self.download()
- def base64(self, encoding='utf-8'):
+ def base64(self, encoding='ascii'):
"""
Returns the attachment object as a base64 string otherwise
None is returned if an error occurs.
@@ -306,7 +306,7 @@ class AttachBase(URLBase):
raise exception.AppriseFileNotFound("Attachment Missing")
try:
- with open(self.path, 'rb') as f:
+ with self.open() as f:
# Prepare our Attachment in Base64
return base64.b64encode(f.read()).decode(encoding) \
if encoding else base64.b64encode(f.read())
diff --git a/apprise/attachment/memory.py b/apprise/attachment/memory.py
index 94645f26..c7d5dca2 100644
--- a/apprise/attachment/memory.py
+++ b/apprise/attachment/memory.py
@@ -29,7 +29,9 @@
import re
import os
import io
+import base64
from .base import AttachBase
+from .. import exception
from ..common import ContentLocation
from ..locale import gettext_lazy as _
import uuid
@@ -145,6 +147,23 @@ class AttachMemory(AttachBase):
return True
+ def base64(self, encoding='ascii'):
+ """
+ We need to over-ride this since the base64 sub-library seems to close
+ our file descriptor making it no longer referencable.
+ """
+
+ if not self:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access attachment {}.'.format(
+ self.url(privacy=True)))
+ raise exception.AppriseFileNotFound("Attachment Missing")
+ self._data.seek(0, 0)
+
+ return base64.b64encode(self._data.read()).decode(encoding) \
+ if encoding else base64.b64encode(self._data.read())
+
def invalidate(self):
"""
Removes data
diff --git a/apprise/plugins/apprise_api.py b/apprise/plugins/apprise_api.py
index fd71236b..d6156438 100644
--- a/apprise/plugins/apprise_api.py
+++ b/apprise/plugins/apprise_api.py
@@ -29,8 +29,8 @@
import re
import requests
from json import dumps
-import base64
+from .. import exception
from .base import NotifyBase
from ..url import PrivacyMode
from ..common import NotifyType
@@ -261,39 +261,45 @@ class NotifyAppriseAPI(NotifyBase):
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access Apprise API attachment {}.'.format(
attachment.url(privacy=True)))
return False
try:
+ # Our Attachment filename
+ filename = attachment.name \
+ if attachment.name else f'file{no:03}.dat'
+
if self.method == AppriseAPIMethod.JSON:
- with open(attachment.path, 'rb') as f:
- # Output must be in a DataURL format (that's what
- # PushSafer calls it):
- attachments.append({
- 'filename': attachment.name,
- 'base64': base64.b64encode(f.read())
- .decode('utf-8'),
- 'mimetype': attachment.mimetype,
- })
+ # Output must be in a DataURL format (that's what
+ # PushSafer calls it):
+ attachments.append({
+ "filename": filename,
+ 'base64': attachment.base64(),
+ 'mimetype': attachment.mimetype,
+ })
else: # AppriseAPIMethod.FORM
files.append((
'file{:02d}'.format(no),
(
- attachment.name,
+ filename,
open(attachment.path, 'rb'),
attachment.mimetype,
)
))
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ except (TypeError, OSError, exception.AppriseException):
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access AppriseAPI attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending AppriseAPI attachment {}'.format(
+ attachment.url(privacy=True)))
+
# prepare Apprise API Object
payload = {
# Apprise API Payload
diff --git a/apprise/plugins/custom_form.py b/apprise/plugins/custom_form.py
index 05fe51d1..e9ffcbbb 100644
--- a/apprise/plugins/custom_form.py
+++ b/apprise/plugins/custom_form.py
@@ -302,7 +302,8 @@ class NotifyForm(NotifyBase):
files.append((
self.attach_as.format(no)
if self.attach_multi_support else self.attach_as, (
- attachment.name,
+ attachment.name
+ if attachment.name else f'file{no:03}.dat',
open(attachment.path, 'rb'),
attachment.mimetype)
))
diff --git a/apprise/plugins/custom_json.py b/apprise/plugins/custom_json.py
index 25b4467d..03585c9e 100644
--- a/apprise/plugins/custom_json.py
+++ b/apprise/plugins/custom_json.py
@@ -27,9 +27,9 @@
# POSSIBILITY OF SUCH DAMAGE.
import requests
-import base64
from json import dumps
+from .. import exception
from .base import NotifyBase
from ..url import PrivacyMode
from ..common import NotifyImageSize
@@ -213,33 +213,34 @@ class NotifyJSON(NotifyBase):
# Track our potential attachments
attachments = []
if attach and self.attachment_support:
- for attachment in attach:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access Custom JSON attachment {}.'.format(
attachment.url(privacy=True)))
return False
try:
- with open(attachment.path, 'rb') as f:
- # Output must be in a DataURL format (that's what
- # PushSafer calls it):
- attachments.append({
- 'filename': attachment.name,
- 'base64': base64.b64encode(f.read())
- .decode('utf-8'),
- 'mimetype': attachment.mimetype,
- })
-
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ attachments.append({
+ "filename": attachment.name
+ if attachment.name else f'file{no:03}.dat',
+ 'base64': attachment.base64(),
+ 'mimetype': attachment.mimetype,
+ })
+
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access Custom JSON attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending Custom JSON attachment {}'.format(
+ attachment.url(privacy=True)))
+
# Prepare JSON Object
payload = {
JSONPayloadField.VERSION: self.json_version,
diff --git a/apprise/plugins/custom_xml.py b/apprise/plugins/custom_xml.py
index f72e9a1a..8bfff3ec 100644
--- a/apprise/plugins/custom_xml.py
+++ b/apprise/plugins/custom_xml.py
@@ -28,8 +28,8 @@
import re
import requests
-import base64
+from .. import exception
from .base import NotifyBase
from ..url import PrivacyMode
from ..common import NotifyImageSize
@@ -287,35 +287,39 @@ class NotifyXML(NotifyBase):
attachments = []
if attach and self.attachment_support:
- for attachment in attach:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access Custom XML attachment {}.'.format(
attachment.url(privacy=True)))
return False
try:
- with open(attachment.path, 'rb') as f:
- # Prepare our Attachment in Base64
- entry = \
- ''.format(
- NotifyXML.escape_html(
- attachment.name, whitespace=False),
- NotifyXML.escape_html(
- attachment.mimetype, whitespace=False))
- entry += base64.b64encode(f.read()).decode('utf-8')
- entry += ''
- attachments.append(entry)
-
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ # Prepare our Attachment in Base64
+ entry = \
+ ''.format(
+ NotifyXML.escape_html(
+ attachment.name if attachment.name
+ else f'file{no:03}.dat', whitespace=False),
+ NotifyXML.escape_html(
+ attachment.mimetype, whitespace=False))
+ entry += attachment.base64()
+ entry += ''
+ attachments.append(entry)
+
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access Custom XML attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending Custom XML attachment {}'.format(
+ attachment.url(privacy=True)))
+
# Update our xml_attachments record:
xml_attachments = \
'' + \
diff --git a/apprise/plugins/email.py b/apprise/plugins/email.py
index f720c426..2e423916 100644
--- a/apprise/plugins/email.py
+++ b/apprise/plugins/email.py
@@ -799,7 +799,7 @@ class NotifyEmail(NotifyBase):
mixed = MIMEMultipart("mixed")
mixed.attach(base)
# Now store our attachments
- for attachment in attach:
+ for no, attachment in enumerate(attach, start=1):
if not attachment:
# We could not load the attachment; take an early
# exit since this isn't what the end user wanted
@@ -819,10 +819,14 @@ class NotifyEmail(NotifyBase):
app = MIMEApplication(abody.read())
app.set_type(attachment.mimetype)
+ # Prepare our attachment name
+ filename = attachment.name \
+ if attachment.name else f'file{no:03}.dat'
+
app.add_header(
'Content-Disposition',
'attachment; filename="{}"'.format(
- Header(attachment.name, 'utf-8')),
+ Header(filename, 'utf-8')),
)
mixed.attach(app)
base = mixed
diff --git a/apprise/plugins/mailgun.py b/apprise/plugins/mailgun.py
index 4b73957a..b6818395 100644
--- a/apprise/plugins/mailgun.py
+++ b/apprise/plugins/mailgun.py
@@ -383,9 +383,15 @@ class NotifyMailgun(NotifyBase):
self.logger.debug(
'Preparing Mailgun attachment {}'.format(
attachment.url(privacy=True)))
+
+ # Prepare our filename
+ filename = attachment.name \
+ if attachment.name \
+ else 'file{no:03}.dat'.format(no=idx + 1)
+
try:
files['attachment[{}]'.format(idx)] = \
- (attachment.name, open(attachment.path, 'rb'))
+ (filename, open(attachment.path, 'rb'))
except (OSError, IOError) as e:
self.logger.warning(
diff --git a/apprise/plugins/pushbullet.py b/apprise/plugins/pushbullet.py
index 9f2226f3..2b88bbed 100644
--- a/apprise/plugins/pushbullet.py
+++ b/apprise/plugins/pushbullet.py
@@ -152,7 +152,7 @@ class NotifyPushBullet(NotifyBase):
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:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
@@ -168,7 +168,8 @@ class NotifyPushBullet(NotifyBase):
# prepare payload
payload = {
- 'file_name': attachment.name,
+ 'file_name': attachment.name
+ if attachment.name else f'file{no:03}.dat',
'file_type': attachment.mimetype,
}
# First thing we need to do is make a request so that we can
diff --git a/apprise/plugins/pushsafer.py b/apprise/plugins/pushsafer.py
index 7d4052c0..dd5a6c82 100644
--- a/apprise/plugins/pushsafer.py
+++ b/apprise/plugins/pushsafer.py
@@ -26,11 +26,11 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-import base64
import requests
from json import loads
from .base import NotifyBase
+from .. import exception
from ..common import NotifyType
from ..utils import parse_list
from ..utils import validate_regex
@@ -548,12 +548,12 @@ class NotifyPushSafer(NotifyBase):
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:
+ for no, attachment in enumerate(attach, start=1):
# prepare payload
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access PushSafer attachment {}.'.format(
attachment.url(privacy=True)))
return False
@@ -569,24 +569,27 @@ class NotifyPushSafer(NotifyBase):
attachment.url(privacy=True)))
try:
- with open(attachment.path, 'rb') as f:
- # Output must be in a DataURL format (that's what
- # PushSafer calls it):
- attachment = (
- attachment.name,
- 'data:{};base64,{}'.format(
- attachment.mimetype,
- base64.b64encode(f.read())))
-
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ # Output must be in a DataURL format (that's what
+ # PushSafer calls it):
+ attachments.append((
+ attachment.name
+ if attachment.name else f'file{no:03}.dat',
+ 'data:{};base64,{}'.format(
+ attachment.mimetype,
+ attachment.base64(),
+ )
+ ))
+
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access PushSafer attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
- # Save our pre-prepared payload for attachment posting
- attachments.append(attachment)
+ self.logger.debug(
+ 'Appending PushSafer attachment {}'.format(
+ attachment.url(privacy=True)))
# Create a copy of the targets list
targets = list(self.targets)
diff --git a/apprise/plugins/sendgrid.py b/apprise/plugins/sendgrid.py
index 627815c7..b4f92e9f 100644
--- a/apprise/plugins/sendgrid.py
+++ b/apprise/plugins/sendgrid.py
@@ -342,11 +342,19 @@ class NotifySendGrid(NotifyBase):
# Send our attachments
for no, attachment in enumerate(attach, start=1):
+ # Perform some simple error checking
+ if not attachment:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access SendGrid attachment {}.'.format(
+ attachment.url(privacy=True)))
+ return False
+
try:
attachments.append({
"content": attachment.base64(),
"filename": attachment.name
- if attachment.name else f'attach{no:03}.dat',
+ if attachment.name else f'file{no:03}.dat',
"type": "application/octet-stream",
"disposition": "attachment"
})
@@ -354,7 +362,7 @@ class NotifySendGrid(NotifyBase):
except exception.AppriseException:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access SendGrid attachment {}.'.format(
attachment.url(privacy=True)))
return False
diff --git a/apprise/plugins/ses.py b/apprise/plugins/ses.py
index 5fe4a369..30e59702 100644
--- a/apprise/plugins/ses.py
+++ b/apprise/plugins/ses.py
@@ -448,7 +448,7 @@ class NotifySES(NotifyBase):
base.attach(content)
# Now store our attachments
- for attachment in attach:
+ for no, attachment in enumerate(attach, start=1):
if not attachment:
# We could not load the attachment; take an early
# exit since this isn't what the end user wanted
@@ -468,10 +468,13 @@ class NotifySES(NotifyBase):
app = MIMEApplication(abody.read())
app.set_type(attachment.mimetype)
+ filename = attachment.name \
+ if attachment.name else f'file{no:03}.dat'
+
app.add_header(
'Content-Disposition',
'attachment; filename="{}"'.format(
- Header(attachment.name, 'utf-8')),
+ Header(filename, 'utf-8')),
)
base.attach(app)
diff --git a/apprise/plugins/signal_api.py b/apprise/plugins/signal_api.py
index 5795e0cf..9a019123 100644
--- a/apprise/plugins/signal_api.py
+++ b/apprise/plugins/signal_api.py
@@ -29,10 +29,10 @@
import re
import requests
from json import dumps
-import base64
from .base import NotifyBase
from ..common import NotifyType
+from .. import exception
from ..utils import is_phone_no
from ..utils import parse_phone_no
from ..utils import parse_bool
@@ -239,23 +239,24 @@ class NotifySignalAPI(NotifyBase):
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access Signal API attachment {}.'.format(
attachment.url(privacy=True)))
return False
try:
- with open(attachment.path, 'rb') as f:
- # Prepare our Attachment in Base64
- attachments.append(
- base64.b64encode(f.read()).decode('utf-8'))
+ attachments.append(attachment.base64())
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access Signal API attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending Signal API attachment {}'.format(
+ attachment.url(privacy=True)))
+
# Prepare our headers
headers = {
'User-Agent': self.app_id,
diff --git a/apprise/plugins/slack.py b/apprise/plugins/slack.py
index fb4a8b6e..11d78e4c 100644
--- a/apprise/plugins/slack.py
+++ b/apprise/plugins/slack.py
@@ -646,7 +646,7 @@ class NotifySlack(NotifyBase):
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:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
@@ -663,7 +663,8 @@ class NotifySlack(NotifyBase):
# Get the URL to which to upload the file.
# https://api.slack.com/methods/files.getUploadURLExternal
_params = {
- 'filename': attachment.name,
+ 'filename': attachment.name
+ if attachment.name else f'file{no:03}.dat',
'length': len(attachment),
}
_url = self.api_url.format('files.getUploadURLExternal')
diff --git a/apprise/plugins/smseagle.py b/apprise/plugins/smseagle.py
index 81308345..15943310 100644
--- a/apprise/plugins/smseagle.py
+++ b/apprise/plugins/smseagle.py
@@ -29,11 +29,11 @@
import re
import requests
from json import dumps, loads
-import base64
from itertools import chain
from .base import NotifyBase
from ..common import NotifyType
+from .. import exception
from ..utils import validate_regex
from ..utils import is_phone_no
from ..utils import parse_phone_no
@@ -345,7 +345,7 @@ class NotifySMSEagle(NotifyBase):
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access SMSEagle attachment {}.'.format(
attachment.url(privacy=True)))
return False
@@ -357,21 +357,23 @@ class NotifySMSEagle(NotifyBase):
continue
try:
- with open(attachment.path, 'rb') as f:
- # Prepare our Attachment in Base64
- attachments.append({
- 'content_type': attachment.mimetype,
- 'content': base64.b64encode(
- f.read()).decode('utf-8'),
- })
-
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ # Prepare our Attachment in Base64
+ attachments.append({
+ 'content_type': attachment.mimetype,
+ 'content': attachment.base64(),
+ })
+
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access SMSEagle attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending SMSEagle attachment {}'.format(
+ attachment.url(privacy=True)))
+
# Prepare our headers
headers = {
'User-Agent': self.app_id,
diff --git a/apprise/plugins/smtp2go.py b/apprise/plugins/smtp2go.py
index cb8c71ff..a19e523f 100644
--- a/apprise/plugins/smtp2go.py
+++ b/apprise/plugins/smtp2go.py
@@ -45,11 +45,11 @@
# the email will be transmitted from. If no email address is specified
# then it will also become the 'to' address as well.
#
-import base64
import requests
from json import dumps
from email.utils import formataddr
from .base import NotifyBase
+from .. import exception
from ..common import NotifyType
from ..common import NotifyFormat
from ..utils import parse_emails
@@ -294,33 +294,35 @@ class NotifySMTP2Go(NotifyBase):
attachments = []
if attach and self.attachment_support:
- for attachment in attach:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access SMTP2Go attachment {}.'.format(
attachment.url(privacy=True)))
return False
try:
- with open(attachment.path, 'rb') as f:
- # Output must be in a DataURL format (that's what
- # PushSafer calls it):
- attachments.append({
- 'filename': attachment.name,
- 'fileblob': base64.b64encode(f.read())
- .decode('utf-8'),
- 'mimetype': attachment.mimetype,
- })
-
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ # Format our attachment
+ attachments.append({
+ 'filename': attachment.name
+ if attachment.name else f'file{no:03}.dat',
+ 'fileblob': attachment.base64(),
+ 'mimetype': attachment.mimetype,
+ })
+
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access SMTP2Go attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending SMTP2Go attachment {}'.format(
+ attachment.url(privacy=True)))
+
sender = formataddr(
(self.from_name if self.from_name else False,
self.from_addr), charset='utf-8')
diff --git a/apprise/plugins/sparkpost.py b/apprise/plugins/sparkpost.py
index b1fb7bca..4e4233ca 100644
--- a/apprise/plugins/sparkpost.py
+++ b/apprise/plugins/sparkpost.py
@@ -55,10 +55,10 @@
# API Documentation: https://developers.sparkpost.com/api/
# Specifically: https://developers.sparkpost.com/api/transmissions/
import requests
-import base64
from json import loads
from json import dumps
from .base import NotifyBase
+from .. import exception
from ..common import NotifyType
from ..common import NotifyFormat
from ..utils import is_email
@@ -500,7 +500,7 @@ class NotifySparkPost(NotifyBase):
if not self.targets:
# There is no one to email; we're done
self.logger.warning(
- 'There are no Email recipients to notify')
+ 'There are no SparkPost Email recipients to notify')
return False
# Initialize our has_error flag
@@ -546,35 +546,35 @@ class NotifySparkPost(NotifyBase):
# Prepare ourselves an attachment object
payload['content']['attachments'] = []
- for attachment in attach:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
# We could not access the attachment
self.logger.error(
- 'Could not access attachment {}.'.format(
+ 'Could not access SparkPost attachment {}.'.format(
attachment.url(privacy=True)))
return False
- self.logger.debug(
- 'Preparing SparkPost attachment {}'.format(
- attachment.url(privacy=True)))
-
try:
- with open(attachment.path, 'rb') as fp:
- # Prepare API Upload Payload
- payload['content']['attachments'].append({
- 'name': attachment.name,
- 'type': attachment.mimetype,
- 'data': base64.b64encode(fp.read()).decode("ascii")
- })
-
- except (OSError, IOError) as e:
- self.logger.warning(
- 'An I/O error occurred while reading {}.'.format(
- attachment.name if attachment else 'attachment'))
- self.logger.debug('I/O Exception: %s' % str(e))
+ # Prepare API Upload Payload
+ payload['content']['attachments'].append({
+ 'name': attachment.name
+ if attachment.name else f'file{no:03}.dat',
+ 'type': attachment.mimetype,
+ 'data': attachment.base64(),
+ })
+
+ except exception.AppriseException:
+ # We could not access the attachment
+ self.logger.error(
+ 'Could not access SparkPost attachment {}.'.format(
+ attachment.url(privacy=True)))
return False
+ self.logger.debug(
+ 'Appending SparkPost attachment {}'.format(
+ attachment.url(privacy=True)))
+
# Take a copy of our token dictionary
tokens = self.tokens.copy()
diff --git a/apprise/plugins/twitter.py b/apprise/plugins/twitter.py
index 369aaac0..6d352ea6 100644
--- a/apprise/plugins/twitter.py
+++ b/apprise/plugins/twitter.py
@@ -287,7 +287,7 @@ class NotifyTwitter(NotifyBase):
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:
+ for no, attachment in enumerate(attach, start=1):
# Perform some simple error checking
if not attachment:
@@ -320,11 +320,15 @@ class NotifyTwitter(NotifyBase):
# We can't post our attachment
return False
+ # Prepare our filename
+ filename = attachment.name \
+ if attachment.name else f'file{no:03}.dat'
+
if not (isinstance(response, dict)
and response.get('media_id')):
self.logger.debug(
'Could not attach the file to Twitter: %s (mime=%s)',
- attachment.name, attachment.mimetype)
+ filename, attachment.mimetype)
continue
# If we get here, our output will look something like this:
@@ -344,7 +348,7 @@ class NotifyTwitter(NotifyBase):
response.update({
# Update our response to additionally include the
# attachment details
- 'file_name': attachment.name,
+ 'file_name': filename,
'file_mime': attachment.mimetype,
'file_path': attachment.path,
})
diff --git a/test/test_attach_http.py b/test/test_attach_http.py
index 36ecbad5..6caf8d62 100644
--- a/test/test_attach_http.py
+++ b/test/test_attach_http.py
@@ -29,6 +29,8 @@
import re
from unittest import mock
+import pytest
+from apprise import exception
import requests
import mimetypes
from os.path import join
@@ -481,3 +483,33 @@ def test_attach_http(mock_get, mock_post):
assert mock_post.call_count == 30
# We only fetched once and re-used the same fetch for all posts
assert mock_get.call_count == 1
+
+ #
+ # We will test our base64 handling now
+ #
+ mock_get.reset_mock()
+ mock_post.reset_mock()
+
+ AttachHTTP.max_file_size = getsize(path)
+ # Set ourselves a Content-Disposition (providing a filename)
+ dummy_response.headers['Content-Disposition'] = \
+ 'attachment; filename="myimage.gif"'
+ results = AttachHTTP.parse_url('http://user@localhost/filename.gif')
+ assert isinstance(results, dict)
+ obj = AttachHTTP(**results)
+
+ # now test our base64 output
+ assert isinstance(obj.base64(), str)
+ # No encoding if we choose
+ assert isinstance(obj.base64(encoding=None), bytes)
+
+ # Error cases:
+ with mock.patch("builtins.open", new_callable=mock.mock_open,
+ read_data="mocked file content") as mock_file:
+ mock_file.side_effect = FileNotFoundError
+ with pytest.raises(exception.AppriseFileNotFound):
+ obj.base64()
+
+ mock_file.side_effect = OSError
+ with pytest.raises(exception.AppriseDiskIOError):
+ obj.base64()
diff --git a/test/test_attach_memory.py b/test/test_attach_memory.py
index a4bec417..d5f4c52d 100644
--- a/test/test_attach_memory.py
+++ b/test/test_attach_memory.py
@@ -30,6 +30,7 @@ import re
import urllib
import pytest
+from apprise import exception
from apprise.attachment.base import AttachBase
from apprise.attachment.memory import AttachMemory
from apprise import AppriseAttachment
@@ -203,3 +204,12 @@ def test_attach_memory():
# Test hosted configuration and that we can't add a valid memory file
aa = AppriseAttachment(location=ContentLocation.HOSTED)
assert aa.add(response) is False
+
+ # now test our base64 output
+ assert isinstance(response.base64(), str)
+ # No encoding if we choose
+ assert isinstance(response.base64(encoding=None), bytes)
+
+ response.invalidate()
+ with pytest.raises(exception.AppriseFileNotFound):
+ response.base64()
diff --git a/test/test_plugin_sendgrid.py b/test/test_plugin_sendgrid.py
index 4c775ca0..eb48befa 100644
--- a/test/test_plugin_sendgrid.py
+++ b/test/test_plugin_sendgrid.py
@@ -196,6 +196,12 @@ def test_plugin_sendgrid_attachments(mock_post, mock_get):
mock_post.reset_mock()
mock_get.reset_mock()
+ # Try again in a use case where we can't access the file
+ with mock.patch("os.path.isfile", return_value=False):
+ assert obj.notify(
+ body='body', title='title', notify_type=NotifyType.INFO,
+ attach=attach) is False
+
# Try again in a use case where we can't access the file
with mock.patch("builtins.open", side_effect=OSError):
assert obj.notify(