This repository has been archived by the owner on Jun 11, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
/
mail.py
212 lines (175 loc) · 7.35 KB
/
mail.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
from email.message import Message
from email.utils import formatdate
from zope.interface import implements
from twisted.internet import defer
from twisted.mail.smtp import sendmail
from twisted.python import log as twlog, failure
from buildbot import interfaces
from buildbot.status import base
from buildbot.status import mail
ENCODING = 'utf8'
class MercurialEmailLookup:
implements(interfaces.IEmailLookup)
def getAddress(self, user):
return user
def defaultChangeMessage(change):
revision = change.revision
msgdict = {"type": "plain"}
msgdict["body"] = "Thanks for your submission (%s)."
return msgdict
class ChangeNotifier(base.StatusReceiverMultiService):
compare_attrs = ('fromaddr', 'categories', 'branches', 'subject',
'relayhost', 'lookup', 'extraRecipients', 'sendToInterestedUsers',
'messageFormatter', 'extraHeaders', 'smtpUser', 'smtpPassword',
'smtpPort', 'changeIsImportant')
def __init__(self, fromaddr, categories=None, branches=None,
subject="Notifcation of change %(revision)s on branch %(branch)s",
relayhost="localhost", lookup=None, extraRecipients=None,
sendToInterestedUsers=True, messageFormatter=defaultChangeMessage,
extraHeaders=None, smtpUser=None, smtpPassword=None, smtpPort=25,
changeIsImportant=None):
base.StatusReceiverMultiService.__init__(self)
self.fromaddr = fromaddr
self.categories = categories
self.branches = branches
self.relayhost = relayhost
if lookup is not None:
if type(lookup) is str:
lookup = mail.Domain(lookup)
assert interfaces.IEmailLookup.providedBy(lookup)
self.lookup = lookup
self.smtpUser = smtpUser
self.smtpPassword = smtpPassword
self.smtpPort = smtpPort
self.master_status = None
self.subject = subject
if extraHeaders:
assert isinstance(extraHeaders, dict)
self.extraHeaders = extraHeaders
self.messageFormatter = messageFormatter
self.sendToInterestedUsers = sendToInterestedUsers
if changeIsImportant:
assert callable(changeIsImportant)
self.changeIsImportant = changeIsImportant
if extraRecipients:
assert isinstance(extraRecipients, (list, tuple))
for r in extraRecipients:
assert isinstance(r, str)
assert mail.VALID_EMAIL.search(r) # require full email addresses, not User names
self.extraRecipients = extraRecipients
else:
self.extraRecipients = []
# you should either limit on branches or categories, not both
assert not (self.branches != None and self.categories != None)
def setServiceParent(self, parent):
"""
@type parent: L{buildbot.master.BuildMaster}
"""
base.StatusReceiverMultiService.setServiceParent(self, parent)
self.setup()
def setup(self):
self.master_status = self.parent.getStatus()
self.master_status.subscribe(self)
def disownServiceParent(self):
self.master_status.unsubscribe(self)
return base.StatusReceiverMultiService.disownServiceParent(self)
def changeAdded(self, change):
if self.branches and change.branch not in self.branches:
return
if self.categories and change.category not in self.categories:
return
if self.changeIsImportant:
try:
if not self.changeIsImportant(change):
return
except:
twlog.err(failure.Failure(),
'in changeIsImportant check for %s' % change)
return
return self.buildMessage(change)
def createEmail(self, msgdict, change):
text = msgdict['body'].encode(ENCODING)
type_ = msgdict['type']
if 'subject' in msgdict:
subject = msgdict['subject'].encode(ENCODING)
else:
subject = self.subject % change.asDict()
assert type_ in ('plain', 'html'), "'%s' message type must be 'plain' or 'html'." % type_
m = Message()
m.set_payload(text, ENCODING)
m.set_type("text/%s" % type_)
m['Date'] = formatdate(localtime=True)
m['Subject'] = subject
m['From'] = self.fromaddr
# m['To'] is added later
# Add any extra headers that were requested, doing
# interpolation if necessary
if self.extraHeaders:
d = change.asDict()
for k,v in self.extraHeaders.items():
k = k % d
if k in m:
twlog.msg("Warning: Got header " + k + " in self.extraHeaders "
"but it already exists in the Message - "
"not adding it.")
continue
m[k] = v % d
if 'headers' in msgdict:
d = change.asDict()
for k,v in msgdict['headers'].items():
k = k % d
if k in m:
twlog.msg("Warning: Got header " + k + " in self.extraHeaders "
"but it already exists in the Message - "
"not adding it.")
continue
m[k] = v % d
return m
def buildMessage(self, change):
msgdict = self.messageFormatter(change)
m = self.createEmail(msgdict, change)
# now, who is this message going to?
dl = []
recipients = []
if self.sendToInterestedUsers and self.lookup:
d = defer.maybeDeferred(self.lookup.getAddress, change.who)
d.addCallback(recipients.append)
dl.append(d)
d = defer.DeferredList(dl)
d.addCallback(self._gotRecipients, recipients, m)
return d
def _gotRecipients(self, res, rlist, m):
recipients = set()
for r in rlist:
if r is None: # getAddress didn't like this address
continue
# Git can give emails like 'User' <user@foo.com>@foo.com so check
# for two @ and chop the last
if r.count('@') > 1:
r = r[:r.rindex('@')]
if mail.VALID_EMAIL.search(r):
recipients.add(r)
else:
twlog.msg("INVALID EMAIL: %r" + r)
# if we're sending to interested users move the extra's to the CC
# list so they can tell if they are also interested in the change
# unless there are no interested users
if self.sendToInterestedUsers and len(recipients):
extra_recips = self.extraRecipients[:]
extra_recips.sort()
m['CC'] = ", ".join(extra_recips)
else:
[recipients.add(r) for r in self.extraRecipients[:]]
rlist = list(recipients)
rlist.sort()
m['To'] = ", ".join(rlist)
# The extras weren't part of the TO list so add them now
if self.sendToInterestedUsers:
for r in self.extraRecipients:
recipients.add(r)
return self.sendMessage(m, list(recipients))
def sendMessage(self, m, recipients):
s = m.as_string()
twlog.msg("sending mail (%d bytes) to" % len(s), recipients)
return sendmail(self.relayhost, self.fromaddr, recipients, s,
port=self.smtpPort)