Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

202 řádky
5.2 KiB

  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. from __future__ import unicode_literals
  23. """
  24. This module contains classes for managing incoming emails
  25. """
  26. class IncomingMail:
  27. """
  28. Single incoming email object. Extracts, text / html and attachments from the email
  29. """
  30. def __init__(self, content):
  31. """
  32. Parse the incoming mail content
  33. """
  34. import email
  35. self.mail = email.message_from_string(content)
  36. self.text_content = ''
  37. self.html_content = ''
  38. self.attachments = []
  39. self.parse()
  40. def get_text_content(self):
  41. """
  42. Returns the text parts of the email. If None, then HTML parts
  43. """
  44. return self.text_content or self.html_content
  45. def get_charset(self, part):
  46. """
  47. Guesses character set
  48. """
  49. charset = part.get_content_charset()
  50. if not charset:
  51. import chardet
  52. charset = chardet.detect(str(part))['encoding']
  53. return charset
  54. def get_payload(self, part, charset):
  55. """
  56. get utf-8 encoded part content
  57. """
  58. try:
  59. return unicode(part.get_payload(decode=True),str(charset),"ignore")
  60. except LookupError, e:
  61. return part.get_payload()
  62. def get_attachment(self, part, charset):
  63. """
  64. Extracts an attachment
  65. """
  66. self.attachments.append({
  67. 'content-type': part.get_content_type(),
  68. 'filename': part.get_filename(),
  69. 'content': part.get_payload(decode=True),
  70. })
  71. def parse(self):
  72. """
  73. Extracts text, html and attachments from the mail
  74. """
  75. for part in self.mail.walk():
  76. self.process_part(part)
  77. def get_thread_id(self):
  78. """
  79. Extracts thread id of the message between first []
  80. from the subject
  81. """
  82. import re
  83. subject = self.mail.get('Subject', '')
  84. return re.findall('(?<=\[)[\w/-]+', subject)
  85. def process_part(self, part):
  86. """
  87. Process a single part of an email
  88. """
  89. content_type = part.get_content_type()
  90. charset = part.get_content_charset()
  91. if not charset: charset = self.get_charset(part)
  92. if content_type == 'text/plain':
  93. self.text_content += self.get_payload(part, charset)
  94. if content_type == 'text/html':
  95. self.html_content += self.get_payload(part, charset)
  96. if part.get_filename():
  97. self.get_attachment(part, charset)
  98. class POP3Mailbox:
  99. """
  100. A simple pop3 mailbox, abstracts connection and mail extraction
  101. To use, subclass it and override method process_message(from, subject, text, thread_id)
  102. """
  103. def __init__(self, settings_doc):
  104. """
  105. settings_doc must contain
  106. use_ssl, host, username, password
  107. (by name or object)
  108. """
  109. if isinstance(settings_doc, basestring):
  110. from webnotes.model.doc import Document
  111. self.settings = Document(settings_doc, settings_doc)
  112. else:
  113. self.settings = settings_doc
  114. def connect(self):
  115. """
  116. Connects to the mailbox
  117. """
  118. import poplib
  119. if self.settings.use_ssl:
  120. self.pop = poplib.POP3_SSL(self.settings.host)
  121. else:
  122. self.pop = poplib.POP3(self.settings.host)
  123. self.pop.user(self.settings.username)
  124. self.pop.pass_(self.settings.password)
  125. def get_messages(self):
  126. """
  127. Loads messages from the mailbox and calls
  128. process_message for each message
  129. """
  130. import webnotes
  131. if not self.check_mails():
  132. return # nothing to do
  133. self.connect()
  134. num = num_copy = len(self.pop.list()[1])
  135. # track if errors arised
  136. errors = False
  137. # WARNING: Hard coded max no. of messages to be popped
  138. if num > 20: num = 20
  139. for m in xrange(1, num+1):
  140. msg = self.pop.retr(m)
  141. try:
  142. webnotes.conn.begin()
  143. self.process_message(IncomingMail(b'\n'.join(msg[1])))
  144. self.pop.dele(m)
  145. webnotes.conn.commit()
  146. except:
  147. from webnotes.utils.scheduler import log
  148. # log performs rollback and logs error in scheduler log
  149. log("receive.get_messages")
  150. errors = True
  151. webnotes.conn.begin()
  152. # WARNING: Delete message number 101 onwards from the pop list
  153. # This is to avoid having too many messages entering the system
  154. num = num_copy
  155. if num > 100 and not errors:
  156. for m in xrange(101, num+1):
  157. self.pop.dele(m)
  158. self.pop.quit()
  159. def check_mails(self):
  160. """
  161. To be overridden
  162. If mailbox is to be scanned, returns true
  163. """
  164. return True
  165. def process_message(self, mail):
  166. """
  167. To be overriden
  168. """
  169. pass