jellyfin-accounts/jellyfin_accounts/email.py

199 lines
7.6 KiB
Python
Raw Normal View History

# Handles everything related to emails
import datetime
import pytz
import requests
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from pathlib import Path
from dateutil import parser as date_parser
from jinja2 import Environment, FileSystemLoader
from jellyfin_accounts import config
from jellyfin_accounts import email_log as log
2020-06-21 19:29:53 +00:00
class Email:
def __init__(self, address):
self.address = address
2020-06-21 19:29:53 +00:00
log.debug(f"{self.address}: Creating email")
self.content = {}
2020-06-21 19:29:53 +00:00
self.from_address = config["email"]["address"]
self.from_name = config["email"]["from"]
log.debug(
(
f"{self.address}: Sending from {self.from_address} "
+ f"({self.from_name})"
)
)
def pretty_time(self, expiry):
current_time = datetime.datetime.now()
2020-06-21 19:29:53 +00:00
date = expiry.strftime(config["email"]["date_format"])
if config.getboolean("email", "use_24h"):
log.debug(f"{self.address}: Using 24h time")
time = expiry.strftime("%H:%M")
else:
2020-06-21 19:29:53 +00:00
log.debug(f"{self.address}: Using 12h time")
time = expiry.strftime("%-I:%M %p")
expiry_delta = (expiry - current_time).seconds
2020-06-21 19:29:53 +00:00
expires_in = {
"hours": expiry_delta // 3600,
"minutes": (expiry_delta // 60) % 60,
}
if expires_in["hours"] == 0:
expires_in = f'{str(expires_in["minutes"])}m'
else:
expires_in = f'{str(expires_in["hours"])}h {str(expires_in["minutes"])}m'
2020-06-21 19:29:53 +00:00
log.debug(f"{self.address}: Expires in {expires_in}")
return {"date": date, "time": time, "expires_in": expires_in}
def construct_invite(self, invite):
2020-06-21 19:29:53 +00:00
self.subject = config["invite_emails"]["subject"]
log.debug(f"{self.address}: Using subject {self.subject}")
log.debug(f"{self.address}: Constructing email content")
expiry = invite["expiry"]
expiry.replace(tzinfo=None)
pretty = self.pretty_time(expiry)
2020-06-21 19:29:53 +00:00
email_message = config["email"]["message"]
invite_link = config["invite_emails"]["url_base"]
invite_link += "/" + invite["code"]
for key in ["text", "html"]:
sp = Path(config["invite_emails"]["email_" + key]) / ".."
sp = str(sp.resolve()) + "/"
template_loader = FileSystemLoader(searchpath=sp)
template_env = Environment(loader=template_loader)
2020-06-21 19:29:53 +00:00
fname = Path(config["invite_emails"]["email_" + key]).name
template = template_env.get_template(fname)
2020-06-21 19:29:53 +00:00
c = template.render(
expiry_date=pretty["date"],
expiry_time=pretty["time"],
expires_in=pretty["expires_in"],
invite_link=invite_link,
message=email_message,
)
self.content[key] = c
2020-06-21 19:29:53 +00:00
log.info(f"{self.address}: {key} constructed")
def construct_reset(self, reset):
2020-06-21 19:29:53 +00:00
self.subject = config["password_resets"]["subject"]
log.debug(f"{self.address}: Using subject {self.subject}")
log.debug(f"{self.address}: Constructing email content")
try:
2020-06-21 19:29:53 +00:00
expiry = date_parser.parse(reset["ExpirationDate"])
expiry = expiry.replace(tzinfo=None)
except:
log.error(f"{self.address}: Couldn't parse expiry time")
return False
current_time = datetime.datetime.now()
if expiry >= current_time:
2020-06-21 19:29:53 +00:00
log.debug(f"{self.address}: Invite valid")
pretty = self.pretty_time(expiry)
2020-06-21 19:29:53 +00:00
email_message = config["email"]["message"]
for key in ["text", "html"]:
sp = Path(config["password_resets"]["email_" + key]) / ".."
sp = str(sp.resolve()) + "/"
template_loader = FileSystemLoader(searchpath=sp)
template_env = Environment(loader=template_loader)
2020-06-21 19:29:53 +00:00
fname = Path(config["password_resets"]["email_" + key]).name
template = template_env.get_template(fname)
2020-06-21 19:29:53 +00:00
c = template.render(
username=reset["UserName"],
expiry_date=pretty["date"],
expiry_time=pretty["time"],
expires_in=pretty["expires_in"],
pin=reset["Pin"],
message=email_message,
)
self.content[key] = c
2020-06-21 19:29:53 +00:00
log.info(f"{self.address}: {key} constructed")
return True
else:
2020-06-21 19:29:53 +00:00
err = (
f"{self.address}: "
+ "Reset has reportedly already expired. "
+ "Ensure timezones are correctly configured."
)
log.error(err)
return False
class Mailgun(Email):
def __init__(self, address):
super().__init__(address)
2020-06-21 19:29:53 +00:00
self.api_url = config["mailgun"]["api_url"]
self.api_key = config["mailgun"]["api_key"]
self.from_mg = f"{self.from_name} <{self.from_address}>"
def send(self):
2020-06-21 19:29:53 +00:00
response = requests.post(
self.api_url,
auth=("api", self.api_key),
data={
"from": self.from_mg,
"to": [self.address],
"subject": self.subject,
"text": self.content["text"],
"html": self.content["html"],
},
)
if response.ok:
2020-06-21 19:29:53 +00:00
log.info(f"{self.address}: Sent via mailgun.")
return True
2020-06-21 19:29:53 +00:00
log.debug(f"{self.address}: Mailgun: {response.status_code}")
return response
class Smtp(Email):
def __init__(self, address):
super().__init__(address)
2020-06-21 19:29:53 +00:00
self.server = config["smtp"]["server"]
self.password = config["smtp"]["password"]
try:
2020-06-21 19:29:53 +00:00
self.port = int(config["smtp"]["port"])
except ValueError:
self.port = 465
2020-06-21 19:29:53 +00:00
log.debug(f"{self.address}: Defaulting to port {self.port}")
def send(self):
message = MIMEMultipart("alternative")
message["Subject"] = self.subject
message["From"] = self.from_address
message["To"] = self.address
2020-06-21 19:29:53 +00:00
text = MIMEText(self.content["text"], "plain")
html = MIMEText(self.content["html"], "html")
message.attach(text)
message.attach(html)
try:
2020-06-21 19:29:53 +00:00
if config["smtp"]["encryption"] == "ssl_tls":
self.context = ssl.create_default_context()
2020-06-21 19:29:53 +00:00
with smtplib.SMTP_SSL(
self.server, self.port, context=self.context
) as server:
server.ehlo()
server.login(self.from_address, self.password)
2020-06-21 19:29:53 +00:00
server.sendmail(
self.from_address, self.address, message.as_string()
)
log.info(f"{self.address}: Sent via smtp (ssl/tls)")
return True
2020-06-21 19:29:53 +00:00
elif config["smtp"]["encryption"] == "starttls":
with smtplib.SMTP(self.server, self.port) as server:
server.ehlo()
server.starttls()
server.login(self.from_address, self.password)
2020-06-21 19:29:53 +00:00
server.sendmail(
self.from_address, self.address, message.as_string()
)
log.info(f"{self.address}: Sent via smtp (starttls)")
return True
except Exception as e:
2020-06-21 19:29:53 +00:00
err = f"{self.address}: Failed to send via smtp: "
err += type(e).__name__
log.error(err)
try:
log.error(e.smtp_error)
except:
pass
2020-05-05 10:37:13 +00:00
return False