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