You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

184 regels
4.9 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import webnotes
  5. from webnotes.utils import extract_email_id, convert_utc_to_user_timezone, now
  6. class IncomingMail:
  7. """
  8. Single incoming email object. Extracts, text / html and attachments from the email
  9. """
  10. def __init__(self, content):
  11. import email, email.utils
  12. import datetime
  13. self.mail = email.message_from_string(content)
  14. self.text_content = ''
  15. self.html_content = ''
  16. self.attachments = []
  17. self.parse()
  18. self.set_content_and_type()
  19. self.set_subject()
  20. self.from_email = extract_email_id(self.mail["From"])
  21. self.from_real_name = email.utils.parseaddr(self.mail["From"])[0]
  22. if self.mail["Date"]:
  23. utc = email.utils.mktime_tz(email.utils.parsedate_tz(self.mail["Date"]))
  24. utc_dt = datetime.datetime.utcfromtimestamp(utc)
  25. self.date = convert_utc_to_user_timezone(utc_dt).strftime('%Y-%m-%d %H:%M:%S')
  26. else:
  27. self.date = now()
  28. def parse(self):
  29. for part in self.mail.walk():
  30. self.process_part(part)
  31. def set_subject(self):
  32. import email.header
  33. _subject = email.header.decode_header(self.mail.get("Subject", "No Subject"))
  34. self.subject = _subject[0][0] or ""
  35. if _subject[0][1]:
  36. self.subject = self.subject.decode(_subject[0][1])
  37. else:
  38. # assume that the encoding is utf-8
  39. self.subject = self.subject.decode("utf-8")
  40. def set_content_and_type(self):
  41. self.content, self.content_type = '[Blank Email]', 'text/plain'
  42. if self.text_content:
  43. self.content, self.content_type = self.text_content, 'text/plain'
  44. else:
  45. self.content, self.content_type = self.html_content, 'text/html'
  46. def process_part(self, part):
  47. content_type = part.get_content_type()
  48. charset = part.get_content_charset()
  49. if not charset: charset = self.get_charset(part)
  50. if content_type == 'text/plain':
  51. self.text_content += self.get_payload(part, charset)
  52. if content_type == 'text/html':
  53. self.html_content += self.get_payload(part, charset)
  54. if part.get_filename():
  55. self.get_attachment(part, charset)
  56. def get_text_content(self):
  57. return self.text_content or self.html_content
  58. def get_charset(self, part):
  59. charset = part.get_content_charset()
  60. if not charset:
  61. import chardet
  62. charset = chardet.detect(str(part))['encoding']
  63. return charset
  64. def get_payload(self, part, charset):
  65. try:
  66. return unicode(part.get_payload(decode=True),str(charset),"ignore")
  67. except LookupError, e:
  68. return part.get_payload()
  69. def get_attachment(self, part, charset):
  70. self.attachments.append({
  71. 'content-type': part.get_content_type(),
  72. 'filename': part.get_filename(),
  73. 'content': part.get_payload(decode=True),
  74. })
  75. def save_attachments_in_doc(self, doc):
  76. from webnotes.utils.file_manager import save_file, MaxFileSizeReachedError
  77. for attachment in self.attachments:
  78. try:
  79. fid = save_file(attachment['filename'], attachment['content'],
  80. doc.doctype, doc.name)
  81. except MaxFileSizeReachedError:
  82. # bypass max file size exception
  83. pass
  84. except webnotes.DuplicateEntryError:
  85. # same file attached twice??
  86. pass
  87. def get_thread_id(self):
  88. import re
  89. l = re.findall('(?<=\[)[\w/-]+', self.subject)
  90. return l and l[0] or None
  91. class POP3Mailbox:
  92. def __init__(self, args=None):
  93. self.setup(args)
  94. self.get_messages()
  95. def setup(self, args=None):
  96. # overrride
  97. import webnotes
  98. self.settings = args or webnotes._dict()
  99. def check_mails(self):
  100. # overrride
  101. return True
  102. def process_message(self, mail):
  103. # overrride
  104. pass
  105. def connect(self):
  106. import poplib
  107. if self.settings.use_ssl:
  108. self.pop = poplib.POP3_SSL(self.settings.host)
  109. else:
  110. self.pop = poplib.POP3(self.settings.host)
  111. self.pop.user(self.settings.username)
  112. self.pop.pass_(self.settings.password)
  113. def get_messages(self):
  114. import webnotes
  115. if not self.check_mails():
  116. return # nothing to do
  117. webnotes.conn.commit()
  118. self.connect()
  119. num = num_copy = len(self.pop.list()[1])
  120. # track if errors arised
  121. errors = False
  122. # WARNING: Hard coded max no. of messages to be popped
  123. if num > 20: num = 20
  124. for m in xrange(1, num+1):
  125. msg = self.pop.retr(m)
  126. # added back dele, as most pop3 servers seem to require msg to be deleted
  127. # else it will again be fetched in self.pop.list()
  128. self.pop.dele(m)
  129. try:
  130. incoming_mail = IncomingMail(b'\n'.join(msg[1]))
  131. webnotes.conn.begin()
  132. self.process_message(incoming_mail)
  133. webnotes.conn.commit()
  134. except:
  135. from webnotes.utils.scheduler import log
  136. # log performs rollback and logs error in scheduler log
  137. log("receive.get_messages")
  138. errors = True
  139. webnotes.conn.rollback()
  140. # WARNING: Mark as read - message number 101 onwards from the pop list
  141. # This is to avoid having too many messages entering the system
  142. num = num_copy
  143. if num > 100 and not errors:
  144. for m in xrange(101, num+1):
  145. self.pop.dele(m)
  146. self.pop.quit()
  147. webnotes.conn.begin()