139 lines
4.1 KiB
Python
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:
|