-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathnotification.py
More file actions
203 lines (170 loc) · 7.69 KB
/
notification.py
File metadata and controls
203 lines (170 loc) · 7.69 KB
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
from trac import __version__
from trac.config import BoolOption, Option
from trac.core import Component, implements
from trac.notification import NotifyEmail, NotificationSystem
from trac.util.translation import deactivate, reactivate
from code_comments.api import ICodeCommentChangeListener
from code_comments.comments import Comments
from code_comments.subscription import Subscription
class CodeCommentChangeListener(Component):
"""
Sends email notifications when comments have been created.
"""
implements(ICodeCommentChangeListener)
# ICodeCommentChangeListener methods
def comment_created(self, comment):
notifier = CodeCommentNotifyEmail(self.env)
notifier.notify(comment)
class CodeCommentNotifyEmail(NotifyEmail):
"""
Sends code comment notifications by email.
"""
notify_self = BoolOption('code_comments', 'notify_self', False,
doc="Send comment notifications to the author of "
"the comment.")
smtp_always_cc = Option('code_comments', 'smtp_always_cc', 'default',
doc="Email address(es) to always send notifications"
" to, addresses can be seen by all recipients"
"(Cc:).")
smtp_always_bcc = Option('code_comments', 'smtp_always_bcc', 'default',
doc="Email address(es) to always send "
"notifications to addresses do not appear "
"publicly (Bcc:).")
template_name = "code_comment_notify_email.txt"
from_email = "trac+comments@localhost"
def _get_comment_thread(self, comment):
"""
Returns all comments in the same location as a given comment, sorted
in order of id.
"""
comments = Comments(None, self.env)
args = {'type': comment.type,
'revision': comment.revision,
'path': comment.path,
'line': comment.line}
return comments.search(args, order_by='id')
def get_recipients(self, comment):
"""
Determine who should receive the notification.
Required by NotifyEmail.
Current scheme is as follows:
* For the first comment in a given location, the notification is sent
to any subscribers to that resource
* For any further comments in a given location, the notification is
sent to the author of the last comment in that location, and any other
subscribers for that resource
"""
torcpts = set()
ccrcpts = set()
for subscription in Subscription.for_comment(self.env, comment,
notify=True):
torcpts.add(subscription.user)
# Is this a reply, or a new comment?
thread = self._get_comment_thread(comment)
if len(thread) > 1:
# The author of the comment before this one
torcpts.add(thread[-2].author)
# Should we notify the comment author?
if not self.notify_self:
torcpts = torcpts.difference([comment.author])
ccrcpts = ccrcpts.difference([comment.author])
# Remove duplicates
ccrcpts = ccrcpts.difference(torcpts)
return (torcpts, ccrcpts)
def _get_author_name(self, comment):
"""
Get the real name of the user who made the comment. If it cannot be
determined, return their username.
"""
for username, name, email in self.env.get_known_users():
if username == comment.author and name:
return name
return comment.author
def notify(self, comment):
self.comment_author = self._get_author_name(comment)
self.data.update({
"comment": comment,
"comment_url": self.env.abs_href() + comment.href(),
"project_url": self.env.project_url or self.env.abs_href(),
})
projname = self.config.get("project", "name")
subject = "Re: [%s] %s" % (projname, comment.link_text())
try:
NotifyEmail.notify(self, comment, subject)
except Exception, e:
self.env.log.error("Failure sending notification on creation of "
"comment #%d: %s", comment.id, e)
def send(self, torcpts, ccrcpts, mime_headers={}):
from email.MIMEText import MIMEText
from email.Utils import formatdate
self.from_name = self.comment_author
stream = self.template.generate(**self.data)
# don't translate the e-mail stream
t = deactivate()
try:
body = stream.render('text', encoding='utf-8')
finally:
reactivate(t)
public_cc = self.config.getbool('notification', 'use_public_cc')
headers = {}
headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
headers['X-Trac-Version'] = __version__
headers['X-Trac-Project'] = self.env.project_name
headers['X-URL'] = self.env.project_url
headers['Precedence'] = 'bulk'
headers['Auto-Submitted'] = 'auto-generated'
headers['Subject'] = self.subject
headers['From'] = (self.from_name, self.from_email) if self.from_name \
else self.from_email
headers['Reply-To'] = self.replyto_email
def build_addresses(rcpts):
"""Format and remove invalid addresses"""
return filter(lambda x: x, \
[self.get_smtp_address(addr) for addr in rcpts])
def remove_dup(rcpts, all):
"""Remove duplicates"""
tmp = []
for rcpt in rcpts:
if not rcpt in all:
tmp.append(rcpt)
all.append(rcpt)
return (tmp, all)
toaddrs = build_addresses(torcpts)
ccaddrs = build_addresses(ccrcpts)
accparam = self.config.get('code_comments', 'smtp_always_cc')
if accparam == "default":
accparam = self.config.get('notification', 'smtp_always_cc')
accaddrs = accparam and \
build_addresses(accparam.replace(',', ' ').split()) or []
bccparam = self.config.get('code_comments', 'smtp_always_bcc')
if bccparam == "default":
bccparam = self.config.get('notification', 'smtp_always_bcc')
bccaddrs = bccparam and \
build_addresses(bccparam.replace(',', ' ').split()) or []
recipients = []
(toaddrs, recipients) = remove_dup(toaddrs, recipients)
(ccaddrs, recipients) = remove_dup(ccaddrs, recipients)
(accaddrs, recipients) = remove_dup(accaddrs, recipients)
(bccaddrs, recipients) = remove_dup(bccaddrs, recipients)
# if there is not valid recipient, leave immediately
if len(recipients) < 1:
self.env.log.info("no recipient for a ticket notification")
return
pcc = accaddrs
if public_cc:
pcc += ccaddrs
if toaddrs:
headers['To'] = ', '.join(toaddrs)
if pcc:
headers['Cc'] = ', '.join(pcc)
headers['Date'] = formatdate()
msg = MIMEText(body, 'plain')
# Message class computes the wrong type from MIMEText constructor,
# which does not take a Charset object as initializer. Reset the
# encoding type to force a new, valid evaluation
del msg['Content-Transfer-Encoding']
msg.set_charset(self._charset)
self.add_headers(msg, headers)
self.add_headers(msg, mime_headers)
NotificationSystem(self.env).send_email(self.from_email, recipients,
msg.as_string())