building out and preparing the EmailHandler's adaptive content. Using HTML formatting.
This commit is contained in:
parent
ad1e0249d7
commit
63040c7f21
@ -12,6 +12,12 @@ import email
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.application import MIMEApplication
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from tempfile import NamedTemporaryFile
|
||||
import json
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
class EmailHandler():
|
||||
"""
|
||||
@ -36,7 +42,7 @@ class EmailHandler():
|
||||
self.mail_port = mail_port
|
||||
self.mail_address = mail_address
|
||||
|
||||
self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, SMTP
|
||||
self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, use smtplib.SMTP
|
||||
|
||||
def check_state(self):
|
||||
"""check, whether the server login took place and is open."""
|
||||
@ -59,7 +65,7 @@ class EmailHandler():
|
||||
user = self.server.__dict__.get("user",None)
|
||||
return user is not None
|
||||
|
||||
def login(self, interactive:bool=True):
|
||||
def login(self, interactive:bool=True, pwd=typing.Optional[bytes]):
|
||||
"""
|
||||
login on the determined mail server's mail address. By default, this function opens an interactive window to
|
||||
type the password without echoing (printing '*******' instead of readable characters).
|
||||
@ -71,19 +77,30 @@ class EmailHandler():
|
||||
(status_code, status_msg) = self.server.login(self.mail_address, password=getpass())
|
||||
else:
|
||||
# fernet + password file
|
||||
raise NotImplementedError()
|
||||
assert pwd is not None, f"when non-interactive login is selected, one must provide a password"
|
||||
assert isinstance(pwd, bytes), "please provide only byte-encrypted secure passwords. Those should be Fernet encoded."
|
||||
|
||||
fernet_key_path = os.path.join(os.path.expanduser("~"), "secure", "email_login_fernet_key.json")
|
||||
assert os.path.exists(fernet_key_path), f"cannot find fernet key file at path: {fernet_key_path}"
|
||||
|
||||
with open(fernet_key_path, "r") as jr:
|
||||
json_content = json.load(jr)
|
||||
assert "fernet_key" in json_content
|
||||
key = json_content.get("fernet_key").encode("utf8")
|
||||
|
||||
(status_code, status_msg) = self.server.login(self.mail_address, password=Fernet(key).decrypt(pwd).decode())
|
||||
return (status_code, status_msg) # should be: (235, b'2.7.0 Authentication successful')
|
||||
|
||||
def create_email(self, subject:str, message_body:str)->EmailMessage:
|
||||
def create_email(self, subject:str, message_body:str, subtype:typing.Optional[str]=None, sender_address:typing.Optional[str]=None)->EmailMessage:
|
||||
"""
|
||||
Create an EmailMessage object, which contains the Email's header ("Subject"), content ("Message Body") and the sender's address ("From").
|
||||
The EmailMessage object does not contain the recipients yet, as these will be defined upon sending the Email.
|
||||
"""
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = self.mail_address
|
||||
msg["From"] = self.mail_address if sender_address is None else sender_address
|
||||
#msg["To"] = email_tgts # will be defined in self.send_email
|
||||
msg.set_content(message_body)
|
||||
msg.set_content(message_body, subtype=subtype) if subtype is not None else msg.set_content(message_body, subtype=subtype)
|
||||
return msg
|
||||
|
||||
def build_recipients(self, email_tgts:list[str]):
|
||||
@ -172,3 +189,41 @@ class EmailHandler():
|
||||
self.server.quit()
|
||||
return
|
||||
|
||||
def preview_html_content(self, html:str, delete_after_s_seconds:typing.Optional[float]=None, file_path_dict:dict={}):
|
||||
"""
|
||||
Given an HTML-formatted text string, this method creates a temporary .html file and
|
||||
spawns the local default webbrowser to preview the content.
|
||||
|
||||
This method is useful to design or debug HTML files before sending them via the EmailHandler.
|
||||
|
||||
When providing a floating point to the 'delete_after_s_seconds' argument, the temporary file will be
|
||||
automatically removed after those seconds. The python script is blocked for the duration (using time.sleep)
|
||||
|
||||
args:
|
||||
file_path_dict:
|
||||
it is common to refer to images via 'cid:FILE_ID' within the HTML content. The preview cannot
|
||||
display this, as the attached files are missing. To circumvent this, one can provide a dictionary, which
|
||||
replaced the referred key
|
||||
(e.g., 'cid:FILE_ID')
|
||||
with the actual path, such as a logo or remote absolute path
|
||||
(e.g., 'file:///C:/Users/User/brecal/misc/logo_bremen_calling.png')
|
||||
|
||||
Inspired by: https://stackoverflow.com/questions/53452322/is-there-a-way-that-i-can-preview-my-html-file
|
||||
User: https://stackoverflow.com/users/355230/martineau
|
||||
"""
|
||||
for k, v in file_path_dict.items():
|
||||
html = html.replace(k, v)
|
||||
|
||||
with NamedTemporaryFile(mode='wt', suffix='.html', delete=False, encoding="utf-8") as temp_file:
|
||||
temp_file.write(html)
|
||||
temp_filename = temp_file.name # Save temp file's name.
|
||||
|
||||
command = f"{sys.executable} -m webbrowser -n {temp_filename}"
|
||||
browser = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
if delete_after_s_seconds is not None:
|
||||
assert isinstance(delete_after_s_seconds, float)
|
||||
time.sleep(delete_after_s_seconds)
|
||||
if os.path.exists(temp_filename):
|
||||
os.remove(temp_filename)
|
||||
return
|
||||
Reference in New Issue
Block a user