From 474704af5a7364e802e3c7730abe29b4afc3be57 Mon Sep 17 00:00:00 2001 From: Kurai Date: Fri, 18 Apr 2025 09:45:25 +0200 Subject: [PATCH 1/5] Mailer Update for multiples attachments __init__.py Idk what i'm doiin man --- ofunctions/mailer/__init__.py | 55 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/ofunctions/mailer/__init__.py b/ofunctions/mailer/__init__.py index fac2ca3..aa23852 100644 --- a/ofunctions/mailer/__init__.py +++ b/ofunctions/mailer/__init__.py @@ -177,33 +177,38 @@ def _send_email( else: message.attach(MIMEText(body, "plain", self.encoding)) - if attachment is not None: - att_filename = filename - if isinstance(attachment, bytes): - # Let's suppose we directly attach binary data - payload = attachment + if attachment is not None: #Use Multiples Attachments to your mail if needed + if isinstance(attachment, (bytes, bytearray)): + attachment_list = [attachment] + att_filename_list = [filename] if filename else [None] + elif isinstance(attachment, str): + attachment_list = [attachment] + att_filename_list = [filename if filename else os.path.basename(attachment)] + elif isinstance(attachment, list): + attachment_list = attachment + if isinstance(filename, str): + att_filename_list = [filename] * len(attachment_list) + else: + att_filename_list = [os.path.basename(file_path) for file_path in attachment_list] else: - with open(attachment, "rb") as f_attachment: - payload = f_attachment.read() - if not filename: - att_filename = os.path.basename(attachment) - - # Add file as application/octet-stream - # Email client can usually download this automatically as attachment - part = MIMEBase("application", "octet-stream") - part.set_payload(payload) - - # Encode file in ASCII characters to send by email - encoders.encode_base64(part) - - # Add header as key/value pair to attachment part - part.add_header( - "Content-Disposition", - "attachment; filename=%s" % att_filename, - ) + attachment_list = [] + att_filename_list = [] + + for each_attachment, each_filename in zip(attachment_list, att_filename_list): + if isinstance(each_attachment, (bytes, bytearray)): + payload = each_attachment + att_filename = each_filename if each_filename else "attachment.bin" + else: + with open(each_attachment, "rb") as f_attachment: + payload = f_attachment.read() + att_filename = each_filename if each_filename else os.path.basename(each_attachment) + + part = MIMEBase("application", "octet-stream") + part.set_payload(payload) + encoders.encode_base64(part) + part.add_header("Content-Disposition", "attachment; filename=%s" % att_filename) + message.attach(part) - # Add attachment to message and convert message to string - message.attach(part) text = message.as_string() From a60545c8a00e1c911bd419f02b10734d2b23d19e Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Wed, 23 Apr 2025 15:07:50 +0200 Subject: [PATCH 2/5] Improve PR #4 by allowing empty filename lists to be populated --- ofunctions/mailer/__init__.py | 59 ++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/ofunctions/mailer/__init__.py b/ofunctions/mailer/__init__.py index aa23852..3857b59 100644 --- a/ofunctions/mailer/__init__.py +++ b/ofunctions/mailer/__init__.py @@ -18,8 +18,8 @@ __copyright__ = "Copyright (C) 2014-2025 Orsiris de Jong" __description__ = "Mail sending class that handles encryption, authentication, bulk and split mail sending" __licence__ = "BSD 3 Clause" -__version__ = "1.2.2" -__build__ = "2025040401" +__version__ = "1.3.0" +__build__ = "2025042301" __compat__ = "python2.7+" import logging @@ -124,8 +124,8 @@ def send_email( recipient_mails=None, # type: Union[str, List[str]] subject=None, # type: str body=None, # type: str - attachment=None, # type: str - filename=None, # type: str + attachment=None, # type: Optional[Union[str, bytes, List[str], List[bytes]]] + filename=None, # type: Optional[Union[str, List[str]]] html_enabled=False, # type: bool bcc_mails=None, # type: str priority=False, # type: bool @@ -177,36 +177,45 @@ def _send_email( else: message.attach(MIMEText(body, "plain", self.encoding)) - if attachment is not None: #Use Multiples Attachments to your mail if needed + default_attachment_name = "attachment" + attachments = [] + filenames = [] + if attachment is not None: + # Attachments may be a str (path to file), direct bytes, or a list of str or list of bytes if isinstance(attachment, (bytes, bytearray)): - attachment_list = [attachment] - att_filename_list = [filename] if filename else [None] + attachments = [attachment] + filenames = [filename] if filename else [default_attachment_name] elif isinstance(attachment, str): - attachment_list = [attachment] - att_filename_list = [filename if filename else os.path.basename(attachment)] + attachments = [attachment] + filenames = [filename if filename else os.path.basename(attachment)] elif isinstance(attachment, list): - attachment_list = attachment + attachments = attachment if isinstance(filename, str): - att_filename_list = [filename] * len(attachment_list) + filenames = [] + for index in range(0, len(attachments)): + filenames.append("{}_{}".format(index, filename)) + elif isinstance(filenames, list): + filenames = filename else: - att_filename_list = [os.path.basename(file_path) for file_path in attachment_list] - else: - attachment_list = [] - att_filename_list = [] - - for each_attachment, each_filename in zip(attachment_list, att_filename_list): - if isinstance(each_attachment, (bytes, bytearray)): - payload = each_attachment - att_filename = each_filename if each_filename else "attachment.bin" + filenames = [os.path.basename(file_path) for file_path in attachments] + + if len(attachments) != len(filenames): + raise ValueError("Mismatch between attachments and filenames lists") + for attachment, filename in zip(attachments, filenames): + if isinstance(attachment, (bytes, bytearray)): + payload = attachment + if not filename: + filename = default_attachment_name + filename = filename if filename else default_attachment_name else: - with open(each_attachment, "rb") as f_attachment: + with open(attachment, "rb") as f_attachment: payload = f_attachment.read() - att_filename = each_filename if each_filename else os.path.basename(each_attachment) + filename = filename if filename else os.path.basename(attachment) part = MIMEBase("application", "octet-stream") part.set_payload(payload) encoders.encode_base64(part) - part.add_header("Content-Disposition", "attachment; filename=%s" % att_filename) + part.add_header("Content-Disposition", "attachment; filename=%s" % filename) message.attach(part) @@ -313,8 +322,8 @@ def send_email( security=None, # type: Optional[str] subject=None, # type: str body=None, # type: str - attachment=None, # type: str - filename=None, # type: str + attachment=None, # type: Optional[Union[str, bytes, List[str], List[bytes]]] + filename=None, # type: Optional[Union[str, List[str]]] html_enabled=False, # type: bool bcc_mails=None, # type: str priority=False, # type: bool From 765555c78de5bbf91554b48d27af016633448da4 Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Wed, 23 Apr 2025 15:15:50 +0200 Subject: [PATCH 3/5] ofunctions: Make linter happy --- ofunctions/mailer/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ofunctions/mailer/__init__.py b/ofunctions/mailer/__init__.py index 3857b59..3be2944 100644 --- a/ofunctions/mailer/__init__.py +++ b/ofunctions/mailer/__init__.py @@ -150,6 +150,9 @@ def _send_email( Actual mail sending function """ + nonlocal attachment + nonlocal filename + # Create a multipart message and set headers message = MIMEMultipart() message["From"] = sender_mail From a49b2c25c8d5a7afb3f6c66f57a77e35788c70d4 Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Wed, 23 Apr 2025 15:22:05 +0200 Subject: [PATCH 4/5] Reformat file with black --- ofunctions/mailer/__init__.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ofunctions/mailer/__init__.py b/ofunctions/mailer/__init__.py index 3be2944..d97e343 100644 --- a/ofunctions/mailer/__init__.py +++ b/ofunctions/mailer/__init__.py @@ -184,7 +184,7 @@ def _send_email( attachments = [] filenames = [] if attachment is not None: - # Attachments may be a str (path to file), direct bytes, or a list of str or list of bytes + # Attachments may be a str (path to file), direct bytes, or a list of str or list of bytes if isinstance(attachment, (bytes, bytearray)): attachments = [attachment] filenames = [filename] if filename else [default_attachment_name] @@ -200,7 +200,9 @@ def _send_email( elif isinstance(filenames, list): filenames = filename else: - filenames = [os.path.basename(file_path) for file_path in attachments] + filenames = [ + os.path.basename(file_path) for file_path in attachments + ] if len(attachments) != len(filenames): raise ValueError("Mismatch between attachments and filenames lists") @@ -213,15 +215,18 @@ def _send_email( else: with open(attachment, "rb") as f_attachment: payload = f_attachment.read() - filename = filename if filename else os.path.basename(attachment) + filename = ( + filename if filename else os.path.basename(attachment) + ) part = MIMEBase("application", "octet-stream") part.set_payload(payload) encoders.encode_base64(part) - part.add_header("Content-Disposition", "attachment; filename=%s" % filename) + part.add_header( + "Content-Disposition", "attachment; filename=%s" % filename + ) message.attach(part) - text = message.as_string() try: From cb47a315a96ef818f701c5014a350e858607c363 Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Fri, 2 May 2025 12:58:12 +0200 Subject: [PATCH 5/5] mailer: Refactor attachment filename handling --- ofunctions/mailer/__init__.py | 55 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/ofunctions/mailer/__init__.py b/ofunctions/mailer/__init__.py index d97e343..8513053 100644 --- a/ofunctions/mailer/__init__.py +++ b/ofunctions/mailer/__init__.py @@ -19,7 +19,7 @@ __description__ = "Mail sending class that handles encryption, authentication, bulk and split mail sending" __licence__ = "BSD 3 Clause" __version__ = "1.3.0" -__build__ = "2025042301" +__build__ = "2025050201" __compat__ = "python2.7+" import logging @@ -65,7 +65,7 @@ class ConnectionResetError(ConnectionError): pass -logger = logging.getLogger(__intname__) +logger = logging.getLogger() class Mailer: @@ -144,15 +144,13 @@ def send_email( def _send_email( recipient_mail, # type: Union[str,List[str]] + attachment=None, # type: Optional[Union[str, bytes, List[str], List[bytes]]] + filename=None, # type: Optional[Union[str, List[str]]] ): # type: (...) -> bool """ Actual mail sending function """ - - nonlocal attachment - nonlocal filename - # Create a multipart message and set headers message = MIMEMultipart() message["From"] = sender_mail @@ -183,29 +181,32 @@ def _send_email( default_attachment_name = "attachment" attachments = [] filenames = [] + if attachment is not None: - # Attachments may be a str (path to file), direct bytes, or a list of str or list of bytes - if isinstance(attachment, (bytes, bytearray)): - attachments = [attachment] - filenames = [filename] if filename else [default_attachment_name] - elif isinstance(attachment, str): - attachments = [attachment] - filenames = [filename if filename else os.path.basename(attachment)] - elif isinstance(attachment, list): + if isinstance(attachment, list): attachments = attachment - if isinstance(filename, str): - filenames = [] - for index in range(0, len(attachments)): - filenames.append("{}_{}".format(index, filename)) - elif isinstance(filenames, list): + if isinstance(filename, list): + if len(attachments) != len(filename): + raise ValueError( + "Mismatch between attachments and filenames list sizes" + ) filenames = filename - else: - filenames = [ - os.path.basename(file_path) for file_path in attachments - ] + else: + attachments = [attachment] + + if not filenames: + for index in range(0, len(attachments)): + if isinstance(attachment[index], (bytes, bytearray)): + filenames.append( + "{}_{}".format(index, default_attachment_name) + ) + elif isinstance(attachment[index], str): + filenames.append(os.path.basename(attachment[index])) + else: + filenames.append( + "{}.{}".format(index, default_attachment_name) + ) - if len(attachments) != len(filenames): - raise ValueError("Mismatch between attachments and filenames lists") for attachment, filename in zip(attachments, filenames): if isinstance(attachment, (bytes, bytearray)): payload = attachment @@ -292,11 +293,11 @@ def _send_email( if split_mails: for recipient in rfc822_addresses: - _result = _send_email(recipient) + _result = _send_email(recipient, attachment, filename) if not _result: result = _result else: - _result = _send_email(rfc822_addresses) + _result = _send_email(rfc822_addresses, attachment, filename) if not _result: result = _result