mirror of
https://github.com/caronc/apprise.git
synced 2024-11-21 23:53:23 +01:00
Refactored base64 attachment handling (#1191)
This commit is contained in:
parent
ca50cb7820
commit
98fb4865fc
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
))
|
||||
|
@ -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,
|
||||
})
|
||||
attachments.append({
|
||||
"filename": attachment.name
|
||||
if attachment.name else f'file{no:03}.dat',
|
||||
'base64': attachment.base64(),
|
||||
'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))
|
||||
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,
|
||||
|
@ -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 = \
|
||||
'<Attachment filename="{}" mimetype="{}">'.format(
|
||||
NotifyXML.escape_html(
|
||||
attachment.name, whitespace=False),
|
||||
NotifyXML.escape_html(
|
||||
attachment.mimetype, whitespace=False))
|
||||
entry += base64.b64encode(f.read()).decode('utf-8')
|
||||
entry += '</Attachment>'
|
||||
attachments.append(entry)
|
||||
# Prepare our Attachment in Base64
|
||||
entry = \
|
||||
'<Attachment filename="{}" mimetype="{}">'.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 += '</Attachment>'
|
||||
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))
|
||||
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 = \
|
||||
'<Attachments format="base64">' + \
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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())))
|
||||
# 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 (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 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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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')
|
||||
|
@ -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'),
|
||||
})
|
||||
# Prepare our Attachment in Base64
|
||||
attachments.append({
|
||||
'content_type': attachment.mimetype,
|
||||
'content': 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 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,
|
||||
|
@ -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,
|
||||
})
|
||||
# Format our attachment
|
||||
attachments.append({
|
||||
'filename': attachment.name
|
||||
if attachment.name else f'file{no:03}.dat',
|
||||
'fileblob': attachment.base64(),
|
||||
'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))
|
||||
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')
|
||||
|
@ -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
|
||||
|
||||
try:
|
||||
# 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(
|
||||
'Preparing SparkPost attachment {}'.format(
|
||||
'Appending 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))
|
||||
return False
|
||||
|
||||
# Take a copy of our token dictionary
|
||||
tokens = self.tokens.copy()
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user