Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Der-Henning
GitHub Repository: Der-Henning/tgtg
Path: blob/main/tgtg_scanner/notifiers/smtp.py
725 views
1
import json
2
import logging
3
import smtplib
4
from email.mime.multipart import MIMEMultipart
5
from email.mime.text import MIMEText
6
from email.utils import formatdate
7
from smtplib import SMTPException, SMTPServerDisconnected
8
from typing import Union
9
10
from tgtg_scanner.errors import MaskConfigurationError, SMTPConfigurationError
11
from tgtg_scanner.models import Config, Favorites, Item, Reservations
12
from tgtg_scanner.models.reservations import Reservation
13
from tgtg_scanner.notifiers.base import Notifier
14
15
log = logging.getLogger("tgtg")
16
17
18
class SMTP(Notifier):
19
"""Notifier for SMTP"""
20
21
def __init__(self, config: Config, reservations: Reservations, favorites: Favorites):
22
super().__init__(config, reservations, favorites)
23
self.server: Union[smtplib.SMTP, None] = None
24
self.debug = config.debug
25
self.enabled = config.smtp.enabled
26
self.host = config.smtp.host
27
self.port = config.smtp.port
28
self.use_tls = config.smtp.use_tls
29
self.use_ssl = config.smtp.use_ssl
30
self.timeout = config.smtp.timeout
31
self.username = config.smtp.username
32
self.password = config.smtp.password
33
self.sender = config.smtp.sender
34
self.recipients = config.smtp.recipients
35
self.item_recipients: dict[str, list[str]] = {}
36
self.subject = config.smtp.subject
37
self.body = config.smtp.body
38
self.cron = config.smtp.cron
39
if self.enabled:
40
if self.host is None or self.port is None or self.recipients is None:
41
raise SMTPConfigurationError()
42
try:
43
Item.check_mask(self.subject)
44
Item.check_mask(self.body)
45
except MaskConfigurationError as exc:
46
raise SMTPConfigurationError(exc.message) from exc
47
try:
48
self._connect()
49
except Exception as exc:
50
raise SMTPConfigurationError(exc) from exc
51
if config.smtp.recipients_per_item is not None:
52
item_recipients = None
53
try:
54
item_recipients = json.loads(config.smtp.recipients_per_item)
55
except json.decoder.JSONDecodeError:
56
raise SMTPConfigurationError("Recipients per Item is not a valid dictionary")
57
if not isinstance(item_recipients, dict) or any(
58
not isinstance(value, (list, str)) for value in item_recipients.values()
59
):
60
raise SMTPConfigurationError("Recipients per Item is not a valid dictionary")
61
self.item_recipients = {k: v if isinstance(v, list) else [v] for k, v in item_recipients.items()}
62
63
def __del__(self):
64
"""Closes SMTP connection when shutdown"""
65
if self.server:
66
try:
67
self.server.quit()
68
except Exception as exc:
69
log.warning(exc)
70
71
def _connect(self) -> None:
72
"""Connect to SMTP Server"""
73
if self.host is None or self.port is None:
74
raise SMTPConfigurationError()
75
if self.use_ssl:
76
self.server = smtplib.SMTP_SSL(self.host, self.port, timeout=self.timeout)
77
else:
78
self.server = smtplib.SMTP(self.host, self.port, timeout=self.timeout)
79
self.server.set_debuglevel(self.debug)
80
if self.use_tls:
81
self.server.starttls()
82
self.server.ehlo()
83
if self.username is not None and self.password is not None:
84
self.server.login(self.username, self.password)
85
86
def _stay_connected(self) -> None:
87
"""Refresh server connection if connection is lost"""
88
status = -1
89
if self.server is not None:
90
try:
91
status = self.server.noop()[0]
92
except SMTPServerDisconnected:
93
pass
94
if status != 250:
95
self._connect()
96
97
def _send_mail(self, subject: str, html: str, item_id: str) -> None:
98
"""Sends mail with html body"""
99
if self.server is None:
100
self._connect()
101
if self.sender is None or self.recipients is None or self.server is None:
102
raise SMTPConfigurationError()
103
message = MIMEMultipart("alternative")
104
message["From"] = self.sender
105
106
# Contains either the main recipient(s) or recipient(s) that should be
107
# notified for the specific item. First, initalize with main recipient(s)
108
recipients = self.item_recipients.get(item_id, self.recipients)
109
110
message["To"] = ", ".join(recipients)
111
message["Subject"] = subject
112
message["Date"] = formatdate(localtime=True)
113
message.attach(MIMEText(html, "html", "utf-8"))
114
body = message.as_string()
115
self._stay_connected()
116
try:
117
self.server.sendmail(self.sender, recipients, body)
118
except SMTPException:
119
self._connect()
120
self.server.sendmail(self.sender, recipients, body)
121
122
def _send(self, item: Union[Item, Reservation]) -> None:
123
"""Sends item information via Mail."""
124
if isinstance(item, Item):
125
self._send_mail(item.unmask(self.subject), item.unmask(self.body), item.item_id)
126
127
def __repr__(self) -> str:
128
return f"SMTP: {self.recipients}"
129
130