def decorate_message(self, event, message, decorates=None): if event.realm == 'ticket': if 'status' in event.changes: action = 'Status -> %s' % (event.target['status']) template = NewTextTemplate( self.ticket_email_subject.encode('utf8')) # Create a fallback for invalid custom Genshi template in option. default_template = NewTextTemplate( Option.registry[ ('announcer', 'ticket_email_subject') ].default.encode('utf8')) try: subject = template.generate( ticket=event.target, event=event, action=event.category ).render('text', encoding=None) except TemplateError: # Use fallback template. subject = default_template.generate( ticket=event.target, event=event, action=event.category ).render('text', encoding=None) prefix = self.config.get('announcer', 'email_subject_prefix') if prefix == '__default__': prefix = '[%s] ' % self.env.project_name if prefix: subject = "%s%s" % (prefix, subject) if event.category != 'created': subject = 'Re: %s' % subject set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == 'ticket': for k in ('id', 'priority', 'severity'): name = 'X-Announcement-%s'%k.capitalize() set_header(message, name, event.target[k]) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == 'ticket': for k in ('id', 'priority', 'severity'): name = 'X-Announcement-%s' % k.capitalize() set_header(message, name, event.target[k]) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == 'ticket': if 'status' in event.changes: action = 'Status -> %s' % (event.target['status']) template = NewTextTemplate( self.ticket_email_subject.encode('utf8')) # Create a fallback for invalid custom Genshi template in option. default_template = NewTextTemplate(Option.registry[( 'announcer', 'ticket_email_subject')].default.encode('utf8')) try: subject = template.generate(ticket=event.target, event=event, action=event.category).render( 'text', encoding=None) except TemplateError: # Use fallback template. subject = default_template.generate( ticket=event.target, event=event, action=event.category).render('text', encoding=None) prefix = self.config.get('announcer', 'email_subject_prefix') if prefix == '__default__': prefix = '[%s] ' % self.env.project_name if prefix: subject = "%s%s" % (prefix, subject) if event.category != 'created': subject = 'Re: %s' % subject set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == "acct_mgr": prjname = self.env.project_name subject = '[%s] %s: %s' \ % (prjname, event.category, event.username) set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == "blog": template = NewTextTemplate(self.blog_email_subject.encode('utf8')) subject = template.generate(blog=event.blog_post, action=event.category).render( 'text', encoding=None) set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == "blog": template = NewTextTemplate(self.blog_email_subject.encode('utf8')) subject = template.generate( blog=event.blog_post, action=event.category ).render('text', encoding=None) set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): for k, v in {'Cc': self.always_cc, 'Bcc': self.always_bcc}.items(): if v: self.log.debug("StaticEmailDecorator added '%s' because of " "rule: email_always_%s", v, k.lower()), if message[k] and str(message[k]).split(','): recipients = ", ".join([str(message[k]), v]) else: recipients = v set_header(message, k, recipients) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): for k, v in {'Cc': self.always_cc, 'Bcc': self.always_bcc}.items(): if v: self.log.debug("StaticEmailDecorator added '%s' " "because of rule: email_always_%s" % (v, k.lower())), if message[k] and len(str(message[k]).split(',')) > 0: recips = ", ".join([str(message[k]), v]) else: recips = v set_header(message, k, recips) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): """ Added headers to the outgoing email to track it's relationship with a resource. Reply-To will encode a UID as a email parameter so replies to the resource announcement can be handled by an ITracGateEmailHandler. """ if event.realm in self.reply_to_realms: name, email_addr = parseaddr(str(message['Reply-To'])) uid = uid_encode(self.env.abs_href(), event.realm, event.target) m = ADDR_REGEX.match(email_addr) new_email_addr = '%s+%s@%s'%(m.group(1), uid, m.group(2)) new_addr = formataddr((name, new_email_addr)) set_header(message, 'Reply-To', new_email_addr) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == 'wiki': template = NewTextTemplate(self.wiki_email_subject.encode('utf8')) subject = template.generate(page=event.target, event=event, action=event.category).render( 'text', encoding=None) prefix = self.config.get('announcer', 'email_subject_prefix') if prefix == '__default__': prefix = '[%s] ' % self.env.project_name if prefix: subject = "%s%s" % (prefix, subject) if event.category != 'created': subject = 'Re: %s' % subject set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == 'wiki': template = NewTextTemplate(self.wiki_email_subject.encode('utf8')) subject = template.generate( page=event.target, event=event, action=event.category ).render('text', encoding=None) prefix = self.config.get('announcer', 'email_subject_prefix') if prefix == '__default__': prefix = '[%s] ' % self.env.project_name if prefix: subject = "%s%s" % (prefix, subject) if event.category != 'created': subject = 'Re: %s' % subject set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == "bitten": build_id = str(event.target.id) build_link = self._build_link(event.target) subject = '[%s Build] %s [%s] %s' % (self.readable_states.get( event.target.status, event.target.status), self.env.project_name, event.target.rev, event.target.config) set_header(message, 'X-Trac-Build-ID', build_id) set_header(message, 'X-Trac-Build-URL', build_link) set_header(message, 'Subject', subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): if event.realm == "bitten": build_id = str(event.target.id) build_link = self._build_link(event.target) subject = "[%s Build] %s [%s] %s" % ( self.readable_states.get(event.target.status, event.target.status), self.env.project_name, event.target.rev, event.target.config, ) set_header(message, "X-Trac-Build-ID", build_id) set_header(message, "X-Trac-Build-URL", build_link) set_header(message, "Subject", subject) return next_decorator(event, message, decorates)
def decorate_message(self, event, message, decorates=None): """ Added headers to the outgoing email to track it's relationship with a ticket. References, In-Reply-To and Message-ID are just so email clients can make sense of the threads. """ if to_unicode(event.realm) in self.supported_realms: uid = uid_encode(self.env.abs_href(), event.realm, event.target) email_from = self.config.get('announcer', 'email_from', 'localhost') _, email_addr = parseaddr(email_from) host = re.sub('^.+@', '', email_addr) mymsgid = msgid(uid, host) if event.category == 'created': set_header(message, 'Message-ID', mymsgid) else: set_header(message, 'In-Reply-To', mymsgid) set_header(message, 'References', mymsgid) return next_decorator(event, message, decorates)
def send_as_email(self, req, sender, recipients, subject, text, mode, *resources): """ `authname` Trac username of sender `sender` Tuple of (real name, email address) `recipients` List of (real name, email address) recipient address tuples `subject` The e-mail subject `text` The text body of the e-mail `files` List of paths to the files to send """ assert len(resources) > 0, 'Nothing to send!' mailsys = self.distributor(self.env) from_addr = sender[1] root = MIMEMultipart('related') root.set_charset(self._make_charset()) root.preamble = 'This is a multi-part message in MIME format.' headers = {} recp = [r[1] for r in recipients] headers['Subject'] = subject headers['To'] = ', '.join(recp) headers['From'] = from_addr headers['Date'] = formatdate() authname = req.authname files = [] links = [] attachments = [] mimeview = Mimeview(self.env) for r in resources: repo = RepositoryManager(self.env).get_repository(r.parent.id) n = repo.get_node(r.id, rev=r.version) files.append(n.path) f = os.path.join(repo.repos.path, n.path) if mode in (self.LINKS_ONLY, self.LINKS_ATTACHMENTS): links.append((req.abs_href.browser(repo.reponame or None, n.path, format='raw'), os.path.basename(f))) if mode in (self.ATTACHMENTS_ONLY, self.LINKS_ATTACHMENTS): content = n.get_content().read() mtype = n.get_content_type() or mimeview.get_mimetype(f, content) if not mtype: mtype = 'application/octet-stream' if '; charset=' in mtype: # What to use encoding for? mtype, encoding = mtype.split('; charset=', 1) attachments.append(os.path.basename(f)) maintype, subtype = mtype.split('/', 1) part = MIMEBase(maintype, subtype) part.set_payload(content) part.add_header('content-disposition', 'attachment', filename=os.path.basename(f)) encode_base64(part) root.attach(part) body = self._format_email(authname, sender, recipients, subject, text, mode, links, attachments) msg = MIMEText(body, 'html', 'utf-8') root.attach(msg) del root['Content-Transfer-Encoding'] for k, v in headers.items(): set_header(root, k, v) email = (from_addr, recp, root.as_string()) # Write mail to /tmp #import logging #if self.log.isEnabledFor(logging.DEBUG): # (fd, tmpname) = mkstemp() # os.write(fd, email[2]) # os.close(fd) # self.log.debug('Wrote mail from %s to %s to %s', email[0], email[1], tmpname) self.log.info('Sending mail with items %s from %s to %s', resources, from_addr, recp) try: if using_announcer: if mailsys.use_threaded_delivery: mailsys.get_delivery_queue().put(email) else: mailsys.send(*email) else: mailsys.send_email(*email) except Exception, e: raise TracError(e.message)
def _do_send(self, transport, event, format, recipients, formatter, pubkey_ids=[]): output = formatter.format(transport, event.realm, format, event) # DEVEL: force message body plaintext style for crypto operations if self.crypto != '' and pubkey_ids != []: if self.crypto == 'sign': output = self.enigma.sign(output, self.private_key) elif self.crypto == 'encrypt': output = self.enigma.encrypt(output, pubkey_ids) elif self.crypto == 'sign,encrypt': output = self.enigma.sign_encrypt(output, pubkey_ids, self.private_key) self.log.debug(output) self.log.debug(_("EmailDistributor crypto operaton successful.")) alternate_output = None else: alternate_style = formatter.alternative_style_for( transport, event.realm, format) if alternate_style: alternate_output = formatter.format(transport, event.realm, alternate_style, event) else: alternate_output = None # sanity check if not self._charset.body_encoding: try: dummy = output.encode('ascii') except UnicodeDecodeError: raise TracError(_("Ticket contains non-ASCII chars. " \ "Please change encoding setting")) rootMessage = MIMEMultipart("related") headers = dict() if self.set_message_id: # A different, predictable, but still sufficiently unique # message ID will be generated as replacement in # announcer.email_decorators.generic.ThreadingEmailDecorator # for email threads to work. headers['Message-ID'] = self._message_id(event.realm) headers['Date'] = formatdate() from_header = formataddr((self.from_name or self.env.project_name, self.email_from)) headers['From'] = from_header headers['To'] = '"%s"' % (self.to) if self.use_public_cc: headers['Cc'] = ', '.join([x[2] for x in recipients if x]) headers['Reply-To'] = self.replyto for k, v in headers.iteritems(): set_header(rootMessage, k, v) rootMessage.preamble = 'This is a multi-part message in MIME format.' if alternate_output: parentMessage = MIMEMultipart('alternative') rootMessage.attach(parentMessage) alt_msg_format = 'html' in alternate_style and 'html' or 'plain' msgText = MIMEText(alternate_output, alt_msg_format) parentMessage.attach(msgText) else: parentMessage = rootMessage msg_format = 'html' in format and 'html' or 'plain' msgText = MIMEText(output, msg_format) del msgText['Content-Transfer-Encoding'] msgText.set_charset(self._charset) parentMessage.attach(msgText) decorators = self._get_decorators() if len(decorators) > 0: decorator = decorators.pop() decorator.decorate_message(event, rootMessage, decorators) recip_adds = [x[2] for x in recipients if x] # Append any to, cc or bccs added to the recipient list for field in ('To', 'Cc', 'Bcc'): if rootMessage[field] and \ len(str(rootMessage[field]).split(',')) > 0: for addy in str(rootMessage[field]).split(','): self._add_recipient(recip_adds, addy) # replace with localized bcc hint if headers['To'] == 'undisclosed-recipients: ;': set_header(rootMessage, 'To', _('undisclosed-recipients: ;')) self.log.debug("Content of recip_adds: %s" % (recip_adds)) package = (from_header, recip_adds, rootMessage.as_string()) start = time.time() if self.use_threaded_delivery: self.get_delivery_queue().put(package) else: self.send(*package) stop = time.time() self.log.debug("EmailDistributor took %s seconds to send."\ %(round(stop-start,2)))
def _do_send(self, transport, event, format, recipients, formatter, pubkey_ids=None): pubkey_ids = pubkey_ids or [] # Prepare sender for use in IEmailSender component and message header. from_header = formataddr( (self.from_name and self.from_name or self.env.project_name, self.email_from) ) headers = dict() headers['Message-ID'] = self._message_id(event.realm) headers['Date'] = formatdate() headers['From'] = from_header headers['Reply-To'] = self.reply_to recip_adds = [x[2] for x in recipients if x] if self.use_public_cc: headers['Cc'] = ', '.join(recip_adds) else: # Use localized Bcc: hint for default To: content. if self.to == self.to_default: headers['To'] = _("undisclosed-recipients: ;") else: headers['To'] = '"%s"' % self.to if self.to: recip_adds += [self.to] if not recip_adds: self.log.debug("EmailDistributor stopped (no recipients).") return self.log.debug("All email recipients: %s", recip_adds) root_message = MIMEMultipart('related') # Write header data into message object. for k, v in headers.iteritems(): set_header(root_message, k, v) output = formatter.format(transport, event.realm, format, event) # DEVEL: Currently crypto operations work with format text/plain only. alternate_output = None alternate_style = [] if self.crypto != '' and pubkey_ids: if self.crypto == 'sign': output = self.enigma.sign(output, self.private_key) elif self.crypto == 'encrypt': output = self.enigma.encrypt(output, pubkey_ids) elif self.crypto == 'sign,encrypt': output = self.enigma.sign_encrypt(output, pubkey_ids, self.private_key) self.log.debug(output) self.log.debug("EmailDistributor crypto operation successful.") else: alternate_style = formatter.alternative_style_for( transport, event.realm, format ) if alternate_style: alternate_output = formatter.format( transport, event.realm, alternate_style, event ) # Sanity check for suitable encoding setting. if not self._charset.body_encoding: try: output.encode('ascii') except UnicodeDecodeError: raise TracError(_("Ticket contains non-ASCII chars. Please " "change encoding setting")) root_message.preamble = "This is a multi-part message in MIME format." if alternate_output: parent_message = MIMEMultipart('alternative') root_message.attach(parent_message) alt_msg_format = 'html' in alternate_style and 'html' or 'plain' if isinstance(alternate_output, unicode): alternate_output = alternate_output.encode('utf-8') msg_text = MIMEText(alternate_output, alt_msg_format) msg_text.set_charset(self._charset) parent_message.attach(msg_text) else: parent_message = root_message msg_format = 'html' in format and 'html' or 'plain' if isinstance(output, unicode): output = output.encode('utf-8') msg_text = MIMEText(output, msg_format) del msg_text['Content-Transfer-Encoding'] msg_text.set_charset(self._charset) # According to RFC 2046, the last part of a multipart message is best # and preferred. parent_message.attach(msg_text) # DEVEL: Decorators can interfere with crypto operation here. Fix it. decorators = self._get_decorators() if decorators: decorator = decorators.pop() decorator.decorate_message(event, root_message, decorators) package = (from_header, recip_adds, root_message.as_string()) start = time.time() if self.use_threaded_delivery: self.get_delivery_queue().put(package) else: self.send(*package) stop = time.time() self.log.debug("EmailDistributor took %s seconds to send.", round(stop - start, 2))
def decorate_message(self, event, message, decorators): mailer = 'AnnouncerPlugin v%s on Trac v%s' % (announcer_version, trac_version) set_header(message, 'Auto-Submitted', 'auto-generated') set_header(message, 'Precedence', 'bulk') set_header(message, 'X-Announcer-Version', announcer_version) set_header(message, 'X-Mailer', mailer) set_header(message, 'X-Trac-Announcement-Realm', event.realm) set_header(message, 'X-Trac-Project', self.env.project_name) set_header(message, 'X-Trac-Version', trac_version) return next_decorator(event, message, decorators)
def _do_send(self, transport, event, format, recipients, formatter, pubkey_ids=[]): # Prepare sender for use in IEmailSender component and message header. from_header = formataddr( (self.from_name and self.from_name or self.env.project_name, self.email_from) ) headers = dict() headers['Message-ID'] = self._message_id(event.realm) headers['Date'] = formatdate() headers['From'] = from_header headers['Reply-To'] = self.reply_to recip_adds = [x[2] for x in recipients if x] if self.use_public_cc: headers['Cc'] = ', '.join(recip_adds) else: # Use localized Bcc: hint for default To: content. if self.to == self.to_default: headers['To'] = _('undisclosed-recipients: ;') else: headers['To'] = '"%s"' % self.to if self.to: recip_adds += [self.to] if not recip_adds: self.log.debug( "EmailDistributor stopped (no recipients)." ) return self.log.debug("All email recipients: %s" % recip_adds) rootMessage = MIMEMultipart("related") # TODO: Is this good? (from jabber branch) #rootMessage.set_charset(self._charset) # Write header data into message object. for k, v in headers.iteritems(): set_header(rootMessage, k, v) output = formatter.format(transport, event.realm, format, event) # DEVEL: Currently crypto operations work with format text/plain only. if self.crypto != '' and pubkey_ids != []: if self.crypto == 'sign': output = self.enigma.sign(output, self.private_key) elif self.crypto == 'encrypt': output = self.enigma.encrypt(output, pubkey_ids) elif self.crypto == 'sign,encrypt': output = self.enigma.sign_encrypt(output, pubkey_ids, self.private_key) self.log.debug(output) self.log.debug("EmailDistributor crypto operation successful.") alternate_output = None else: alternate_style = formatter.alternative_style_for( transport, event.realm, format ) if alternate_style: alternate_output = formatter.format( transport, event.realm, alternate_style, event ) else: alternate_output = None # Sanity check for suitable encoding setting. if not self._charset.body_encoding: try: dummy = output.encode('ascii') except UnicodeDecodeError: raise TracError(_("Ticket contains non-ASCII chars. " \ "Please change encoding setting")) rootMessage.preamble = 'This is a multi-part message in MIME format.' if alternate_output: parentMessage = MIMEMultipart('alternative') rootMessage.attach(parentMessage) alt_msg_format = 'html' in alternate_style and 'html' or 'plain' msgText = MIMEText(alternate_output, alt_msg_format) msgText.set_charset(self._charset) parentMessage.attach(msgText) else: parentMessage = rootMessage msg_format = 'html' in format and 'html' or 'plain' msgText = MIMEText(output, msg_format) del msgText['Content-Transfer-Encoding'] msgText.set_charset(self._charset) # According to RFC 2046, the last part of a multipart message is best # and preferred. parentMessage.attach(msgText) # DEVEL: Decorators can interfere with crypto operation here. Fix it. decorators = self._get_decorators() if len(decorators) > 0: decorator = decorators.pop() decorator.decorate_message(event, rootMessage, decorators) package = (from_header, recip_adds, rootMessage.as_string()) start = time.time() if self.use_threaded_delivery: self.get_delivery_queue().put(package) else: self.send(*package) stop = time.time() self.log.debug("EmailDistributor took %s seconds to send." % (round(stop - start, 2)))
def decorate_message(self, event, message, decorators): mailer = 'AnnouncerPlugin v%s on Trac v%s' % ( announcer_version, trac_version ) set_header(message, 'Auto-Submitted', 'auto-generated') set_header(message, 'Precedence', 'bulk') set_header(message, 'X-Announcer-Version', announcer_version) set_header(message, 'X-Mailer', mailer) set_header(message, 'X-Trac-Announcement-Realm', event.realm) set_header(message, 'X-Trac-Project', self.env.project_name) set_header(message, 'X-Trac-Version', trac_version) return next_decorator(event, message, decorators)
def _do_send(self, transport, event, format, recipients, formatter, pubkey_ids=[]): output = formatter.format(transport, event.realm, format, event) # DEVEL: force message body plaintext style for crypto operations if self.crypto != '' and pubkey_ids != []: if self.crypto == 'sign': output = self.enigma.sign(output, self.private_key) elif self.crypto == 'encrypt': output = self.enigma.encrypt(output, pubkey_ids) elif self.crypto == 'sign,encrypt': output = self.enigma.sign_encrypt(output, pubkey_ids, self.private_key) self.log.debug(output) self.log.debug(_("EmailDistributor crypto operaton successful.")) alternate_output = None else: alternate_style = formatter.alternative_style_for( transport, event.realm, format ) if alternate_style: alternate_output = formatter.format( transport, event.realm, alternate_style, event ) else: alternate_output = None # sanity check if not self._charset.body_encoding: try: dummy = output.encode('ascii') except UnicodeDecodeError: raise TracError(_("Ticket contains non-ASCII chars. " \ "Please change encoding setting")) rootMessage = MIMEMultipart("related") headers = dict() if self.set_message_id: # A different, predictable, but still sufficiently unique # message ID will be generated as replacement in # announcer.email_decorators.generic.ThreadingEmailDecorator # for email threads to work. headers['Message-ID'] = self._message_id(event.realm) headers['Date'] = formatdate() from_header = formataddr(( self.from_name or self.env.project_name, self.email_from )) headers['From'] = from_header headers['To'] = '"%s"'%(self.to) if self.use_public_cc: headers['Cc'] = ', '.join([x[2] for x in recipients if x]) headers['Reply-To'] = self.replyto for k, v in headers.iteritems(): set_header(rootMessage, k, v) rootMessage.preamble = 'This is a multi-part message in MIME format.' if alternate_output: parentMessage = MIMEMultipart('alternative') rootMessage.attach(parentMessage) alt_msg_format = 'html' in alternate_style and 'html' or 'plain' msgText = MIMEText(alternate_output, alt_msg_format) parentMessage.attach(msgText) else: parentMessage = rootMessage msg_format = 'html' in format and 'html' or 'plain' msgText = MIMEText(output, msg_format) del msgText['Content-Transfer-Encoding'] msgText.set_charset(self._charset) parentMessage.attach(msgText) decorators = self._get_decorators() if len(decorators) > 0: decorator = decorators.pop() decorator.decorate_message(event, rootMessage, decorators) recip_adds = [x[2] for x in recipients if x] # Append any to, cc or bccs added to the recipient list for field in ('To', 'Cc', 'Bcc'): if rootMessage[field] and \ len(str(rootMessage[field]).split(',')) > 0: for addy in str(rootMessage[field]).split(','): self._add_recipient(recip_adds, addy) # replace with localized bcc hint if headers['To'] == 'undisclosed-recipients: ;': set_header(rootMessage, 'To', _('undisclosed-recipients: ;')) self.log.debug("Content of recip_adds: %s" %(recip_adds)) package = (from_header, recip_adds, rootMessage.as_string()) start = time.time() if self.use_threaded_delivery: self.get_delivery_queue().put(package) else: self.send(*package) stop = time.time() self.log.debug("EmailDistributor took %s seconds to send."\ %(round(stop-start,2)))