def email_signature(self, nodeid, msgid): ''' Add a signature to the e-mail with some useful information ''' # simplistic check to see if the url is valid, # then append a trailing slash if it is missing base = self.db.config.TRACKER_WEB if (not isinstance(base , type('')) or not (base.startswith('http://') or base.startswith('https://'))): web = "Configuration Error: TRACKER_WEB isn't a " \ "fully-qualified URL" else: if not base.endswith('/'): base = base + '/' web = base + self.classname + nodeid # ensure the email address is properly quoted email = straddr((self.db.config.TRACKER_NAME, self.db.config.TRACKER_EMAIL)) line = '_' * max(len(web)+2, len(email)) return '\n%s\n%s\n<%s>\n%s'%(line, email, web, line)
def email_signature(self, nodeid, msgid): ''' Add a signature to the e-mail with some useful information ''' # simplistic check to see if the url is valid, # then append a trailing slash if it is missing base = self.db.config.TRACKER_WEB if (not isinstance(base, type('')) or not (base.startswith('http://') or base.startswith('https://'))): web = "Configuration Error: TRACKER_WEB isn't a " \ "fully-qualified URL" else: if not base.endswith('/'): base = base + '/' web = base + self.classname + nodeid # ensure the email address is properly quoted email = straddr( (self.db.config.TRACKER_NAME, self.db.config.TRACKER_EMAIL)) line = '_' * max(len(web) + 2, len(email)) return '\n%s\n%s\n<%s>\n%s' % (line, email, web, line)
def send_message(self, nodeid, msgid, note, sendto, from_address=None, bcc_sendto=[]): '''Actually send the nominated message from this node to the sendto recipients, with the note appended. ''' users = self.db.user messages = self.db.msg files = self.db.file if msgid is None: inreplyto = None messageid = None else: inreplyto = messages.get(msgid, 'inreplyto') messageid = messages.get(msgid, 'messageid') # make up a messageid if there isn't one (web edit) if not messageid: # this is an old message that didn't get a messageid, so # create one messageid = "<%s.%s.%s%s@%s>" % (time.time(), random.random(), self.classname, nodeid, self.db.config.MAIL_DOMAIN) if msgid is not None: messages.set(msgid, messageid=messageid) # compose title cn = self.classname title = self.get(nodeid, 'title') or '%s message copy' % cn # figure author information if msgid: authid = messages.get(msgid, 'author') else: authid = self.db.getuid() authname = users.get(authid, 'realname') if not authname: authname = users.get(authid, 'username', '') authaddr = users.get(authid, 'address', '') if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL: authaddr = " <%s>" % straddr(('', authaddr)) elif authaddr: authaddr = "" # make the message body m = [''] # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'top': m.append(self.email_signature(nodeid, msgid)) # add author information if authid and self.db.config.MAIL_ADD_AUTHORINFO: if msgid and len(self.get(nodeid, 'messages')) == 1: m.append( _("New submission from %(authname)s%(authaddr)s:") % locals()) elif msgid: m.append( _("%(authname)s%(authaddr)s added the comment:") % locals()) else: m.append(_("Change by %(authname)s%(authaddr)s:") % locals()) m.append('') # add the content if msgid is not None: m.append(messages.get(msgid, 'content', '')) # get the files for this message message_files = [] if msgid: for fileid in messages.get(msgid, 'files'): # check the attachment size filename = self.db.filename('file', fileid, None) filesize = os.path.getsize(filename) if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE: message_files.append(fileid) else: base = self.db.config.TRACKER_WEB link = "".join((base, files.classname, fileid)) filename = files.get(fileid, 'name') m.append( _("File '%(filename)s' not attached - " "you can download it from %(link)s.") % locals()) # add the change note if note: m.append(note) # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom': m.append(self.email_signature(nodeid, msgid)) # encode the content as quoted-printable charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8') m = '\n'.join(m) if charset != 'utf-8': m = unicode(m, 'utf-8').encode(charset) content = cStringIO.StringIO(m) content_encoded = cStringIO.StringIO() quopri.encode(content, content_encoded, 0) content_encoded = content_encoded.getvalue() # make sure the To line is always the same (for testing mostly) sendto.sort() # make sure we have a from address if from_address is None: from_address = self.db.config.TRACKER_EMAIL # additional bit for after the From: "name" from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '') if from_tag: from_tag = ' ' + from_tag subject = '[%s%s] %s' % (cn, nodeid, title) author = (authname + from_tag, from_address) # send an individual message per recipient? if self.db.config.NOSY_EMAIL_SENDING != 'single': sendto = [[address] for address in sendto] else: sendto = [sendto] # now send one or more messages # TODO: I believe we have to create a new message each time as we # can't fiddle the recipients in the message ... worth testing # and/or fixing some day first = True for sendto in sendto: # create the message mailer = Mailer(self.db.config) message, writer = mailer.get_standard_message( sendto, subject, author) # set reply-to to the tracker tracker_name = self.db.config.TRACKER_NAME if charset != 'utf-8': tracker = unicode(tracker_name, 'utf-8').encode(charset) tracker_name = encode_header(tracker_name, charset) writer.addheader('Reply-To', straddr((tracker_name, from_address))) # message ids if messageid: writer.addheader('Message-Id', messageid) if inreplyto: writer.addheader('In-Reply-To', inreplyto) # Generate a header for each link or multilink to # a class that has a name attribute for propname, prop in self.getprops().items(): if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)): continue cl = self.db.getclass(prop.classname) if not 'name' in cl.getprops(): continue if isinstance(prop, hyperdb.Link): value = self.get(nodeid, propname) if value is None: continue values = [value] else: values = self.get(nodeid, propname) if not values: continue values = [cl.get(v, 'name') for v in values] values = ', '.join(values) writer.addheader( "X-Roundup-%s-%s" % (self.classname, propname), values) if not inreplyto: # Default the reply to the first message msgs = self.get(nodeid, 'messages') # Assume messages are sorted by increasing message number here if msgs[0] != nodeid: inreplyto = messages.get(msgs[0], 'messageid') if inreplyto: writer.addheader('In-Reply-To', inreplyto) # attach files if message_files: part = writer.startmultipartbody('mixed') part = writer.nextpart() part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain; charset=%s' % charset) body.write(content_encoded) for fileid in message_files: name = files.get(fileid, 'name') mime_type = files.get(fileid, 'type') content = files.get(fileid, 'content') part = writer.nextpart() if mime_type == 'text/plain': part.addheader('Content-Disposition', 'attachment;\n filename="%s"' % name) try: content.decode('ascii') except UnicodeError: # the content cannot be 7bit-encoded. # use quoted printable part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain') body.write(quopri.encodestring(content)) else: part.addheader('Content-Transfer-Encoding', '7bit') body = part.startbody('text/plain') body.write(content) else: # some other type, so encode it if not mime_type: # this should have been done when the file was saved mime_type = mimetypes.guess_type(name)[0] if mime_type is None: mime_type = 'application/octet-stream' part.addheader('Content-Disposition', 'attachment;\n filename="%s"' % name) part.addheader('Content-Transfer-Encoding', 'base64') body = part.startbody(mime_type) body.write(base64.encodestring(content)) writer.lastpart() else: writer.addheader('Content-Transfer-Encoding', 'quoted-printable') body = writer.startbody('text/plain; charset=%s' % charset) body.write(content_encoded) if first: mailer.smtp_send(sendto + bcc_sendto, message) else: mailer.smtp_send(sendto, message) first = False
def send_message(self, nodeid, msgid, note, sendto, from_address=None, bcc_sendto=[]): '''Actually send the nominated message from this node to the sendto recipients, with the note appended. ''' users = self.db.user messages = self.db.msg files = self.db.file if msgid is None: inreplyto = None messageid = None else: inreplyto = messages.get(msgid, 'inreplyto') messageid = messages.get(msgid, 'messageid') # make up a messageid if there isn't one (web edit) if not messageid: # this is an old message that didn't get a messageid, so # create one messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(), self.classname, nodeid, self.db.config.MAIL_DOMAIN) if msgid is not None: messages.set(msgid, messageid=messageid) # compose title cn = self.classname title = self.get(nodeid, 'title') or '%s message copy'%cn # figure author information if msgid: authid = messages.get(msgid, 'author') else: authid = self.db.getuid() authname = users.get(authid, 'realname') if not authname: authname = users.get(authid, 'username', '') authaddr = users.get(authid, 'address', '') if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL: authaddr = " <%s>" % straddr( ('',authaddr) ) elif authaddr: authaddr = "" # make the message body m = [''] # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'top': m.append(self.email_signature(nodeid, msgid)) # add author information if authid and self.db.config.MAIL_ADD_AUTHORINFO: if msgid and len(self.get(nodeid, 'messages')) == 1: m.append(_("New submission from %(authname)s%(authaddr)s:") % locals()) elif msgid: m.append(_("%(authname)s%(authaddr)s added the comment:") % locals()) else: m.append(_("Change by %(authname)s%(authaddr)s:") % locals()) m.append('') # add the content if msgid is not None: m.append(messages.get(msgid, 'content', '')) # get the files for this message message_files = [] if msgid : for fileid in messages.get(msgid, 'files') : # check the attachment size filename = self.db.filename('file', fileid, None) filesize = os.path.getsize(filename) if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE: message_files.append(fileid) else: base = self.db.config.TRACKER_WEB link = "".join((base, files.classname, fileid)) filename = files.get(fileid, 'name') m.append(_("File '%(filename)s' not attached - " "you can download it from %(link)s.") % locals()) # add the change note if note: m.append(note) # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom': m.append(self.email_signature(nodeid, msgid)) # encode the content as quoted-printable charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8') m = '\n'.join(m) if charset != 'utf-8': m = unicode(m, 'utf-8').encode(charset) content = cStringIO.StringIO(m) content_encoded = cStringIO.StringIO() quopri.encode(content, content_encoded, 0) content_encoded = content_encoded.getvalue() # make sure the To line is always the same (for testing mostly) sendto.sort() # make sure we have a from address if from_address is None: from_address = self.db.config.TRACKER_EMAIL # additional bit for after the From: "name" from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '') if from_tag: from_tag = ' ' + from_tag subject = '[%s%s] %s'%(cn, nodeid, title) author = (authname + from_tag, from_address) # send an individual message per recipient? if self.db.config.NOSY_EMAIL_SENDING != 'single': sendto = [[address] for address in sendto] else: sendto = [sendto] # now send one or more messages # TODO: I believe we have to create a new message each time as we # can't fiddle the recipients in the message ... worth testing # and/or fixing some day first = True for sendto in sendto: # create the message mailer = Mailer(self.db.config) message, writer = mailer.get_standard_message(sendto, subject, author) # set reply-to to the tracker tracker_name = self.db.config.TRACKER_NAME if charset != 'utf-8': tracker = unicode(tracker_name, 'utf-8').encode(charset) tracker_name = encode_header(tracker_name, charset) writer.addheader('Reply-To', straddr((tracker_name, from_address))) # message ids if messageid: writer.addheader('Message-Id', messageid) if inreplyto: writer.addheader('In-Reply-To', inreplyto) # Generate a header for each link or multilink to # a class that has a name attribute for propname, prop in self.getprops().items(): if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)): continue cl = self.db.getclass(prop.classname) if not 'name' in cl.getprops(): continue if isinstance(prop, hyperdb.Link): value = self.get(nodeid, propname) if value is None: continue values = [value] else: values = self.get(nodeid, propname) if not values: continue values = [cl.get(v, 'name') for v in values] values = ', '.join(values) writer.addheader("X-Roundup-%s-%s" % (self.classname, propname), values) if not inreplyto: # Default the reply to the first message msgs = self.get(nodeid, 'messages') # Assume messages are sorted by increasing message number here if msgs[0] != nodeid: inreplyto = messages.get(msgs[0], 'messageid') if inreplyto: writer.addheader('In-Reply-To', inreplyto) # attach files if message_files: part = writer.startmultipartbody('mixed') part = writer.nextpart() part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain; charset=%s'%charset) body.write(content_encoded) for fileid in message_files: name = files.get(fileid, 'name') mime_type = files.get(fileid, 'type') content = files.get(fileid, 'content') part = writer.nextpart() if mime_type == 'text/plain': part.addheader('Content-Disposition', 'attachment;\n filename="%s"'%name) try: content.decode('ascii') except UnicodeError: # the content cannot be 7bit-encoded. # use quoted printable part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain') body.write(quopri.encodestring(content)) else: part.addheader('Content-Transfer-Encoding', '7bit') body = part.startbody('text/plain') body.write(content) else: # some other type, so encode it if not mime_type: # this should have been done when the file was saved mime_type = mimetypes.guess_type(name)[0] if mime_type is None: mime_type = 'application/octet-stream' part.addheader('Content-Disposition', 'attachment;\n filename="%s"'%name) part.addheader('Content-Transfer-Encoding', 'base64') body = part.startbody(mime_type) body.write(base64.encodestring(content)) writer.lastpart() else: writer.addheader('Content-Transfer-Encoding', 'quoted-printable') body = writer.startbody('text/plain; charset=%s'%charset) body.write(content_encoded) if first: mailer.smtp_send(sendto + bcc_sendto, message) else: mailer.smtp_send(sendto, message) first = False