building out and preparing the EmailHandler's adaptive content. Using HTML formatting.
This commit is contained in:
parent
5ae2d74745
commit
cea615fe63
@ -12,6 +12,12 @@ import email
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.application import MIMEApplication
|
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():
|
class EmailHandler():
|
||||||
"""
|
"""
|
||||||
@ -36,7 +42,7 @@ class EmailHandler():
|
|||||||
self.mail_port = mail_port
|
self.mail_port = mail_port
|
||||||
self.mail_address = mail_address
|
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):
|
def check_state(self):
|
||||||
"""check, whether the server login took place and is open."""
|
"""check, whether the server login took place and is open."""
|
||||||
@ -59,7 +65,7 @@ class EmailHandler():
|
|||||||
user = self.server.__dict__.get("user",None)
|
user = self.server.__dict__.get("user",None)
|
||||||
return user is not 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
|
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).
|
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())
|
(status_code, status_msg) = self.server.login(self.mail_address, password=getpass())
|
||||||
else:
|
else:
|
||||||
# fernet + password file
|
# 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')
|
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").
|
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.
|
The EmailMessage object does not contain the recipients yet, as these will be defined upon sending the Email.
|
||||||
"""
|
"""
|
||||||
msg = EmailMessage()
|
msg = EmailMessage()
|
||||||
msg["Subject"] = subject
|
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["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
|
return msg
|
||||||
|
|
||||||
def build_recipients(self, email_tgts:list[str]):
|
def build_recipients(self, email_tgts:list[str]):
|
||||||
@ -172,3 +189,41 @@ class EmailHandler():
|
|||||||
self.server.quit()
|
self.server.quit()
|
||||||
return
|
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