email.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. from email import message_from_bytes
  2. import email.header
  3. import os
  4. import re
  5. import datetime
  6. import calendar
  7. class HTML:
  8. def __init__(self, body):
  9. self.body = body
  10. self.type = "text/html"
  11. class PLAIN:
  12. def __init__(self, body):
  13. self.body = body
  14. self.type = "text/plain"
  15. class FILE:
  16. def __init__(self, filename, byte: bytes, content_type: str, content_disposition: str):
  17. self.filename = filename
  18. self.size = len(byte) / 1024 / 1024 # 换算得到mb
  19. self.content_type = content_type
  20. self.content_disposition = content_disposition
  21. if self.size >= 0.1:
  22. self.size_str = f"{self.size:.2f}MB"
  23. elif self.size * 1024 > 0.1:
  24. self.size_str = f"{self.size * 1024:.2f}KB"
  25. else:
  26. self.size_str = f"{int(self.size * 1024 * 1024):d}B"
  27. class Mail:
  28. date_pattern = re.compile(
  29. r"[A-Za-z]+, "
  30. r"([0-9]{1,2}) "
  31. r"([A-Za-z]+) "
  32. r"([0-9]{4}) "
  33. r"([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}) "
  34. r"([\s\S]*)"
  35. )
  36. time_zone_pattern = re.compile(r"([+-])([0-9]{2})00")
  37. def __init__(self, num: str, data: bytes):
  38. self.__date_ = None
  39. self.byte = data
  40. self.num = num
  41. @property
  42. def msg_data(self): # 有需要的时候才加载
  43. if self.__date_:
  44. return self.__date_
  45. self.__date_ = message_from_bytes(self.byte)
  46. return self.__date_
  47. @property
  48. def from_addr(self):
  49. if not self.msg_data['From']:
  50. return ""
  51. return str(email.header.make_header(email.header.decode_header(self.msg_data['From'])))
  52. @property
  53. def to_addr(self):
  54. if not self.msg_data['To']:
  55. return []
  56. res = str(email.header.make_header(email.header.decode_header(self.msg_data['To'])))
  57. return [i.strip() for i in res.split(",")]
  58. @property
  59. def cc_addr(self):
  60. if not self.msg_data['Cc']:
  61. return []
  62. res = str(email.header.make_header(email.header.decode_header(self.msg_data['Cc'])))
  63. return [i.strip() for i in res.split(",")]
  64. @property
  65. def date(self):
  66. if not self.msg_data['Date']:
  67. return datetime.datetime(2022, 1, 1)
  68. date = str(email.header.make_header(email.header.decode_header(self.msg_data['Date'])))
  69. res = self.date_pattern.match(str(date)).groups()
  70. time = datetime.datetime(int(res[2]),
  71. list(calendar.month_abbr).index(res[1]),
  72. int(res[0]),
  73. int(res[3]),
  74. int(res[4]),
  75. int(res[5]))
  76. timezone = self.time_zone_pattern.match(res[6])
  77. if timezone:
  78. if timezone.groups()[0] == '-':
  79. time += datetime.timedelta(hours=int(timezone.groups()[1]))
  80. else:
  81. time -= datetime.timedelta(hours=int(timezone.groups()[1]))
  82. time += datetime.timedelta(hours=8) # 转换为北京时间
  83. return time
  84. @property
  85. def title(self):
  86. if not self.msg_data['Subject']:
  87. return ""
  88. return (str(email.header.make_header(email.header.decode_header(self.msg_data['Subject'])))
  89. .replace('\n', '')
  90. .replace('\r', ''))
  91. @property
  92. def body(self):
  93. return self.__get_body(self.msg_data)
  94. def __get_body(self, msg):
  95. if msg.is_multipart():
  96. res = ""
  97. for i in msg.get_payload():
  98. res += self.__get_body(i)
  99. return res
  100. else:
  101. msg_type = msg.get_content_type()
  102. if msg_type == "text/plain":
  103. return "text/plain:\n" + msg.get_payload(decode=True).decode('utf-8') + "\n"
  104. elif msg_type == "text/html":
  105. return "text/html:\n" + msg.get_payload(decode=True).decode('utf-8') + "\n"
  106. else:
  107. return ""
  108. @property
  109. def body_list(self):
  110. return self.__get_body_list(self.msg_data)
  111. def __get_body_list(self, msg):
  112. if msg.is_multipart():
  113. res = []
  114. for i in msg.get_payload():
  115. son = self.__get_body_list(i)
  116. if son is not None:
  117. res += son
  118. return res
  119. else:
  120. msg_type = msg.get_content_type()
  121. if msg_type == "text/plain":
  122. return [PLAIN(msg.get_payload(decode=True).decode('utf-8'))]
  123. elif msg_type == "text/html":
  124. return [HTML(msg.get_payload(decode=True).decode('utf-8'))]
  125. else:
  126. return None
  127. def save_file(self, file_dir: str):
  128. return self.__get_files(self.msg_data, file_dir)
  129. @staticmethod
  130. def __get_files(msg, file_dir: str):
  131. create = False
  132. for part in msg.walk():
  133. if not create:
  134. os.makedirs(file_dir, exist_ok=True)
  135. create = True
  136. if part.get_content_maintype() == 'multipart':
  137. continue
  138. if part.get('Content-Disposition') is None:
  139. continue
  140. filename = part.get_filename()
  141. if filename:
  142. filepath = os.path.join(file_dir, filename)
  143. with open(filepath, 'wb') as f:
  144. f.write(part.get_payload(decode=True))
  145. @property
  146. def file_list(self):
  147. res = []
  148. for part in self.msg_data.walk():
  149. if part.get_content_maintype() == 'multipart':
  150. continue
  151. if part.get('Content-Disposition') is None:
  152. continue
  153. filename = part.get_filename()
  154. if filename:
  155. res.append(FILE(filename, part.get_payload(decode=True),
  156. part.get_content_type(), part.get('Content-Disposition')))
  157. return res
  158. def get_file(self, filename) -> "(bytes, str, str) | (None, None, None)":
  159. for part in self.msg_data.walk():
  160. if part.get_content_maintype() == 'multipart':
  161. continue
  162. if part.get('Content-Disposition') is None:
  163. continue
  164. if filename == part.get_filename():
  165. return part.get_payload(decode=True), part.get_content_type(), part.get('Content-Disposition')
  166. return None, None, None
  167. def __lt__(self, other: "Mail"):
  168. return self.date < other.date
  169. def __eq__(self, other: "Mail"):
  170. return self.date == other.date
  171. def __le__(self, other: "Mail"):
  172. return self.date <= other.date
  173. def __str__(self):
  174. return f"{self.num} {self.title} {self.from_addr} {self.date}"