platypush/platypush/plugins/mail/_plugin/_out.py

139 lines
4.1 KiB
Python

import os
from abc import ABC, abstractmethod
from datetime import datetime
from email.message import Message
from email.mime.application import MIMEApplication
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from mimetypes import guess_type
from typing import Dict, Optional, Sequence, Union
from dateutil import tz
from .._utils import normalize_from_header
from ._base import BaseMailPlugin
class MailOutPlugin(BaseMailPlugin, ABC):
"""
Base class for mail out plugins.
"""
@abstractmethod
def send_message(self, message: Message, **_):
raise NotImplementedError()
@staticmethod
def _file_to_part(file: str) -> MIMEBase:
_type, _subtype, _type_class = 'application', 'octet-stream', MIMEApplication
mime_type, _sub_subtype = guess_type(file)
if mime_type:
_type, _subtype = mime_type.split('/')
if _sub_subtype:
_subtype += ';' + _sub_subtype
if _type == 'application':
_type_class = MIMEApplication
elif _type == 'audio':
_type_class = MIMEAudio
elif _type == 'image':
_type_class = MIMEImage
elif _type == 'text':
_type_class = MIMEText
args = {}
if _type_class != MIMEText:
mode = 'rb'
args['Name'] = os.path.basename(file)
else:
mode = 'r'
with open(file, mode) as f:
return _type_class(f.read(), _subtype, **args)
@classmethod
def create_message(
cls,
to: Union[str, Sequence[str]],
from_: Optional[str] = None,
cc: Optional[Union[str, Sequence[str]]] = None,
bcc: Optional[Union[str, Sequence[str]]] = None,
subject: str = '',
body: str = '',
body_type: str = 'plain',
attachments: Optional[Sequence[str]] = None,
headers: Optional[Dict[str, str]] = None,
) -> Message:
assert from_, 'from/from_ field not specified'
content = MIMEText(body, body_type)
if attachments:
msg = MIMEMultipart()
msg.attach(content)
for attachment in attachments:
attachment = os.path.abspath(os.path.expanduser(attachment))
assert os.path.isfile(attachment), f'No such file: {attachment}'
part = cls._file_to_part(attachment)
part[
'Content-Disposition'
] = f'attachment; filename="{os.path.basename(attachment)}"'
msg.attach(part)
else:
msg = content
msg['From'] = from_
msg['To'] = to if isinstance(to, str) else ', '.join(to)
msg['Cc'] = ', '.join(cc) if cc else ''
msg['Bcc'] = ', '.join(bcc) if bcc else ''
msg['Subject'] = subject
msg['Date'] = (
datetime.now()
.replace(tzinfo=tz.tzlocal())
.strftime('%a, %d %b %Y %H:%M:%S %z')
)
if headers:
for name, value in headers.items():
msg.add_header(name, value)
return msg
def send(
self,
to: Union[str, Sequence[str]],
from_: Optional[str] = None,
cc: Optional[Union[str, Sequence[str]]] = None,
bcc: Optional[Union[str, Sequence[str]]] = None,
subject: str = '',
body: str = '',
body_type: str = 'plain',
attachments: Optional[Sequence[str]] = None,
headers: Optional[Dict[str, str]] = None,
**args,
):
if not from_ and 'from' in args:
from_ = args.pop('from')
msg = self.create_message(
to=to,
from_=normalize_from_header(from_, self.account, self.server),
cc=cc,
bcc=bcc,
subject=subject,
body=body,
body_type=body_type,
attachments=attachments,
headers=headers,
)
return self.send_message(msg, **args)
# vim:sw=4:ts=4:et: