Пример #1
0
def clean_comment(comment):
    comment = EmailReplyParser.parse_reply(comment)

    block = re.compile(r'`{3,}.*?`{3,}|<pre>.*?</pre>|^(?: {4,}|\t+).*?$', re.MULTILINE | re.DOTALL)
    comment = block.sub('', comment)

    inline = re.compile(r'`.*?`', re.MULTILINE | re.DOTALL)
    comment = inline.sub('', comment)

    link = re.compile(r'!?\[(.*?)\]\(.*?\)', re.MULTILINE | re.DOTALL)
    comment = link.sub(r'\1', comment)

    url = re.compile(r'\(?https?://\S+\)?', re.MULTILINE | re.DOTALL)
    comment = url.sub('', comment)

    code = re.compile(r'(?:[A-Z][a-z]*){2,}(?:::(?:[A-Z][a-z]*)+)*(?:\.|#\S+)*', re.MULTILINE | re.DOTALL)
    comment = code.sub('', comment)

    ruby = re.compile(r'(?:\w+/)*\w*\.rb', re.MULTILINE | re.DOTALL)
    comment = ruby.sub('', comment)

    emoji = re.compile(r':\S+:', re.MULTILINE | re.DOTALL)
    comment = emoji.sub('', comment)

    return comment
Пример #2
0
	def get_summary_message(self):
		'''Return summary of replies as HTML'''
		settings = frappe.get_doc('Daily Work Summary Settings')

		replies = frappe.get_all('Communication', fields=['content', 'text_content', 'sender'],
			filters=dict(reference_doctype=self.doctype, reference_name=self.name,
				communication_type='Communication', sent_or_received='Received'),
				order_by='creation asc')

		did_not_reply = self.email_sent_to.split()

		for d in replies:
			d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender},
				"employee_name") or d.sender
			if d.sender in did_not_reply:
				did_not_reply.remove(d.sender)
			if d.text_content:
				d.content = markdown(EmailReplyParser.parse_reply(d.text_content))


		did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email}, "employee_name") or email)
			for email in did_not_reply]

		return frappe.render_template(self.get_summary_template(),
			dict(replies=replies,
				original_message=settings.message,
				title=_('Daily Work Summary for {0}'.format(formatdate(self.creation))),
				did_not_reply= ', '.join(did_not_reply) or '',
				did_not_reply_title = _('No replies from')))
Пример #3
0
def extract_reply(msg):
    """Extracts the portion of an email that should contain commands.
    """
    for part in msg.walk():
        if part.get_content_type() == 'text/plain':
            content = part.get_payload(decode=True)
            return EmailReplyParser.parse_reply(content)
Пример #4
0
def incoming_letter_email():
	body = EmailReplyParser.parse_reply(unicode(request.form.get('text')).encode('ascii','xmlcharrefreplace'))
	body = '\n'.join(body.split('\n')).replace("\n","<br />")
	regexp = re.findall(r'[\w\.-]+@[\w\.-]+',request.form.get('from'))

	try:
		attachments = int(request.form.get('attachments'))
	except Exception:
		attachments = 0

	if len(regexp) > 0 and len(regexp[-1]) > 0:
		username = regexp[-1].lower()
	else:
		return_bad_params(username)
		return Response(response=jfail("missing parameters"), status=200)

	to_name = request.form.get('to')
	to_address = unicode(request.form.get('subject')).encode('ascii','xmlcharrefreplace').lower().replace("fw:","").replace("re:","").strip()

	if None in [body,username,to_name,to_address]:
		return_bad_params(username)
		return Response(response=jfail("missing parameters"), status=200)

	user = User(username)
	if user.is_valid():
		send_letter(user,to_name,to_address,body,attachments)
	else:
		return_unknown_sender(username)
		return Response(response=jfail("unknown sender"), status=200)

	return Response(response=jsuccess(), status=200)
Пример #5
0
def sentiment_sliding(messages, window=1000, shift=20):
    allwords = []
    data = {}
    for m in messages:
        if "\\Sent" not in m.get("folders", tuple()):
            continue
        if not m.get("body") or not m["body"].get("content"):
            continue
        allwords.append(EmailReplyParser.parse_reply(m["body"]["content"]))

    allwords = " ".join(allwords)
    allwords = allwords.encode("ascii", "ignore")
    allwords = allwords.split()

    current_window = 0
    next_window = window
    print "number of words", len(allwords)
    while True:
        if len(allwords) < next_window:
            print "sliding-sentiment reached end at lengths:%s" % len(allwords)
            break
        print "sliding-sentiment start:%s end:%s" % (current_window, next_window)
        data[current_window] = " ".join(allwords[current_window:next_window])
        data[current_window] = indicoio.sentiment(data[current_window])
        print data[current_window]
        current_window += shift
        next_window += shift
    return data
Пример #6
0
def collect_project_status():
	for data in frappe.get_all("Project Update",
		{'date': today(), 'sent': 0}):
		replies = frappe.get_all('Communication',
			fields=['content', 'text_content', 'sender'],
			filters=dict(reference_doctype="Project Update",
				reference_name=data.name,
				communication_type='Communication',
				sent_or_received='Received'),
			order_by='creation asc')

		for d in replies:
			doc = frappe.get_doc("Project Update", data.name)
			user_data = frappe.db.get_values("User", {"email": d.sender},
				["full_name", "user_image", "name"], as_dict=True)[0]

			doc.append("users", {
				'user': user_data.name,
				'full_name': user_data.full_name,
				'image': user_data.user_image,
				'project_status': frappe.utils.md_to_html(
					EmailReplyParser.parse_reply(d.text_content) or d.content
				)
			})

			doc.save(ignore_permissions=True)
    def post(self, request):
        token = request.POST['token']
        signature = request.POST['signature']
        timestamp = request.POST['timestamp']

        key = options.get('mail.mailgun-api-key')
        if not key:
            logging.error('mail.mailgun-api-key is not set')
            return HttpResponse(status=500)

        if not self.verify(key, token, timestamp, signature):
            logging.info('Unable to verify signature for mailgun request')
            return HttpResponse(status=403)

        to_email = parseaddr(request.POST['To'])[1]
        from_email = parseaddr(request.POST['From'])[1]

        try:
            group_id = email_to_group_id(to_email)
        except Exception:
            logging.info('%r is not a valid email address', to_email)
            return HttpResponse(status=500)

        payload = EmailReplyParser.parse_reply(request.POST['body-plain']).strip()
        if not payload:
            # If there's no body, we don't need to go any further
            return HttpResponse(status=200)

        process_inbound_email.delay(from_email, group_id, payload)

        return HttpResponse(status=201)
Пример #8
0
 def text_reply(self):
     """
     >>> email_msg = MessageDecorator.from_file('test/test_single.eml')
     >>> email_msg.text_reply()
     'plain word1, word2, word2, word3, word3, word3'
     """
     text = self.text()
     return EmailReplyParser.parse_reply(text)
Пример #9
0
    def instanciate_answer(self, lines):
        answer = self.answer_class()
        msgtxt = ''.join(lines)

        msg = email.message_from_string(msgtxt)
        temporary, permanent = all_failures(msg)

        if permanent:
            answer.is_bounced = True
            answer.email_from = scan_message(msg).pop()
        elif temporary:
            raise TemporaryFailure
        else:
            answer.email_from = msg["From"]

        the_recipient = msg["To"]
        answer.subject = msg["Subject"]
        answer.when = msg["Date"]
        answer.message_id = msg["Message-ID"]

        the_recipient = re.sub(r"\n", "", the_recipient)

        regex = re.compile(r".*[\+\-](.*)@.*")
        the_match = regex.match(the_recipient)
        if the_match is None:
            raise CouldNotFindIdentifier
        answer.email_to = the_recipient
        answer.outbound_message_identifier = the_match.groups()[0]
        logging.info("Reading the parts")
        for part in msg.walk():
            logging.info("Part of type " + part.get_content_type())

            content_type_attr = self.content_types_attrs.get(part.get_content_type())
            if content_type_attr:
                charset = part.get_content_charset() or "ISO-8859-1"
                data = part.get_payload(decode=True).decode(charset)

                setattr(
                    answer,
                    content_type_attr,
                    EmailReplyParser.parse_reply(data).strip(),
                    )
            else:
                self.handle_not_processed_part(part)

            attachment = self.parse_attachment(part)
            if attachment:
                answer.add_attachment(attachment)

        log = 'New incoming email from %(from)s sent on %(date)s with subject %(subject)s and content %(content)s'
        log = log % {
            'from': answer.email_from,
            'date': answer.when,
            'subject': answer.subject,
            'content': answer.content_text,
            }
        logging.info(log)
        return answer
Пример #10
0
    def get_reply_message(message):
        from email_reply_parser import EmailReplyParser

        for payload in message.get_payload():
            if payload.get_content_type() == 'text/plain':
                content = payload.get_payload()
                break

        return EmailReplyParser.parse_reply(content)
Пример #11
0
 def extract_body(self, message):
     body = None
     for part in message.walk():
         if part.get_content_type() == 'text/plain':
             text = part.get_payload()
             try:
                 body = erp.parse_reply(text)
             except:
                 body = text
     return body
Пример #12
0
def get_data(start=0):
	#frappe.only_for('Employee', 'System Manager')
	data = frappe.get_all('Communication',
		fields=('content', 'text_content', 'sender', 'creation'),
		filters=dict(reference_doctype='Daily Work Summary'),
		order_by='creation desc', limit=40, start=start)

	for d in data:
		d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender},
			"employee_name") or d.sender
		if d.text_content:
			d.content = markdown(EmailReplyParser.parse_reply(d.text_content))

	return data
Пример #13
0
    def handle(self, lines):
        answer = self.answer_class()
        msgtxt = ''.join(lines)

        msg = email.message_from_string(msgtxt)
        temporary, permanent = all_failures(msg)
        
        
        if temporary or permanent:
            answer.is_bounced = True
            answer.email_from = scan_message(msg).pop()
        else:
            answer.email_from = msg["From"]


        the_recipient = msg["To"]
        answer.subject = msg["Subject"]
        answer.when = msg["Date"]

        the_recipient = re.sub(r"\n", "", the_recipient)

        regex = re.compile(r".*[\+\-](.*)@.*")

        answer.outbound_message_identifier = regex.match(the_recipient).groups()[0]
        charset = msg.get_charset()
        logging.info("Reading the parts")
        for part in msg.walk():
            logging.info("Part of type "+part.get_content_type())
            if part.get_content_type() == 'text/plain':
                charset = part.get_content_charset()
                if not charset:
                    charset = "ISO-8859-1"
                data = part.get_payload(decode=True).decode(charset)
                text = EmailReplyParser.parse_reply(data)
                text.strip()
                answer.content_text = text
        #logging stuff
        
        log = 'New incoming email from %(from)s sent on %(date)s with subject %(subject)s and content %(content)s'
        log = log % {
            'from':answer.email_from,
            'date':answer.when,
            'subject':answer.subject,
            'content':answer.content_text
            }
        logging.info(log)
        return answer
Пример #14
0
def parse(shitmail):
    message_id = shitmail['Message-ID']
    date = datetime.fromtimestamp(mktime(email.utils.parsedate(shitmail['Date'])))
    from_ = shitmail['From']
    subject = shitmail['Subject']
    body = extract_body(shitmail)
    cleanup_body = EmailReplyParser.parse_reply(body).strip()
    in_reply_to = shitmail['In-Reply-To']

    c = model.session.query(model.Post).filter(model.Post.message_id == message_id).count()
    if c > 0:
        logging.error("double message-id: " + message_id)
        return

    post = model.Post(message_id, date, from_, subject, cleanup_body, in_reply_to)
    model.session.add(post)
    model.session.commit()
Пример #15
0
    def createEmailToCentralServer(self, subj, body, html, email, attachments=[]):
        '''
        Make an email with the project key and issue id and subj and body
        
        return it.
        '''
        exists, match, subj = self.findProjectIssueInformation(subj)
        description = ""
        try:
            description = body[0].get_payload()
        except:
            description = body
        
        description_html = html
        issue = None
        thisUser = None

        try:
            thisUser = User.objects.get(email=email)
        except:
            thisUser = User(username=email, first_name='Anonymous', last_name='User', email=email)
            thisUser.set_unusable_password()
            thisUser.save()
        
        try:
            if exists:
                print "Issue exists, attempting to create a comment"
                
                # This strips the HTML twice. Once, to remove the HTML before stripping the reply
                # email and once more incase there is HTML in the reply email...
                if description is None or description == "":
                    description = strip_html(description_html)
                description = EmailReplyParser.parse_reply(description).strip()
                comment = self.createComment(match, strip_html(description).strip(), thisUser, attachments)
                issue = comment.issue
            else:
                print "Issue doesn't exist, attempting to create an issue"
                proj = self.findClosestProject(subj+description)
                print proj.key, subj, description, thisUser
                issue = self.createIssue(proj, subj, description, description_html, thisUser, attachments)
        except Exception, e:
            print e
Пример #16
0
    def post(self, request):
        token = request.POST['token']
        signature = request.POST['signature']
        timestamp = request.POST['timestamp']

        key = options.get('mail.mailgun-api-key')
        if not key:
            logger.error('mailgun.api-key-missing')
            return HttpResponse(status=500)

        if not self.verify(key, token, timestamp, signature):
            logger.info(
                'mailgun.invalid-signature',
                extra={
                    'token': token,
                    'timestamp': timestamp,
                    'signature': signature,
                }
            )
            return HttpResponse(status=200)

        to_email = request.POST['recipient']
        from_email = request.POST['sender']

        try:
            group_id = email_to_group_id(to_email)
        except Exception:
            logger.info(
                'mailgun.invalid-email', extra={
                    'email': to_email,
                }
            )
            return HttpResponse(status=200)

        payload = EmailReplyParser.parse_reply(request.POST['body-plain']).strip()
        if not payload:
            # If there's no body, we don't need to go any further
            return HttpResponse(status=200)

        process_inbound_email.delay(from_email, group_id, payload)

        return HttpResponse(status=201)
Пример #17
0
 def parse_file(self, f):
     mail = email.message_from_file(f)
     _ , to_addr = iemail.utils.parseaddr(mail.get('To'))
     local_part = re.match(r'([^@]*)@' + settings.EMAIL_FROM_DOMAIN,to_addr)
     if local_part:
         local_part = local_part.group(1)
         message = []
         for part in mail.walk():
             if part.get_content_type() == 'text/plain':
                 message.append(part.get_payload())
         body = EmailReplyParser.parse_reply('\n'.join(message))
         match = re.match(r'issue-([0-9]*)', local_part)
         _ , from_addr = email.utils.parse(mail.get('From'))
         if local_part in settings.NEW_ISSUE_ADDRESSES:
             subject = mail.get('Subject')
             issue = models.Issue(name=subject, desc=body, email=from_addr, opened=timezone.now())
             issue.save()
         elif match:
             key = match.group(1)
             issue = models.Issue.objects.get(pk=key)
             issue.add_message(from_addr, body)
Пример #18
0
    def get_body(self,
                 use_html_parsing=True,
                 use_txt_parsing=True) -> typing.Optional[str]:
        body_part = self._get_mime_body_message()
        body = None
        if body_part:
            charset = body_part.get_content_charset("iso-8859-1")
            content_type = body_part.get_content_type()
            if content_type == CONTENT_TYPE_TEXT_PLAIN:
                txt_body = body_part.get_payload(decode=True).decode(charset)
                if use_txt_parsing:
                    txt_body = EmailReplyParser.parse_reply(txt_body)
                html_body = markdown.markdown(txt_body)
                body = HtmlSanitizer.sanitize(html_body)

            elif content_type == CONTENT_TYPE_TEXT_HTML:
                html_body = body_part.get_payload(decode=True).decode(charset)
                if use_html_parsing:
                    html_body = str(ParsedHTMLMail(html_body))
                body = HtmlSanitizer.sanitize(html_body)
            if not body:
                raise EmptyEmailBody()
        return body
Пример #19
0
def preprocessing_english(x):
    x = BeautifulSoup(x)
    x = EmailReplyParser.parse_reply(x.get_text())
    x = re.sub(r'<.*?>', '', x)
    x = x.replace("\n", " ").strip()
    x = re.sub(pattern=r'[\!"#$%&\*+,-./:;<=>?@^_`()|~=]', repl='', string=x)
    x = x.replace("\n", " ").strip()
    x = x.strip()
    x = re.sub(r"(^|\W)\d+", "", x)
    x = x.lower()
    x = re.sub(r'[^a-zA-Z]', ' ', x)
    x = re.sub("\s\s+", " ", x)

    stopwords = {
        'forwarded', 'message', 'lz', 'logitech', 'dear', 'my', 'date', 'i',
        'recently', 'hi', 'hello', 'product', 'serial', 'number', '1', '2',
        '3', '4', '5', '6', '7', '8', '9', '0', 'purchased', 'purchase',
        'support', 'http', 'com', 'logitech', 'www', 'https', 'logi',
        'customercare', 'contact', 'terms', 'blvd', 'gateway', 'newark', 'usa',
        'logo', 'care', 'ca', 'footer', 'use', 'customer', 'owned', 'us',
        'survey', 'americas', 'copyright', 'headquarters', 'owners',
        'respective', 'the', 'rights', 'trademarks', 'reserved', 'property',
        'dear', 'regards', 'thanks', 'mail', 'email', 'lz', 'g', 'x', 'k',
        'date', 'like', 'get', 'one', 'set', 'thank', 'also', 'two', 'see',
        'able', 'n', 'could', 'since', 'last', 'know', 'still', 'got', 'pm',
        'p', 'n', 's'
        'operating', 'system', 'platform', 'ce', 's', 'hs', 'y', 'mr', 'de',
        'lfcm', 'sy', 'm', 'kh', 'w', 'ks', 'hs', 'afternoon', 'morning',
        'regards', 'thx'
        'thanks', 'fri', 'mon', 'tue', 'wed', 'thu', 'sat', 'sun', 'jan',
        'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'sep', 'oct', 'nov', 'dec'
    }

    x = x.split()
    x = [word for word in x if word.lower() not in stopwords]
    x = ' '.join(x)
    return x
    def process_message(self, peer, mailfrom, rcpttos, raw_message):
        logger.info('Incoming message received from %s', mailfrom)
        if not len(rcpttos):
            logger.info('Incoming email had no recipients. Ignoring.')
            return STATUS[550]

        if len(raw_message) > self.max_message_length:
            logger.info('Inbound email message was too long: %d',
                        len(raw_message))
            return STATUS[552]

        try:
            group_id = email_to_group_id(rcpttos[0])
        except Exception:
            logger.info('%r is not a valid email address', rcpttos)
            return STATUS[550]

        message = email.message_from_string(raw_message)
        payload = None
        if message.is_multipart():
            for msg in message.walk():
                if msg.get_content_type() == 'text/plain':
                    payload = msg.get_payload()
                    break
            if payload is None:
                # No text/plain part, bailing
                return STATUS[200]
        else:
            payload = message.get_payload()

        payload = EmailReplyParser.parse_reply(payload).strip()
        if not payload:
            # If there's no body, we don't need to go any further
            return STATUS[200]

        process_inbound_email.delay(mailfrom, group_id, payload)
        return STATUS[200]
Пример #21
0
    def get_summary_message(self):
        '''Return summary of replies as HTML'''
        settings = frappe.get_doc('Daily Work Summary Settings')

        replies = frappe.get_all('Communication',
                                 fields=['content', 'text_content', 'sender'],
                                 filters=dict(
                                     reference_doctype=self.doctype,
                                     reference_name=self.name,
                                     communication_type='Communication',
                                     sent_or_received='Received'),
                                 order_by='creation asc')

        did_not_reply = self.email_sent_to.split()

        for d in replies:
            d.sender_name = frappe.db.get_value(
                "Employee", {"user_id": d.sender}, "employee_name") or d.sender
            if d.sender in did_not_reply:
                did_not_reply.remove(d.sender)
            if d.text_content:
                d.content = markdown(
                    EmailReplyParser.parse_reply(d.text_content))

        did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email},
                                              "employee_name") or email)
                         for email in did_not_reply]

        return frappe.render_template(
            self.get_summary_template(),
            dict(replies=replies,
                 original_message=settings.message,
                 title=_('Daily Work Summary for {0}'.format(
                     formatdate(self.creation))),
                 did_not_reply=', '.join(did_not_reply) or '',
                 did_not_reply_title=_('No replies from')))
Пример #22
0
def parse_mbox(path):
    mbox = mailbox.mbox(path)
    messages = []
    for message in mbox:
        if message['From'] in ("Michael Perrone <*****@*****.**>", "Mike Perrone <*****@*****.**>"):
            while message.is_multipart():
                message = message.get_payload(0)
            text = message.get_payload()

            # use Zapier's port of Github's open source reply parser to parse just the reply and not the quoted text.
            reply = EmailReplyParser.parse_reply(text)

            # I'm very consistent with my signature, so trim off anything after
            # it, in case EmailReplyParser couldn't figure that out. Also,
            # EmailReplyParser can't figure out gmail's forwarded message thing
            signature_index = first_non_neg([
                reply.find('-Mike Perrone'),
                reply.find('-Michael Perrone'),
                reply.find("---------- Forwarded message ----------")
            ])
            if signature_index >= 0:
                reply = reply[:signature_index]
            messages.append(reply)
    return messages
Пример #23
0
def collect_project_status():
    for data in dataent.get_all("Project Update", {
            'date': today(),
            'sent': 0
    }):
        replies = dataent.get_all('Communication',
                                  fields=['content', 'text_content', 'sender'],
                                  filters=dict(
                                      reference_doctype="Project Update",
                                      reference_name=data.name,
                                      communication_type='Communication',
                                      sent_or_received='Received'),
                                  order_by='creation asc')

        for d in replies:
            doc = dataent.get_doc("Project Update", data.name)
            user_data = dataent.db.get_values(
                "User", {"email": d.sender},
                ["full_name", "user_image", "name"],
                as_dict=True)[0]

            doc.append(
                "users", {
                    'user':
                    user_data.name,
                    'full_name':
                    user_data.full_name,
                    'image':
                    user_data.user_image,
                    'project_status':
                    dataent.utils.md_to_html(
                        EmailReplyParser.parse_reply(d.text_content)
                        or d.content)
                })

            doc.save(ignore_permissions=True)
Пример #24
0
    def process_message(self, peer, mailfrom, rcpttos, raw_message):
        logger.info('Incoming message received from %s', mailfrom)
        if not len(rcpttos):
            logger.info('Incoming email had no recipients. Ignoring.')
            return STATUS[550]

        if len(raw_message) > self.max_message_length:
            logger.info('Inbound email message was too long: %d', len(raw_message))
            return STATUS[552]

        try:
            group_id = email_to_group_id(rcpttos[0])
        except Exception:
            logger.info('%r is not a valid email address', rcpttos)
            return STATUS[550]

        message = email.message_from_string(raw_message)
        payload = None
        if message.is_multipart():
            for msg in message.walk():
                if msg.get_content_type() == 'text/plain':
                    payload = msg.get_payload()
                    break
            if payload is None:
                # No text/plain part, bailing
                return STATUS[200]
        else:
            payload = message.get_payload()

        payload = EmailReplyParser.parse_reply(payload).strip()
        if not payload:
            # If there's no body, we don't need to go any further
            return STATUS[200]

        process_inbound_email.delay(mailfrom, group_id, payload)
        return STATUS[200]
Пример #25
0
    def handle_DATA(self, server, session, envelope):
        mail_from = envelope.mail_from

        message = BytesParser(policy=policy.default).parsebytes(
            envelope.content)
        body = message.get_body(preferencelist=('plain', ))
        if body:
            content = body.get_content()
            reply = EmailReplyParser.parse_reply(content)
            author, _ = User.objects.get_or_create(email=mail_from)
            ticket, message_id = self.get_ticket(message)

            if ticket:
                if not ticket.inbox.enable_reply_by_email:
                    return '450 Reply by email is disabled for the inbox'

                Comment.objects.create(
                    ticket=ticket,
                    author=author,
                    is_reply=ticket.reply_message_id == message_id,
                    content=reply)
                UserInbox.objects.get_or_create(user=author,
                                                inbox=ticket.inbox)

            else:
                inbox = Inbox.objects.get(email__in=envelope.rcpt_tos, )
                if not inbox.enable_create_new_ticket_by_email:
                    return '450 Creation of ticket by email is disabled for the inbox'

                Ticket.objects.create(author=author,
                                      inbox=inbox,
                                      title=message["Subject"],
                                      content=reply)
                UserInbox.objects.get_or_create(user=author, inbox=inbox)

        return '250 OK'
Пример #26
0
def ticket_from_message(message, queue, logger):
    # 'message' must be an RFC822 formatted message.
    message = email.message_from_string(message) if six.PY3 else email.message_from_string(message.encode('utf-8'))
    subject = message.get('subject', _('Comment from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
    for affix in STRIPPED_SUBJECT_STRINGS:
        subject = subject.replace(affix, "")
    subject = subject.strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
    sender_email = email.utils.parseaddr(sender)[1]

    cc = message.get_all('cc', None)
    if cc:
        # first, fixup the encoding if necessary
        cc = [decode_mail_headers(decodeUnknown(message.get_charset(), x)) for x in cc]
        # get_all checks if multiple CC headers, but individual emails may be comma separated too
        tempcc = []
        for hdr in cc:
            tempcc.extend(hdr.split(','))
        # use a set to ensure no duplicates
        cc = set([x.strip() for x in tempcc])

    for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r".*\[" + queue.slug + r"-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
        logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket))
    else:
        logger.info("No tracking ID matched.")
        ticket = None

    body = None
    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = email.utils.collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name is None:
            if part.get_content_subtype() == 'plain':
                body = EmailReplyParser.parse_reply(
                    decodeUnknown(part.get_content_charset(), part.get_payload(decode=True))
                )
                # workaround to get unicode text out rather than escaped text
                try:
                    body = body.encode('ascii').decode('unicode_escape')
                except UnicodeEncodeError:
                    body.encode('utf-8')
                logger.debug("Discovered plain text MIME part")
            else:
                files.append(
                    SimpleUploadedFile(_("email_html_body.html"), encoding.smart_bytes(part.get_payload()), 'text/html')
                )
                logger.debug("Discovered HTML MIME part")
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)
            payload = part.get_payload()
            if isinstance(payload, list):
                payload = payload.pop().as_string()
            payloadToWrite = payload
            # check version of python to ensure use of only the correct error type
            if six.PY2:
                non_b64_err = binascii.Error
            else:
                non_b64_err = TypeError
            try:
                logger.debug("Try to base64 decode the attachment payload")
                if six.PY2:
                    payloadToWrite = base64.decodestring(payload)
                else:
                    payloadToWrite = base64.decodebytes(payload)
            except non_b64_err:
                logger.debug("Payload was not base64 encoded, using raw bytes")
                payloadToWrite = payload
            files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0]))
            logger.debug("Found MIME attachment %s" % name)

        counter += 1

    if not body:
        mail = BeautifulSoup(part.get_payload(), "lxml")
        if ">" in mail.text:
            body = mail.find('body')
            body = body.text
            body = body.encode('ascii', errors='ignore')
        else:
            body = mail.text

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
        except Ticket.DoesNotExist:
            logger.info("Tracking ID %s-%s not associated with existing ticket. Creating new ticket." % (queue.slug, ticket))
            ticket = None
        else:
            logger.info("Found existing ticket with Tracking ID %s-%s" % (t.queue.slug, t.id))
            if t.status == Ticket.CLOSED_STATUS:
                t.status = Ticket.REOPENED_STATUS
                t.save()
            new = False

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')
    high_priority_types = {'high', 'important', '1', 'urgent'}
    priority = 2 if high_priority_types & {smtp_priority, smtp_importance} else 3

    if ticket is None:
        if settings.QUEUE_EMAIL_BOX_UPDATE_ONLY:
            return None
        new = True
        t = Ticket.objects.create(
            title=subject,
            queue=queue,
            submitter_email=sender_email,
            created=timezone.now(),
            description=body,
            priority=priority,
        )
        logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id))

    if cc:
        # get list of currently CC'd emails
        current_cc = TicketCC.objects.filter(ticket=ticket)
        current_cc_emails = [x.email for x in current_cc if x.email]
        # get emails of any Users CC'd to email, if defined
        # (some Users may not have an associated email, e.g, when using LDAP)
        current_cc_users = [x.user.email for x in current_cc if x.user and x.user.email]
        # ensure submitter, assigned user, queue email not added
        other_emails = [queue.email_address]
        if t.submitter_email:
            other_emails.append(t.submitter_email)
        if t.assigned_to:
            other_emails.append(t.assigned_to.email)
        current_cc = set(current_cc_emails + current_cc_users + other_emails)
        # first, add any User not previously CC'd (as identified by User's email)
        all_users = User.objects.all()
        all_user_emails = set([x.email for x in all_users])
        users_not_currently_ccd = all_user_emails.difference(set(current_cc))
        users_to_cc = cc.intersection(users_not_currently_ccd)
        for user in users_to_cc:
            tcc = TicketCC.objects.create(
                ticket=t,
                user=User.objects.get(email=user),
                can_view=True,
                can_update=False
            )
            tcc.save()
        # then add remaining emails alphabetically, makes testing easy
        new_cc = cc.difference(current_cc).difference(all_user_emails)
        new_cc = sorted(list(new_cc))
        for ccemail in new_cc:
            tcc = TicketCC.objects.create(
                ticket=t,
                email=ccemail.replace('\n', ' ').replace('\r', ' '),
                can_view=True,
                can_update=False
            )
            tcc.save()

    f = FollowUp(
        ticket=t,
        title=_('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
        date=timezone.now(),
        public=True,
        comment=body,
    )

    if t.status == Ticket.REOPENED_STATUS:
        f.new_status = Ticket.REOPENED_STATUS
        f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})

    f.save()
    logger.debug("Created new FollowUp for Ticket")

    if six.PY2:
        logger.info(("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace'))
    elif six.PY3:
        logger.info("[%s-%s] %s" % (t.queue.slug, t.id, t.title,))

    attached = process_attachments(f, files)
    for att_file in attached:
        logger.info("Attachment '%s' (with size %s) successfully added to ticket from email." % (att_file[0], att_file[1].size))

    context = safe_template_context(t)

    if new:
        if sender_email:
            send_templated_mail(
                'newticket_submitter',
                context,
                recipients=sender_email,
                sender=queue.from_address,
                fail_silently=True,
            )
        if queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.new_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )
        if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )
    else:
        context.update(comment=f.comment)
        if t.assigned_to:
            send_templated_mail(
                'updated_owner',
                context,
                recipients=t.assigned_to.email,
                sender=queue.from_address,
                fail_silently=True,
            )
        if queue.updated_ticket_cc:
            send_templated_mail(
                'updated_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

    return t
Пример #27
0
def object_from_message(message, queue, logger):
    # 'message' must be an RFC822 formatted message.
    message = email.message_from_string(message)

    subject = message.get('subject', _('Comment from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(),
                                                subject))
    for affix in STRIPPED_SUBJECT_STRINGS:
        subject = subject.replace(affix, "")
    subject = subject.strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
    # to address bug #832, we wrap all the text in front of the email address in
    # double quotes by using replace() on the email string. Then,
    # take first item of list, second item of tuple is the actual email address.
    # Note that the replace won't work on just an email with no real name,
    # but the getaddresses() function seems to be able to handle just unclosed quotes
    # correctly. Not ideal, but this seems to work for now.
    sender_email = email.utils.getaddresses(
        ['\"' + sender.replace('<', '\" <')])[0][1]

    body_plain, body_html = '', ''

    cc = message.get_all('cc', None)
    if cc:
        # first, fixup the encoding if necessary
        cc = [
            decode_mail_headers(decodeUnknown(message.get_charset(), x))
            for x in cc
        ]
        # get_all checks if multiple CC headers, but individual emails may be comma separated too
        tempcc = []
        for hdr in cc:
            tempcc.extend(hdr.split(','))
        # use a set to ensure no duplicates
        cc = set([x.strip() for x in tempcc])

    for ignore in IgnoreEmail.objects.filter(
            Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r".*\[" + queue.slug + r"-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
        logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket))
    else:
        logger.info("No tracking ID matched.")
        ticket = None

    body = None
    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = email.utils.collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name is None:
            if part.get_content_subtype() == 'plain':
                body = part.get_payload(decode=True)
                # https://github.com/django-helpdesk/django-helpdesk/issues/732
                if part['Content-Transfer-Encoding'] == '8bit' and part.get_content_charset(
                ) == 'utf-8':
                    body = body.decode('unicode_escape')
                body = decodeUnknown(part.get_content_charset(), body)
                body = EmailReplyParser.parse_reply(body)
                # workaround to get unicode text out rather than escaped text
                try:
                    body = body.encode('ascii').decode('unicode_escape')
                except UnicodeEncodeError:
                    body.encode('utf-8')
                logger.debug("Discovered plain text MIME part")
            else:
                try:
                    email_body = encoding.smart_text(
                        part.get_payload(decode=True))
                except UnicodeDecodeError:
                    email_body = encoding.smart_text(
                        part.get_payload(decode=False))

                payload = """
<html>
<head>
<meta charset="utf-8"/>
</head>
%s
</html>""" % email_body
                files.append(
                    SimpleUploadedFile(_("email_html_body.html"),
                                       payload.encode("utf-8"), 'text/html'))
                logger.debug("Discovered HTML MIME part")
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)
            payload = part.get_payload()
            if isinstance(payload, list):
                payload = payload.pop().as_string()
            payloadToWrite = payload
            # check version of python to ensure use of only the correct error type
            non_b64_err = TypeError
            try:
                logger.debug("Try to base64 decode the attachment payload")
                payloadToWrite = base64.decodebytes(payload)
            except non_b64_err:
                logger.debug("Payload was not base64 encoded, using raw bytes")
                payloadToWrite = payload
            files.append(
                SimpleUploadedFile(name, part.get_payload(decode=True),
                                   mimetypes.guess_type(name)[0]))
            logger.debug("Found MIME attachment %s" % name)

        counter += 1

    if not body:
        mail = BeautifulSoup(str(message), "html.parser")
        beautiful_body = mail.find('body')
        if beautiful_body:
            try:
                body = beautiful_body.text
            except AttributeError:
                pass
        if not body:
            body = ""

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')
    high_priority_types = {'high', 'important', '1', 'urgent'}
    priority = 2 if high_priority_types & {smtp_priority, smtp_importance
                                           } else 3

    payload = {
        'body': body,
        'subject': subject,
        'queue': queue,
        'sender_email': sender_email,
        'priority': priority,
        'files': files,
    }

    return create_object_from_email_message(message,
                                            ticket,
                                            payload,
                                            files,
                                            logger=logger)
 def test_email_one_is_not_on(self):
     with open('test/emails/email_one_is_not_on.txt') as email:
         self.assertTrue(
             "On Oct 1, 2012, at 11:55 PM, Dave Tapley wrote:" not in EmailReplyParser.parse_reply(email.read()))
 def test_parse_out_just_top_for_outlook_with_reply_directly_above_line(self):
     with open('test/emails/email_2_2.txt') as f:
         self.assertEqual("Outlook with a reply directly above line", EmailReplyParser.parse_reply(f.read()))
 def test_reply_from_gmail(self):
     with open('test/emails/email_gmail.txt') as f:
         self.assertEqual('This is a test for inbox replying to a github message.',
                          EmailReplyParser.parse_reply(f.read()))
Пример #31
0
	def set_content_and_type(self):
		self.content, self.content_type = '[Blank Email]', 'text/plain'
		if self.html_content:
			self.content, self.content_type = self.html_content, 'text/html'
		else:
			self.content, self.content_type = EmailReplyParser.parse_reply(self.text_content), 'text/plain'
Пример #32
0
def inbox():
    # Parse E-Mail
    parsed_email = message_from_string(request.form.to_dict()['email'])
    parsed_email_from = parseaddr(parsed_email['From'])[1]
    parsed_email_to = parseaddr(parsed_email['To'])[1]
    parsed_email_to_domain = parsed_email_to.split('@')[1]
    parsed_email_session = md5(
        bytes(parsed_email.get('Subject', '').replace('Re: ', '') +
              parsed_email_from,
              encoding='utf8')).hexdigest()
    parsed_email_body = ''
    for part in parsed_email.walk():
        if part.get_content_type() == 'text/plain':
            parsed_email_body += part.get_payload()

    parsed_email_body = EmailReplyParser.parse_reply(parsed_email_body)
    parsed_email_lang = fallback_lang
    try:
        parsed_email_lang = detect(parsed_email_body)
    except:
        pass

    # Log E-Mail
    app.logger.info('Received new E-Mail')
    app.logger.info('From: ' + parsed_email_from)
    app.logger.info('To: ' + parsed_email_to)
    app.logger.info('Text: ' + parsed_email_body)
    app.logger.info('Message ID: ' + parsed_email['Message-ID'])
    app.logger.info('Session ID: ' + parsed_email_session)

    # Build Request
    agent_id = parsed_email_to.split('@')[0]
    req = {
        'session': parsed_email_session,
        'queryInput': {
            'text': {
                'text': parsed_email_body,
                'languageCode': parsed_email_lang
            }
        },
        'queryParams': {
            'payload': {
                'email': {
                    'from': parsed_email_from,
                    'to': parsed_email_to,
                    'subject': parsed_email['Subject'],
                    'body': parsed_email_body
                }
            }
        }
    }

    # Make the request
    agent = requests.get(endpoint.replace('*', agent_id))
    r = requests.post(endpoint.replace('*', agent_id), json=req)
    if r.status_code == 200:
        # Make new E-Mail for the response
        message = MIMEMultipart()
        message['Message-ID'] = make_msgid()
        message['In-Reply-To'] = parsed_email['Message-ID']
        message['References'] = parsed_email['Message-ID']
        message['From'] = agent.json(
        )['displayName'] + ' <' + parsed_email_to + '>' if agent.json().get(
            'displayName') else parsed_email['To']
        message['To'] = parsed_email['From']
        message['Subject'] = parsed_email['Subject']

        # Attach the components
        result = r.json()['queryResult']
        if 'fulfillmentMessages' in result:
            for component in result['fulfillmentMessages']:
                if 'text' in component:
                    message.attach(
                        MIMEText(component['text']['text'][0], 'plain'))
                elif 'simpleResponses' in component:
                    message.attach(
                        MIMEText(
                            component['simpleResponses']['simpleResponses'][0]
                            ['textToSpeech'], 'plain'))

        if 'webhookPayload' in result:
            if 'google' in result['webhookPayload']:
                for component in result['webhookPayload']['google'][
                        'richResponse']['items']:
                    if 'simpleResponse' in component:
                        message.attach(
                            MIMEText(
                                component['simpleResponse']['textToSpeech'],
                                'plain'))

        # Send the E-Mail
        session = smtplib.SMTP(host, 587)
        session.ehlo()
        session.starttls()
        session.ehlo()
        session.login(user, password)
        session.sendmail(message['From'], message['To'], message.as_string())

        # Log response status
        app.logger.info('E-Mail response sent to ' + parsed_email_from)
    elif r.status_code == 404 and catchall:
        # Make new E-Mail for the response
        message = MIMEMultipart()
        message['Message-ID'] = make_msgid()
        message['In-Reply-To'] = parsed_email['Message-ID']
        message['Reply-To'] = parsed_email['From']
        message['References'] = parsed_email['Message-ID']
        message['From'] = 'no-reply@' + parsed_email_to_domain
        message['To'] = catchall
        message['Subject'] = parsed_email['Subject']
        message.attach(MIMEText(parsed_email_body, 'plain'))

        # Send the E-Mail
        session = smtplib.SMTP(host, 587)
        session.ehlo()
        session.starttls()
        session.ehlo()
        session.login(user, password)
        session.sendmail(message['From'], message['To'], message.as_string())

        # Log response status
        app.logger.info('E-Mail response sent to ' + parsed_email_from)
    else:
        # Log request error
        app.logger.error('Request failed')
        app.logger.error('Status: ' + str(r.status_code))
        app.logger.error(str(r.json()))

    return "OK", 200
Пример #33
0
 def set_content_and_type(self):
     self.content, self.content_type = "[Blank Email]", "text/plain"
     if self.html_content:
         self.content, self.content_type = self.html_content, "text/html"
     else:
         self.content, self.content_type = EmailReplyParser.parse_reply(self.text_content), "text/plain"
Пример #34
0
 def test_parse_android_gmail_inline(self):
     with open('test/emails/email_android_gmail_inline.txt') as f:
         self.assertEqual("This is a test", EmailReplyParser.parse_reply(f.read()))
Пример #35
0
    def get_reply_text(self):
        reply_text = EmailReplyParser.parse_reply(self.plain_content)

        return reply_text
Пример #36
0
def parse_text():
    parser = EmailReplyParser(language='en')
    with open('test/emails/caution.txt', 'r') as fl:
        message = fl.read()
    text = parser.parse_reply(message)
    print(text)
Пример #37
0
def receive(from_addr, to_plus_cc_addrs, current_email, thread_id, fulist, bu):

    try:
        # the following is simply to fix the state among all free users. If a new
        # free guy is added by the busy guy, please take the most recent state
        # among the free guys and add him to that. <-- this adding a free guy in the
        # middle has not been included in the below snippet within the if statement
        if from_addr in fulist:

            audience = [addr for addr in to_plus_cc_addrs if addr != bu]
            if audience and audience != fulist:
                adding_others_reply(fulist, current_email, thread_id)
                to_plus_cc_addrs = fulist

        person_list = []
        for item in to_plus_cc_addrs:

            person_list.append({
                'email': item,
                'first_name': address_dict[item][0]
            })

        from_entry = {
            'email': from_addr,
            'first_name': address_dict[from_addr][0]
        }

        input_obj = {
            'email': {
                'from': from_entry,
                'to': person_list,
                'body':
                EmailReplyParser.parse_reply(mail.get_body(current_email)),
            },
            'availability': {
                'dt': get_free_slots(bu),  # list of tuples of dt objects
                'loc': 'Location',
            }
        }

        sara_debug('INPUTTT' + input_obj.__str__())
        # dsobj = ds(thread_id) # if tid is None ds will pass a brand new object
        dsobj = gds(thread_id)

        output_obj = dsobj.take_turn(input_obj)
        sara_debug('OUTPUTTT ' + output_obj.__str__())

        if output_obj is not None:

            for each_email in output_obj['emails']:
                to_addrs = list(each_email['to'])
                sara_debug("INPUTTTTTTTT" + ','.join(to_addrs))
                to_addrs = [address_dict[item][1] for item in to_addrs]
                body = each_email['body']
                send(to_addrs, body, thread_id)

            if output_obj['meeting'] is not None:
                send_invite(SARA_F, list(output_obj['meeting']['to']),
                            output_obj['meeting']['loc'],
                            output_obj['meeting']['dt']['start'],
                            output_obj['meeting']['dt']['end'])

        sara_debug("Finished receiving...Returning")
        return 'success'

    except Exception as e:

        print e
        print sys.exc_info()[0]
        sara_debug("Failed receiving...Returning")
        return 'Failure'
Пример #38
0
def ticket_from_message(message, queue, quiet):
    # 'message' must be an RFC822 formatted message.
    msg = message
    message = email.message_from_string(msg.decode('utf-8'))
    subject = message.get('subject', _('Created from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(),
                                                subject))
    subject = subject.replace("Re: ", "").replace("Fw: ", "").replace(
        "RE: ", "").replace("FW: ", "").replace("Automatic reply: ",
                                                "").strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))

    sender_email = parseaddr(sender)[1]

    body_plain, body_html = '', ''

    for ignore in IgnoreEmail.objects.filter(
            Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r'.*\[' + re.escape(queue.slug) + r'-(?P<id>\d+)\]',
                        subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
    else:
        ticket = None

    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name is None:
            if part.get_content_subtype() == 'plain':
                body_plain = EmailReplyParser.parse_reply(
                    decodeUnknown(part.get_content_charset(),
                                  part.get_payload(decode=True)))
            else:
                body_html = decodeUnknown(part.get_content_charset(),
                                          part.get_payload(decode=True))
                # make plain text more legible when viewing the ticket
                body_html, n = re.subn(r'[\r\n]+', r'', body_html)
                body_html, n = re.subn(r'\>\s+\<', r'><', body_html)
                body_html = body_html.replace("</h1>", "</h1>\n")
                body_html = body_html.replace("</h2>", "</h2>\n")
                body_html = body_html.replace("</h3>", "</h3>\n")
                body_html = body_html.replace("<p>", "\n<p>")
                body_html = body_html.replace("</p>", "</p>\n")
                body_html = body_html.replace("</div>", "</div>\n")
                body_html = body_html.replace("</tr>", "</tr>\n")
                body_html = body_html.replace("</td>", "</td> ")
                body_html = body_html.replace("<table>", "\n<table>")
                body_html = body_html.replace("</table>", "</table>\n")
                body_html = body_html.replace("<br />", "<br />\n")

                try:
                    # strip html tags
                    body_plain = striptags(body_html)
                except DjangoUnicodeDecodeError:
                    charset = chardet.detect(body_html)['encoding']
                    body_plain = striptags(str(body_html, charset))

                body_plain = unescape(body_plain)
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)

            files.append(
                {
                    'filename': name,
                    'content': part.get_payload(decode=True),
                    'type': part.get_content_type()
                }, )

        counter += 1

    if body_plain:
        body = body_plain
        if body_html:
            body += '\n\n'
            body += _(
                '***Note that HTML tags are stripped out. Please see attachment email_html_body.html for the full html content.'
            )
    else:
        body = _(
            'No plain-text email body available. Please see attachment email_html_body.html.'
        )

    if body_html:
        files.append({
            'filename': _("email_html_body.html"),
            'content': body_html,
            'type': 'text/html',
        })

    now = timezone.now()

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
            new = False
        except Ticket.DoesNotExist:
            ticket = None

    priority = 3

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')

    high_priority_types = ('high', 'important', '1', 'urgent')

    if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
        priority = 2

    if ticket is None:
        t = Ticket(
            title=subject,
            queue=queue,
            submitter_email=sender_email,
            created=now,
            description=body,
            priority=priority,
        )
        t.save()
        new = True
        #update = ''

    elif t.status == Ticket.CLOSED_STATUS:
        t.status = Ticket.REOPENED_STATUS
        t.save()

    f = FollowUp(
        ticket=t,
        title=_('E-Mail Received from %(sender_email)s' %
                {'sender_email': sender_email}),
        date=timezone.now(),
        public=True,
        comment=body,
    )

    if t.status == Ticket.REOPENED_STATUS:
        f.new_status = Ticket.REOPENED_STATUS
        f.title = _(
            'Ticket Re-Opened by E-Mail Received from %(sender_email)s' %
            {'sender_email': sender_email})

    f.save()

    if not quiet:
        print((" [%s-%s] %s" % (
            t.queue.slug,
            t.id,
            t.title,
        )).encode('ascii', 'replace'))

    for file in files:
        if file['content']:
            filename = file['filename'].replace(' ', '_')
            filename = re.sub(r'[^a-zA-Z0-9._-]+', '', filename)
            a = Attachment(
                followup=f,
                filename=filename,
                mime_type=file['type'],
                size=len(file['content']),
            )
            a.file.save(filename, ContentFile(file['content']), save=False)
            a.save()
            if not quiet:
                print("    - %s" % filename)

    context = safe_template_context(t)

    if new:

        if sender_email and not is_no_reply_address(sender_email):
            send_templated_mail(
                'newticket_submitter',
                context,
                recipients=sender_email,
                sender=queue.from_address,
                fail_silently=True,
            )

        if queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.new_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

        if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

    else:
        context.update(comment=f.comment)

        #if t.status == Ticket.REOPENED_STATUS:
        #    update = _(' (Reopened)')
        #else:
        #    update = _(' (Updated)')

        if t.assigned_to:
            send_templated_mail(
                'updated_owner',
                context,
                recipients=t.assigned_to.email,
                sender=queue.from_address,
                fail_silently=True,
            )

        if queue.updated_ticket_cc:
            send_templated_mail(
                'updated_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

    return t
Пример #39
0
def parse_reply_from_email(message):
    # not working right now
    return EmailReplyParser.parse_reply(message)
Пример #40
0
	def set_content_and_type(self):
		self.content, self.content_type = '[Blank Email]', 'text/plain'
		if self.html_content:
			self.content, self.content_type = self.html_content, 'text/html'
		else:
			self.content, self.content_type = EmailReplyParser.parse_reply(self.text_content), 'text/plain'
Пример #41
0
def object_from_message(message, queue, logger):
    # 'message' must be an RFC822 formatted message.
    message = email.message_from_string(message)

    subject = message.get('subject', _('Comment from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
    for affix in STRIPPED_SUBJECT_STRINGS:
        subject = subject.replace(affix, "")
    subject = subject.strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))

    # to address bug #832, we wrap all the text in front of the email address in
    # double quotes by using replace() on the email string. Then,
    # take first item of list, second item of tuple is the actual email address.
    # Note that the replace won't work on just an email with no real name,
    # but the getaddresses() function seems to be able to handle just unclosed quotes
    # correctly. Not ideal, but this seems to work for now.
    sender_email = email.utils.getaddresses(['\"' + sender.replace('<', '\" <')])[0][1]

    cc = message.get_all('cc', None)
    if cc:
        # first, fixup the encoding if necessary
        cc = [decode_mail_headers(decodeUnknown(message.get_charset(), x)) for x in cc]
        # get_all checks if multiple CC headers, but individual emails may be comma separated too
        tempcc = []
        for hdr in cc:
            tempcc.extend(hdr.split(','))
        # use a set to ensure no duplicates
        cc = set([x.strip() for x in tempcc])

    for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r".*\[" + queue.slug + r"-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
        logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket))
    else:
        logger.info("No tracking ID matched.")
        ticket = None

    body = None
    full_body = None
    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = email.utils.collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name is None:
            if part.get_content_subtype() == 'plain':
                body = part.get_payload(decode=True)
                # https://github.com/django-helpdesk/django-helpdesk/issues/732
                if part['Content-Transfer-Encoding'] == '8bit' and part.get_content_charset() == 'utf-8':
                    body = body.decode('unicode_escape')
                body = decodeUnknown(part.get_content_charset(), body)
                # have to use django_settings here so overwritting it works in tests
                # the default value is False anyway
                if ticket is None and getattr(django_settings, 'HELPDESK_FULL_FIRST_MESSAGE_FROM_EMAIL', False):
                    # first message in thread, we save full body to avoid losing forwards and things like that
                    body_parts = []
                    for f in EmailReplyParser.read(body).fragments:
                        body_parts.append(f.content)
                    full_body = '\n\n'.join(body_parts)
                    body = EmailReplyParser.parse_reply(body)
                else:
                    # second and other reply, save only first part of the message
                    body = EmailReplyParser.parse_reply(body)
                    full_body = body
                # workaround to get unicode text out rather than escaped text
                try:
                    body = body.encode('ascii').decode('unicode_escape')
                except UnicodeEncodeError:
                    body.encode('utf-8')
                logger.debug("Discovered plain text MIME part")
            else:
                try:
                    email_body = encoding.smart_text(part.get_payload(decode=True))
                except UnicodeDecodeError:
                    email_body = encoding.smart_text(part.get_payload(decode=False))

                if not body and not full_body:
                    # no text has been parsed so far - try such deep parsing for some messages
                    altered_body = email_body.replace("</p>", "</p>\n").replace("<br", "\n<br")
                    mail = BeautifulSoup(str(altered_body), "html.parser")
                    full_body = mail.get_text()

                if "<body" not in email_body:
                    email_body = f"<body>{email_body}</body>"

                payload = (
                    '<html>'
                    '<head>'
                    '<meta charset="utf-8" />'
                    '</head>'
                    '%s'
                    '</html>'
                ) % email_body
                files.append(
                    SimpleUploadedFile(_("email_html_body.html"), payload.encode("utf-8"), 'text/html')
                )
                logger.debug("Discovered HTML MIME part")
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)
            else:
                name = ("part-%i_" % counter) + name

            # # FIXME: this code gets the paylods, then does something with it and then completely ignores it
            # # writing the part.get_payload(decode=True) instead; and then the payload variable is
            # # replaced by some dict later.
            # # the `payloadToWrite` has been also ignored so was commented
            # payload = part.get_payload()
            # if isinstance(payload, list):
            #     payload = payload.pop().as_string()
            # # payloadToWrite = payload
            # # check version of python to ensure use of only the correct error type
            # non_b64_err = TypeError
            # try:
            #     logger.debug("Try to base64 decode the attachment payload")
            #     # payloadToWrite = base64.decodebytes(payload)
            # except non_b64_err:
            #     logger.debug("Payload was not base64 encoded, using raw bytes")
            #     # payloadToWrite = payload
            files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0]))
            logger.debug("Found MIME attachment %s" % name)

        counter += 1

    if not body:
        mail = BeautifulSoup(str(message), "html.parser")
        beautiful_body = mail.find('body')
        if beautiful_body:
            try:
                body = beautiful_body.text
                full_body = body
            except AttributeError:
                pass
        if not body:
            body = ""

    if getattr(django_settings, 'HELPDESK_ALWAYS_SAVE_INCOMING_EMAIL_MESSAGE', False):
        # save message as attachment in case of some complex markup renders wrong
        files.append(
            SimpleUploadedFile(
                _("original_message.eml").replace(
                    ".eml",
                    timezone.localtime().strftime("_%d-%m-%Y_%H:%M") + ".eml"
                ),
                str(message).encode("utf-8"),
                'text/plain'
            )
        )

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')
    high_priority_types = {'high', 'important', '1', 'urgent'}
    priority = 2 if high_priority_types & {smtp_priority, smtp_importance} else 3

    payload = {
        'body': body,
        'full_body': full_body or body,
        'subject': subject,
        'queue': queue,
        'sender_email': sender_email,
        'priority': priority,
        'files': files,
    }

    return create_object_from_email_message(message, ticket, payload, files, logger=logger)
Пример #42
0
import sys
from email_reply_parser import EmailReplyParser

print EmailReplyParser.parse_reply(sys.stdin.read())
Пример #43
0
def ticket_from_message(message, quiet):
    """
    Create a ticket or a followup (if ticket id in subject)
    """
    msg = message
    message = email.message_from_string(msg)
    subject = message.get("subject", "Created from e-mail")
    subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
    sender = message.get("from", ("Unknown Sender"))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
    sender_email = parseaddr(sender)[1]
    body_plain, body_html = "", ""

    matchobj = re.match(r".*\[" + "-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group("id")
    else:
        ticket = None

    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == "multipart":
            continue

        name = part.get_param("name")
        if name:
            name = collapse_rfc2231_value(name)

        if part.get_content_maintype() == "text" and name == None:
            if part.get_content_subtype() == "plain":
                body_plain = EmailReplyParser.parse_reply(
                    decodeUnknown(part.get_content_charset(), part.get_payload(decode=True))
                )
            else:
                body_html = part.get_payload(decode=True)
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)

            files.append({"filename": name, "content": part.get_payload(decode=True), "type": part.get_content_type()})

        counter += 1

    if body_plain:
        body = body_plain
    else:
        body = "No plain-text email body available. Please see attachment email_html_body.html."

    if body_html:
        files.append({"filename": "email_html_body.html", "content": body_html, "type": "text/html"})

    now = timezone.now()

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
            new = False
        except Ticket.DoesNotExist:
            ticket = None

    if ticket == None:

        # set owner depending on sender_email
        # list of all email addresses from the user model
        users = User.objects.all()
        email_addresses = []
        for user in users:
            email_addresses.append(user.email)

        ############################################################
        # if ticket id in subject => new followup instead of new ticket
        tickets = Ticket.objects.all()
        ticket_ids = []
        for ticket in tickets:
            ticket_ids.append(ticket.id)

        # extract id from subject
        subject_id = re.search(r"\[#(\d*)\]\s.*", subject)
        try:
            subject_id = subject_id.group(1)
        except:
            subject_id = "0000"  # no valid id

        # if there was an ID in the subject, create followup
        if int(subject_id) in ticket_ids:

            if sender_email in email_addresses:
                f = FollowUp(
                    title=subject,
                    created=now,
                    text=body,
                    ticket=Ticket.objects.get(id=subject_id),
                    user=User.objects.get(email=sender_email),
                )
            else:
                f = FollowUp(title=subject, created=now, text=body, ticket=Ticket.objects.get(id=subject_id))

            f.save()

        # if no ID in the subject, create ticket
        else:

            # if known sender, set also the field owner
            if sender_email in email_addresses:
                t = Ticket(
                    title=subject,
                    status="TODO",
                    created=now,
                    description=body,
                    owner=User.objects.get(email=sender_email),
                )
            # if unknown sender, skip the field owner
            else:
                t = Ticket(title=subject, status="TODO", created=now, description=body)

            t.save()

            from django.core.mail import send_mail

            notification_subject = "[#" + str(t.id) + "] New ticket created"
            notification_body = "Hi,\n\na new ticket was created: http://localhost:8000/ticket/" + str(t.id) + "/"
            send_mail(
                notification_subject,
                notification_body,
                os.environ["DJANGO_TICKET_EMAIL_NOTIFICATIONS_FROM"],
                [os.environ["DJANGO_TICKET_EMAIL_NOTIFICATIONS_TO"]],
                fail_silently=False,
            )

        ############################################################

        new = True
        update = ""

    elif t.status == Ticket.CLOSED_STATUS:
        t.status = Ticket.REOPENED_STATUS
        t.save()

    # files of followups should be assigned to the corresponding ticket
    for file in files:

        if file["content"]:

            filename = file["filename"].encode("ascii", "replace").replace(" ", "_")
            filename = re.sub("[^a-zA-Z0-9._-]+", "", filename)

            # if followup
            if int(subject_id) in ticket_ids:
                a = Attachment(
                    ticket=Ticket.objects.get(id=subject_id),
                    filename=filename,
                    # mime_type=file['type'],
                    # size=len(file['content']),
                )

            # if new ticket
            else:
                a = Attachment(
                    ticket=t,
                    filename=filename,
                    # mime_type=file['type'],
                    # size=len(file['content']),
                )

            a.file.save(filename, ContentFile(file["content"]), save=False)
            a.save()

            if not quiet:
                print " - %s" % filename

    if int(subject_id) in ticket_ids:
        return f
    else:
        return t
Пример #44
0
def ticket_from_message(message, queue, logger):
    # 'message' must be an RFC822 formatted message.
    message = email.message_from_string(
        message) if six.PY3 else email.message_from_string(
            message.encode('utf-8'))
    subject = message.get('subject', _('Created from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(),
                                                subject))
    for affix in STRIPPED_SUBJECT_STRINGS:
        subject = subject.replace(affix, "")
    subject = subject.strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
    sender_email = email.utils.parseaddr(sender)[1]

    for ignore in IgnoreEmail.objects.filter(
            Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
        logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket))
    else:
        logger.info("No tracking ID matched.")
        ticket = None

    body = None
    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = email.utils.collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name is None:
            if part.get_content_subtype() == 'plain':
                body = EmailReplyParser.parse_reply(
                    decodeUnknown(part.get_content_charset(),
                                  part.get_payload(decode=True)))
                # workaround to get unicode text out rather than escaped text
                body = body.encode('ascii').decode(
                    'unicode_escape') if six.PY3 else body.encode('utf-8')
                logger.debug("Discovered plain text MIME part")
            else:
                files.append(
                    SimpleUploadedFile(
                        _("email_html_body.html"),
                        encoding.smart_bytes(part.get_payload()), 'text/html'))
                logger.debug("Discovered HTML MIME part")
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)
            files.append(
                SimpleUploadedFile(name,
                                   encoding.smart_bytes(part.get_payload()),
                                   part.get_content_type()))
            logger.debug("Found MIME attachment %s" % name)

        counter += 1

    if not body:
        body = _(
            'No plain-text email body available. Please see attachment "email_html_body.html".'
        )

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
        except Ticket.DoesNotExist:
            logger.info(
                "Tracking ID %s-%s not associated with existing ticket. Creating new ticket."
                % (queue.slug, ticket))
            ticket = None
        else:
            logger.info("Found existing ticket with Tracking ID %s-%s" %
                        (t.queue.slug, t.id))
            if t.status == Ticket.CLOSED_STATUS:
                t.status = Ticket.REOPENED_STATUS
                t.save()
            new = False

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')
    high_priority_types = {'high', 'important', '1', 'urgent'}
    priority = 2 if high_priority_types & {smtp_priority, smtp_importance
                                           } else 3

    if ticket is None:
        new = True
        t = Ticket.objects.create(
            title=subject,
            queue=queue,
            submitter_email=sender_email,
            created=timezone.now(),
            description=body,
            priority=priority,
        )
        logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id))

    f = FollowUp(
        ticket=t,
        title=_('E-Mail Received from %(sender_email)s' %
                {'sender_email': sender_email}),
        date=timezone.now(),
        public=True,
        comment=body,
    )

    if t.status == Ticket.REOPENED_STATUS:
        f.new_status = Ticket.REOPENED_STATUS
        f.title = _(
            'Ticket Re-Opened by E-Mail Received from %(sender_email)s' %
            {'sender_email': sender_email})

    f.save()
    logger.debug("Created new FollowUp for Ticket")

    if six.PY2:
        logger.info(("[%s-%s] %s" % (
            t.queue.slug,
            t.id,
            t.title,
        )).encode('ascii', 'replace'))
    elif six.PY3:
        logger.info("[%s-%s] %s" % (
            t.queue.slug,
            t.id,
            t.title,
        ))

    attached = process_attachments(f, files)
    for att_file in attached:
        logger.info(
            "Attachment '%s' successfully added to ticket from email." %
            att_file[0])

    context = safe_template_context(t)

    if new:
        if sender_email:
            send_templated_mail(
                'newticket_submitter',
                context,
                recipients=sender_email,
                sender=queue.from_address,
                fail_silently=True,
            )
        if queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.new_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )
        if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )
    else:
        context.update(comment=f.comment)
        if t.assigned_to:
            send_templated_mail(
                'updated_owner',
                context,
                recipients=t.assigned_to.email,
                sender=queue.from_address,
                fail_silently=True,
            )
        if queue.updated_ticket_cc:
            send_templated_mail(
                'updated_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

    return t
Пример #45
0
        shortcode = ShortCode.objects.get(alias=email_alias)
        if isinstance(shortcode, Introduction):
            intro = shortcode
        else:
            raise Introduction.DoesNotExist("%s does not appear to represent an existing introduction. Message: %s" % (delivered_to, message.pk))
    # If the sender is the user that owns the intro, add it as a comment
    person = None
    try:
        person = PersonEmail.objects.get(email_address=get_from_addr(msg))
    except Exception, e:
        pass
    if person.person == intro.person:
        new_comment = IntroductionComment(
            person = person.person,
            introduction = intro,
            comment = EmailReplyParser.parse_reply(message.text)
        )
        new_comment.save()
    return intro

def process_message_as_new_intro(message):
    msg = message.get_email_object()
    from_addr = get_from_addr(msg)
    delivered_to = get_delivered_to_addr(msg)
    to_addrs = ','.join(get_to_addrs(msg))
    cc_addrs = ','.join(get_to_addrs(msg))
    try:
        person = PersonEmail.objects.get(email_address=from_addr).person
    except PersonEmail.DoesNotExist:
        raise Introduction.DoesNotExist("from address: %s doesn't represent a person in our system.  Message: %s" % (from_addr, message.pk))
    if not person:
 def test_parse_out_just_top_for_outlook_reply(self):
     with open('test/emails/email_2_1.txt') as f:
         self.assertEqual("Outlook with a reply", EmailReplyParser.parse_reply(f.read()))
Пример #47
0
 def test_parse_out_just_top_for_outlook_with_reply_directly_above_line(
         self):
     with open('test/emails/email_2_2.txt') as f:
         self.assertEqual("Outlook with a reply directly above line",
                          EmailReplyParser.parse_reply(f.read()))
 def test_sent_from_iphone(self):
     with open('test/emails/email_iPhone.txt') as email:
         self.assertTrue("Sent from my iPhone" not in EmailReplyParser.parse_reply(email.read()))
Пример #49
0
 def test_reply_from_gmail(self):
     with open("test/emails/email_gmail.txt") as f:
         self.assertEqual(
             "This is a test for inbox replying to a github message.",
             EmailReplyParser.parse_reply(f.read()),
         )
Пример #50
0
def ticket_from_message(message, quiet):
    """
    Create a ticket or a followup (if ticket id in subject)
    """
    msg = message
    message = email.message_from_string(msg)
    subject = message.get('subject', 'Created from e-mail')
    subject = decode_mail_headers(decodeUnknown(message.get_charset(),
                                                subject))
    sender = message.get('from', ('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
    sender_email = parseaddr(sender)[1]
    body_plain, body_html = '', ''

    matchobj = re.match(r".*\[" + "-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
    else:
        ticket = None

    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name == None:
            if part.get_content_subtype() == 'plain':
                body_plain = EmailReplyParser.parse_reply(
                    decodeUnknown(part.get_content_charset(),
                                  part.get_payload(decode=True)))
            else:
                body_html = part.get_payload(decode=True)
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)

            files.append(
                {
                    'filename': name,
                    'content': part.get_payload(decode=True),
                    'type': part.get_content_type()
                }, )

        counter += 1

    if body_plain:
        body = body_plain
    else:
        body = 'No plain-text email body available. Please see attachment email_html_body.html.'

    if body_html:
        files.append({
            'filename': 'email_html_body.html',
            'content': body_html,
            'type': 'text/html',
        })

    now = timezone.now()

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
            new = False
        except Ticket.DoesNotExist:
            ticket = None

    if ticket == None:

        # set owner depending on sender_email
        # list of all email addresses from the user model
        users = User.objects.all()
        email_addresses = []
        for user in users:
            email_addresses.append(user.email)

        ############################################################
        # if ticket id in subject => new followup instead of new ticket
        tickets = Ticket.objects.all()
        ticket_ids = []
        for ticket in tickets:
            ticket_ids.append(ticket.id)

        # extract id from subject
        subject_id = re.search(r'\[#(\d*)\]\s.*', subject)
        try:
            subject_id = subject_id.group(1)
        except:
            subject_id = "0000"  # no valid id

        # if there was an ID in the subject, create followup
        if int(subject_id) in ticket_ids:

            if sender_email in email_addresses:
                f = FollowUp(
                    title=subject,
                    created=now,
                    text=body,
                    ticket=Ticket.objects.get(id=subject_id),
                    user=User.objects.get(email=sender_email),
                )
            else:
                f = FollowUp(
                    title=subject,
                    created=now,
                    text=body,
                    ticket=Ticket.objects.get(id=subject_id),
                )

            f.save()

        # if no ID in the subject, create ticket
        else:

            # if known sender, set also the field owner
            if sender_email in email_addresses:
                t = Ticket(
                    title=subject,
                    status="TODO",
                    created=now,
                    description=body,
                    owner=User.objects.get(email=sender_email),
                )
            # if unknown sender, skip the field owner
            else:
                t = Ticket(
                    title=subject,
                    status="TODO",
                    created=now,
                    description=body,
                )

            t.save()

            from django.core.mail import send_mail
            notification_subject = "[#" + str(t.id) + "] New ticket created"
            notification_body = "Hi,\n\na new ticket was created: http://localhost:8000/ticket/" \
                                + str(t.id) + "/"
            send_mail(notification_subject,
                      notification_body,
                      os.environ["DJANGO_TICKET_EMAIL_NOTIFICATIONS_FROM"],
                      [os.environ["DJANGO_TICKET_EMAIL_NOTIFICATIONS_TO"]],
                      fail_silently=False)

        ############################################################

        new = True
        update = ''

    elif t.status == Ticket.CLOSED_STATUS:
        t.status = Ticket.REOPENED_STATUS
        t.save()

    # files of followups should be assigned to the corresponding ticket
    for file in files:

        if file['content']:

            filename = file['filename'].encode('ascii',
                                               'replace').replace(' ', '_')
            filename = re.sub('[^a-zA-Z0-9._-]+', '', filename)

            # if followup
            if int(subject_id) in ticket_ids:
                a = Attachment(
                    ticket=Ticket.objects.get(id=subject_id),
                    filename=filename,
                    #mime_type=file['type'],
                    #size=len(file['content']),
                )

            # if new ticket
            else:
                a = Attachment(
                    ticket=t,
                    filename=filename,
                    #mime_type=file['type'],
                    #size=len(file['content']),
                )

            a.file.save(filename, ContentFile(file['content']), save=False)
            a.save()

            if not quiet:
                print " - %s" % filename

    if int(subject_id) in ticket_ids:
        return f
    else:
        return t
Пример #51
0
 def test_parse_out_just_top_for_outlook_reply(self):
     with open("test/emails/email_2_1.txt") as f:
         self.assertEqual("Outlook with a reply",
                          EmailReplyParser.parse_reply(f.read()))
Пример #52
0
	def get_message_details(self):
		'''Return args for template'''
		dws_group = frappe.get_doc('Daily Work Summary Group',
			self.daily_work_summary_group)

		replies = frappe.get_all('Communication',
			fields=['content', 'text_content', 'sender'],
			filters=dict(reference_doctype=self.doctype,
				reference_name=self.name,
				communication_type='Communication',
				sent_or_received='Received'),
			order_by='creation asc')

		did_not_reply = self.email_sent_to.split()

		for d in replies:
			user = frappe.db.get_values("User",
				{"email": d.sender},
				["full_name", "user_image"],
				as_dict=True)

			d.sender_name = user[0].full_name if user else d.sender
			d.image = user[0].image if user and user[0].image else None

			original_image = d.image
			# make thumbnail image
			try:
				if original_image:
					file_name = frappe.get_list('File',
						{'file_url': original_image})

					if file_name:
						file_name = file_name[0].name
						file_doc = frappe.get_doc('File', file_name)
						thumbnail_image = file_doc.make_thumbnail(
							set_as_thumbnail=False,
							width=100,
							height=100,
							crop=True
						)
						d.image = thumbnail_image
			except Exception:
				d.image = original_image

			if d.sender in did_not_reply:
				did_not_reply.remove(d.sender)
			if d.text_content:
				d.content = frappe.utils.md_to_html(
					EmailReplyParser.parse_reply(d.text_content)
				)

		did_not_reply = [(frappe.db.get_value("User", {"email": email}, "full_name") or email)
			for email in did_not_reply]

		return dict(replies=replies,
			original_message=dws_group.message,
			title=_('Work Summary for {0}').format(
				global_date_format(self.creation)
			),
			did_not_reply=', '.join(did_not_reply) or '',
			did_not_reply_title=_('No replies from'))
Пример #53
0
 def test_parse_out_just_top_for_outlook_with_unusual_headers_format(self):
     with open("test/emails/email_2_3.txt") as f:
         self.assertEqual(
             "Outlook with a reply above headers using unusual format",
             EmailReplyParser.parse_reply(f.read()),
         )
Пример #54
0
    # Parse E-Mail
    parsed_email = message_from_string(data[0][1].decode('utf-8'))
    parsed_email_from = parseaddr(parsed_email['From'])[1]
    parsed_email_to = parseaddr(parsed_email['To'])[1]
    parsed_email_to_domain = parsed_email_to.split('@')[1]
    parsed_email_session = md5(
        bytes(parsed_email.get('Subject', '').replace('Re: ', '') +
              parsed_email_from,
              encoding='utf8')).hexdigest()
    parsed_email_body = ''
    for part in parsed_email.walk():
        if part.get_content_type() == 'text/plain':
            parsed_email_body += part.get_payload()

    parsed_email_body = EmailReplyParser.parse_reply(parsed_email_body)
    parsed_email_lang = fallback_lang
    try:
        parsed_email_lang = detect(parsed_email_body)
    except:
        pass

    # Log E-Mail
    logging.info('Received new E-Mail')
    logging.info('From: ' + parsed_email_from)
    logging.info('To: ' + parsed_email_to)
    logging.info('Text: ' + parsed_email_body)
    logging.info('Message ID: ' + parsed_email['Message-ID'])
    logging.info('Session ID: ' + parsed_email_session)

    # Build Request
Пример #55
0
 def test_sent_from_iphone(self):
     with open("test/emails/email_iPhone.txt") as email:
         self.assertTrue("Sent from my iPhone" not in
                         EmailReplyParser.parse_reply(email.read()))
Пример #56
0
def ticket_from_message(message, queue, quiet):
    # 'message' must be an RFC822 formatted message.
    msg = message
    message = email.message_from_string(msg.decode('utf-8'))
    subject = message.get('subject', _('Created from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
    subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").replace("Automatic reply: ", "").strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))

    sender_email = parseaddr(sender)[1]

    body_plain, body_html = '', ''

    for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r'.*\['+re.escape(queue.slug)+r'-(?P<id>\d+)\]', subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
    else:
        ticket = None

    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name is None:
            if part.get_content_subtype() == 'plain':
                body_plain = EmailReplyParser.parse_reply(decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)))
            else:
                body_html = decodeUnknown(part.get_content_charset(), part.get_payload(decode=True))
                # make plain text more legible when viewing the ticket
                body_html, n = re.subn(r'[\r\n]+', r'', body_html)
                body_html, n = re.subn(r'\>\s+\<', r'><', body_html)
                body_html = body_html.replace("</h1>", "</h1>\n")
                body_html = body_html.replace("</h2>", "</h2>\n")
                body_html = body_html.replace("</h3>", "</h3>\n")
                body_html = body_html.replace("<p>", "\n<p>")
                body_html = body_html.replace("</p>", "</p>\n")
                body_html = body_html.replace("</div>", "</div>\n")
                body_html = body_html.replace("</tr>", "</tr>\n")
                body_html = body_html.replace("</td>", "</td> ")
                body_html = body_html.replace("<table>", "\n<table>")
                body_html = body_html.replace("</table>", "</table>\n")
                body_html = body_html.replace("<br />", "<br />\n")

                try:
                    # strip html tags
                    body_plain = striptags(body_html)
                except DjangoUnicodeDecodeError:
                    charset = chardet.detect(body_html)['encoding']
                    body_plain = striptags(str(body_html, charset))

                body_plain = unescape(body_plain)
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)

            files.append({
                'filename': name,
                'content': part.get_payload(decode=True),
                'type': part.get_content_type()},
                )

        counter += 1

    if body_plain:
        body = body_plain
        if body_html:
            body += '\n\n'
            body += _('***Note that HTML tags are stripped out. Please see attachment email_html_body.html for the full html content.')
    else:
        body = _('No plain-text email body available. Please see attachment email_html_body.html.')

    if body_html:
        files.append({
            'filename': _("email_html_body.html"),
            'content': body_html,
            'type': 'text/html',
        })

    now = timezone.now()

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
            new = False
        except Ticket.DoesNotExist:
            ticket = None

    priority = 3

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')

    high_priority_types = ('high', 'important', '1', 'urgent')

    if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
        priority = 2

    if ticket is None:
        t = Ticket(
            title=subject,
            queue=queue,
            submitter_email=sender_email,
            created=now,
            description=body,
            priority=priority,
        )
        t.save()
        new = True
        #update = ''

    elif t.status == Ticket.CLOSED_STATUS:
        t.status = Ticket.REOPENED_STATUS
        t.save()

    f = FollowUp(
        ticket = t,
        title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
        date = timezone.now(),
        public = True,
        comment = body,
    )

    if t.status == Ticket.REOPENED_STATUS:
        f.new_status = Ticket.REOPENED_STATUS
        f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})

    f.save()

    if not quiet:
        print((" [%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace'))

    for file in files:
        if file['content']:
            filename = file['filename'].replace(' ', '_')
            filename = re.sub(r'[^a-zA-Z0-9._-]+', '', filename)
            a = Attachment(
                followup=f,
                filename=filename,
                mime_type=file['type'],
                size=len(file['content']),
                )
            a.file.save(filename, ContentFile(file['content']), save=False)
            a.save()
            if not quiet:
                print("    - %s" % filename)

    context = safe_template_context(t)

    if new:

        if sender_email and not is_no_reply_address(sender_email):
            send_templated_mail(
                'newticket_submitter',
                context,
                recipients=sender_email,
                sender=queue.from_address,
                fail_silently=True,
                )

        if queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.new_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
                )

        if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
                )

    else:
        context.update(comment=f.comment)

        #if t.status == Ticket.REOPENED_STATUS:
        #    update = _(' (Reopened)')
        #else:
        #    update = _(' (Updated)')

        if t.assigned_to:
            send_templated_mail(
                'updated_owner',
                context,
                recipients=t.assigned_to.email,
                sender=queue.from_address,
                fail_silently=True,
                )

        if queue.updated_ticket_cc:
            send_templated_mail(
                'updated_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
                )

    return t
Пример #57
0
 def test_email_one_is_not_on(self):
     with open("test/emails/email_one_is_not_on.txt") as email:
         self.assertTrue("On Oct 1, 2012, at 11:55 PM, Dave Tapley wrote:"
                         not in EmailReplyParser.parse_reply(email.read()))
Пример #58
0
def ticket_from_message(message, queue, quiet):
    # 'message' must be an RFC822 formatted message.
    msg = message
    message = email.message_from_string(msg)
    subject = message.get('subject', _('Created from e-mail'))
    subject = decode_mail_headers(decodeUnknown(message.get_charset(),
                                                subject))
    subject = subject.replace("Re: ", "").replace("Fw: ", "").replace(
        "RE: ", "").replace("FW: ", "").replace("Automatic reply: ",
                                                "").strip()

    sender = message.get('from', _('Unknown Sender'))
    sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))

    sender_email = parseaddr(sender)[1]

    body_plain, body_html = '', ''

    for ignore in IgnoreEmail.objects.filter(
            Q(queues=queue) | Q(queues__isnull=True)):
        if ignore.test(sender_email):
            if ignore.keep_in_mailbox:
                # By returning 'False' the message will be kept in the mailbox,
                # and the 'True' will cause the message to be deleted.
                return False
            return True

    matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject)
    if matchobj:
        # This is a reply or forward.
        ticket = matchobj.group('id')
    else:
        ticket = None

    counter = 0
    files = []

    for part in message.walk():
        if part.get_content_maintype() == 'multipart':
            continue

        name = part.get_param("name")
        if name:
            name = collapse_rfc2231_value(name)

        if part.get_content_maintype() == 'text' and name == None:
            if part.get_content_subtype() == 'plain':
                body_plain = EmailReplyParser.parse_reply(
                    decodeUnknown(part.get_content_charset(),
                                  part.get_payload(decode=True)))
            else:
                body_html = part.get_payload(decode=True)
        else:
            if not name:
                ext = mimetypes.guess_extension(part.get_content_type())
                name = "part-%i%s" % (counter, ext)

            files.append(
                {
                    'filename': name,
                    'content': part.get_payload(decode=True),
                    'type': part.get_content_type()
                }, )

        counter += 1

    if body_plain:
        body = body_plain
    else:
        body = _(
            'No plain-text email body available. Please see attachment email_html_body.html.'
        )

    if body_html:
        files.append({
            'filename': _("email_html_body.html"),
            'content': body_html,
            'type': 'text/html',
        })

    now = timezone.now()

    if ticket:
        try:
            t = Ticket.objects.get(id=ticket)
            new = False
        except Ticket.DoesNotExist:
            ticket = None

    priority = 3

    smtp_priority = message.get('priority', '')
    smtp_importance = message.get('importance', '')

    high_priority_types = ('high', 'important', '1', 'urgent')

    if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
        priority = 2

    if ticket == None:
        t = Ticket(
            title=subject,
            queue=queue,
            submitter_email=sender_email,
            created=now,
            description=body,
            priority=priority,
        )
        t.save()
        new = True
        update = ''

    elif t.status == Ticket.CLOSED_STATUS:
        t.status = Ticket.REOPENED_STATUS
        t.save()

    f = FollowUp(
        ticket=t,
        title=_('E-Mail Received from %(sender_email)s' %
                {'sender_email': sender_email}),
        date=timezone.now(),
        public=True,
        comment=body,
    )

    if t.status == Ticket.REOPENED_STATUS:
        f.new_status = Ticket.REOPENED_STATUS
        f.title = _(
            'Ticket Re-Opened by E-Mail Received from %(sender_email)s' %
            {'sender_email': sender_email})

    f.save()

    if not quiet:
        print(" [%s-%s] %s" % (
            t.queue.slug,
            t.id,
            t.title,
        )).encode('ascii', 'replace')

    print files

    for file in files:
        print file
        if file['content']:
            filename = file['filename'].encode('ascii',
                                               'replace').replace(' ', '_')
            print filename
            filename = re.sub('[^a-zA-Z0-9._-]+', '', filename)
            print filename
            a = Attachment(
                followup=f,
                filename=filename,
                mime_type=file['type'],
                size=len(file['content']),
            )
            a.file.save(filename, ContentFile(file['content']), save=False)
            a.save()
            if not quiet:
                print "    - %s" % filename

    context = safe_template_context(t)

    if new:

        if sender_email:
            send_templated_mail(
                'newticket_submitter',
                context,
                recipients=sender_email,
                sender=queue.from_address,
                fail_silently=True,
            )

        if queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.new_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

        if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
            send_templated_mail(
                'newticket_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

    else:
        context.update(comment=f.comment)

        if t.status == Ticket.REOPENED_STATUS:
            update = _(' (Reopened)')
        else:
            update = _(' (Updated)')

        if t.assigned_to:
            send_templated_mail(
                'updated_owner',
                context,
                recipients=t.assigned_to.email,
                sender=queue.from_address,
                fail_silently=True,
            )

        if queue.updated_ticket_cc:
            send_templated_mail(
                'updated_cc',
                context,
                recipients=queue.updated_ticket_cc,
                sender=queue.from_address,
                fail_silently=True,
            )

    return t
Пример #59
0
def get_reply_contents(email):
    return EmailReplyParser.parse_reply(email.text)