Guest User

Untitled

a guest
Feb 14th, 2018
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.96 KB | None | 0 0
  1. import email
  2. import email.utils
  3. import poplib
  4. from collections import OrderedDict
  5. from functools import lru_cache
  6.  
  7. """
  8. Wrapper around poplib.POP3/poplib.POP3_SSL to make it easier to use. Doesn't (currently)
  9. offer all functionality of poplib, but it does what I need it to.
  10.  
  11. Best used via the context manager connect() or connect_ssl():
  12.  
  13. with pop3.connect_ssl(host, username, password) as p:
  14. for msg in p.messages.values(): # messages is a mapping: `num` -> `message`
  15. print(msg.summary)
  16. print(' to:', msg.headers['To'])
  17. """
  18.  
  19. class Message:
  20. """Message represents a message on a POP3 server"""
  21.  
  22. def __init__(self, pop3, num, size):
  23. """Initialize a new instance. Don't call this manually: this is only
  24. meant to be called by Pop3 instances."""
  25. self.pop3 = pop3
  26. self.num = num
  27. self.size = size
  28. self.marked_for_deletion = False
  29.  
  30. def __repr__(self):
  31. return 'Message(num=%d, size=%d)' % (self.num, self.size)
  32.  
  33. def __str__(self):
  34. return self.summary
  35.  
  36. @property
  37. @lru_cache()
  38. def headers(self):
  39. """The message's headers as an email.message instance"""
  40. return self.pop3._top(self.num, 0)
  41.  
  42. @property
  43. @lru_cache()
  44. def email(self):
  45. """The complete email as an email.message instance"""
  46. return self.pop3._retr(self.num)
  47.  
  48. @property
  49. def summary(self):
  50. """Summary line: deletion flag, message number, from, date, subject"""
  51. return '%s%-3d %-20s %-25s %s' % (
  52. '*' if self.marked_for_deletion else ' ',
  53. self.num,
  54. self.headers['From'],
  55. email.utils.parsedate_to_datetime(self.headers['Date']).isoformat(sep=' '),
  56. self.headers['Subject'],
  57. )
  58.  
  59. def mark_for_deletion(self):
  60. """Mark this message for deletion. The server will delete the message
  61. when the connection is closed."""
  62. self.pop3._mark_for_deletion(self.num)
  63.  
  64. class Pop3:
  65. """Connection to a POP3 mailbox (simple wrapper around poplib.POP3 or poplib.POP3_SSL)
  66.  
  67. Attribute `messages` holds the messages in the mailbox, as follows:
  68. Messages are represented by Message instances. Each message has an attribute num which
  69. is used to uniquely identify the message when communication with the server.
  70. `messages` is an OrderedDict indexed on `num`.
  71. Use e.g. `messages.values()` to get all messages.
  72. """
  73.  
  74. def __init__(self, poplib_pop3, username, password):
  75. """Initialize a new instance. This is normally called from the connect or
  76. connect_ssl context managers.
  77.  
  78. poplib_pop3: pre-made poplib.POP3 or poplib.POP3_SSL instance
  79. username: username
  80. password: password
  81. """
  82. self.poplib_pop3 = poplib_pop3
  83. self.poplib_pop3.user(username)
  84. self.poplib_pop3.pass_(password)
  85. self.messages = self._request_list()
  86.  
  87. def _request_list(self):
  88. """Request the list of messages from the server"""
  89. response, msg_infos, size = self.poplib_pop3.list()
  90. messages = OrderedDict()
  91. for msg_info in msg_infos:
  92. msg_num_string, size_string = msg_info.split()
  93. msg_num = int(msg_num_string)
  94. size = int(size_string)
  95. messages[msg_num] = Message(self, msg_num, size)
  96. return messages
  97.  
  98. @property
  99. def greeting(self):
  100. """Server greeting"""
  101. return self.poplib_pop3.getwelcome()
  102.  
  103. def _mark_for_deletion(self, num):
  104. """Mark message <num> for deletion"""
  105. if num not in self.messages:
  106. raise KeyError('Invalid message number %d' % num)
  107. self.poplib_pop3.dele(num)
  108. self.messages[num].marked_for_deletion = True
  109.  
  110. def _email_from_lines(self, lines):
  111. """Parse email as email.message from lines as we get them from the server"""
  112. # lines as we get them from the poplib module are bytestrings, but the
  113. # email module needs a string. Which codec to use? Depends on the
  114. # encoding specified in the headers, I would think, but we don't know
  115. # that yet.
  116. # Use UTF-8 for now ...
  117. message = ''.join(line.decode('UTF-8') + 'n' for line in lines)
  118. return email.message_from_string(message)
  119.  
  120. def _top(self, num, nr_lines_extra):
  121. """Retrieve header + nr_lines_extra lines from server as an email.message instance"""
  122. if num not in self.messages:
  123. raise KeyError('Invalid message number %d' % num)
  124. response, lines, size = self.poplib_pop3.top(num, nr_lines_extra)
  125. return self._email_from_lines(lines)
  126.  
  127. def _retr(self, num):
  128. """Retrieve message <num> from server as an email.message instance"""
  129. if num not in self.messages:
  130. raise KeyError('Invalid message number %d' % num)
  131. response, lines, size = self.poplib_pop3.retr(num)
  132. return self._email_from_lines(lines)
  133.  
  134. def reset_deletion_marks(self):
  135. """Reset all deletion marks"""
  136. self.poplib_pop3.rset()
  137.  
  138. def close(self):
  139. """Close the connection. Normally handed for you by the context manager."""
  140. self.poplib_pop3.quit()
  141.  
  142. class connect:
  143. """Context manager for Pop3 without SSL"""
  144. def __init__(self, host, username, password, port=poplib.POP3_PORT, timeout=None):
  145. self.pop3 = Pop3(
  146. poplib.POP3(host, port, timeout),
  147. username,
  148. password
  149. )
  150. def __enter__(self):
  151. return self.pop3
  152. def __exit__(self, exc_type, exc_value, traceback):
  153. self.pop3.close()
  154.  
  155. class connect_ssl:
  156. """Context manager for Pop3 with SSL"""
  157. def __init__(self, host, username, password, port=poplib.POP3_SSL_PORT, keyfile=None, certfile=None, timeout=None, context=None):
  158. self.pop3 = Pop3(
  159. poplib.POP3_SSL(host, port, keyfile, certfile, timeout, context),
  160. username,
  161. password
  162. )
  163. def __enter__(self):
  164. return self.pop3
  165. def __exit__(self, exc_type, exc_value, traceback):
  166. self.pop3.close()
Add Comment
Please, Sign In to add comment