From cac32303182ede59fe43af7de21c083ca700c00c Mon Sep 17 00:00:00 2001 From: Thomas Feldmann Date: Fri, 12 Jun 2020 11:39:29 +0200 Subject: [PATCH 1/2] add email action --- CHANGELOG.md | 3 ++ organize/actions/__init__.py | 1 + organize/actions/action.py | 8 +++- organize/actions/echo.py | 1 - organize/actions/email.py | 87 ++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 organize/actions/email.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bcc4be0..b3f4f1b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## WIP +- Add `email` action. + ## v1.9 (2020-06-12) - Add filter `Duplicate`. diff --git a/organize/actions/__init__.py b/organize/actions/__init__.py index fcb25343..6605bef0 100644 --- a/organize/actions/__init__.py +++ b/organize/actions/__init__.py @@ -1,6 +1,7 @@ from .copy import Copy from .delete import Delete from .echo import Echo +from .email import Email from .move import Move from .python import Python from .rename import Rename diff --git a/organize/actions/action.py b/organize/actions/action.py index dfdbaccf..976f3149 100644 --- a/organize/actions/action.py +++ b/organize/actions/action.py @@ -13,7 +13,7 @@ class TemplateAttributeError(Error): class Action: - pre_print_hook = None # type: Optional[Callable] + pre_print_hook = None # type: Optional[Callable] def run(self, **kwargs) -> Optional[Mapping[str, Any]]: return self.pipeline(DotDict(kwargs)) @@ -25,7 +25,11 @@ def print(self, msg) -> None: """ print a message for the user """ if callable(self.pre_print_hook): self.pre_print_hook() # pylint: disable=not-callable - print(indent("- [%s] %s" % (self.__class__.__name__, msg), " " * 4)) + # indent multiline message + lines = msg.splitlines() + print(indent("- [%s] %s" % (self.__class__.__name__, lines[0]), " " * 4)) + for line in lines[1:]: + print(indent(line, " " * 6)) @staticmethod def fill_template_tags(msg: str, args) -> str: diff --git a/organize/actions/echo.py b/organize/actions/echo.py index 8c502ba7..b97dc6c6 100644 --- a/organize/actions/echo.py +++ b/organize/actions/echo.py @@ -70,7 +70,6 @@ class Echo(Action): def __init__(self, msg) -> None: self.msg = msg - self.log = logging.getLogger(__name__) def pipeline(self, args) -> None: path = args["path"] diff --git a/organize/actions/email.py b/organize/actions/email.py new file mode 100644 index 00000000..2037092c --- /dev/null +++ b/organize/actions/email.py @@ -0,0 +1,87 @@ +import logging +import smtplib +from email import encoders +from email.mime.base import MIMEBase +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.utils import COMMASPACE, formatdate +from pathlib import Path + +from .action import Action + +logger = logging.getLogger(__name__) + + +class Email(Action): + def __init__( + self, + send_from, + send_to, + subject, + message, + server="localhost", + port=587, + username="", + password="", + use_tls=True, + ) -> None: + """Compose and send email with provided info and attachments. + + Args: + send_from (str): from name + send_to (list[str]): to name(s) + subject (str): message title + message (str): message body + server (str): mail server host name + port (int): port number + username (str): server auth username + password (str): server auth password + use_tls (bool): use TLS mode + """ + self.send_from = send_from + self.send_to = send_to + self.subject = subject + self.message = message + self.server = server + self.port = port + self.username = username or send_from + self.password = password + self.use_tls = use_tls + + def pipeline(self, args) -> None: + path = args["path"] + simulate = args["simulate"] + + full_subject = self.fill_template_tags(self.subject, args) + full_message = self.fill_template_tags(self.message, args) + self.print( + "\nRecp: %s\nSubj: %s\n%s" + % (", ".join(self.send_to), full_subject, full_message) + ) + if not simulate: + msg = MIMEMultipart() + msg["From"] = self.send_from + msg["To"] = COMMASPACE.join(self.send_to) + msg["Date"] = formatdate(localtime=True) + msg["Subject"] = full_subject + + msg.attach(MIMEText(full_message)) + + part = MIMEBase("application", "octet-stream") + with open(path, "rb") as f: + part.set_payload(f.read()) + encoders.encode_base64(part) + part.add_header( + "Content-Disposition", 'attachment; filename="{}"'.format(Path(path).name) + ) + msg.attach(part) + + smtp = smtplib.SMTP(self.server, self.port) + if self.use_tls: + smtp.starttls() + smtp.login(self.username, self.password) + smtp.sendmail(self.send_from, self.send_to, msg.as_string()) + smtp.quit() + + def __str__(self) -> str: + return "Email" From d22ef8ae54f34ffcfe58063fcde7d91c80e2d806 Mon Sep 17 00:00:00 2001 From: Thomas Feldmann Date: Fri, 12 Jun 2020 11:58:00 +0200 Subject: [PATCH 2/2] fix mypy error --- organize/actions/email.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/organize/actions/email.py b/organize/actions/email.py index 2037092c..b8d2f16e 100644 --- a/organize/actions/email.py +++ b/organize/actions/email.py @@ -4,7 +4,7 @@ from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from email.utils import COMMASPACE, formatdate +from email.utils import formatdate from pathlib import Path from .action import Action @@ -55,13 +55,13 @@ def pipeline(self, args) -> None: full_subject = self.fill_template_tags(self.subject, args) full_message = self.fill_template_tags(self.message, args) self.print( - "\nRecp: %s\nSubj: %s\n%s" + "\nTo: %s\nSubj: %s\n%s" % (", ".join(self.send_to), full_subject, full_message) ) if not simulate: msg = MIMEMultipart() msg["From"] = self.send_from - msg["To"] = COMMASPACE.join(self.send_to) + msg["To"] = ", ".join(self.send_to) msg["Date"] = formatdate(localtime=True) msg["Subject"] = full_subject @@ -72,7 +72,8 @@ def pipeline(self, args) -> None: part.set_payload(f.read()) encoders.encode_base64(part) part.add_header( - "Content-Disposition", 'attachment; filename="{}"'.format(Path(path).name) + "Content-Disposition", + 'attachment; filename="{}"'.format(Path(path).name), ) msg.attach(part)