imap.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import json
  2. import os
  3. import re
  4. from dataclasses import dataclass
  5. from datetime import datetime, timedelta
  6. import plac
  7. from imap_tools import AND, MailBox, MailMessage
  8. @dataclass
  9. class ImapCredentials:
  10. server: str
  11. username: str
  12. password: str
  13. class Imap:
  14. commands = ["cleanup", "move", "remove", "add_to_whitelist", "undelivered"]
  15. whitelist = {}
  16. blacklist = []
  17. credentials = {
  18. "archiv": ImapCredentials("mail.global-cube.com", "archiv", "Gc01am64!"),
  19. "versand": ImapCredentials("mail.global-cube.com", "versand", "y6!avXX3tQvr"),
  20. }
  21. def __init__(self):
  22. with open(os.path.dirname(__file__) + "/whitelist.json", "r") as frh:
  23. self.whitelist = json.load(frh)
  24. with open(os.path.dirname(__file__) + "/blacklist.json", "r") as frh:
  25. self.blacklist = json.load(frh)
  26. def connect(self, key):
  27. creds = self.credentials[key]
  28. return MailBox(creds.server).login(creds.username, creds.password)
  29. def cleanup(self):
  30. date_now = datetime.now()
  31. date_criteria = (date_now - timedelta(days=60)).date()
  32. msg_limit = 100
  33. with self.connect("archiv") as mb:
  34. folder_list = [f.name for f in mb.folder.list() if "Archive." in f.name]
  35. folder_list.append("INBOX")
  36. folder_list.sort()
  37. with open("mailserver/folder_list.json", "w") as fwh:
  38. json.dump(folder_list, fwh, indent=2)
  39. for folder in folder_list:
  40. msg_count = mb.folder.status(folder)["MESSAGES"]
  41. if msg_count < msg_limit:
  42. continue
  43. mb.folder.set(folder)
  44. uids = [0] * (msg_limit + 1)
  45. while len(uids) > msg_limit:
  46. messages = mb.fetch(
  47. criteria=AND(date_lt=date_criteria), mark_seen=True, limit=1000, headers_only=True
  48. )
  49. uids = [msg.uid for msg in messages]
  50. mb.delete(uids[0 : (msg_count - msg_limit)])
  51. def move(self):
  52. with self.connect("archiv") as mb:
  53. messages = mb.fetch(
  54. criteria=AND(from_="@global-cube.com"),
  55. mark_seen=True,
  56. bulk=True,
  57. limit=2_000,
  58. headers_only=True,
  59. reverse=True,
  60. )
  61. for msg in messages:
  62. if (
  63. msg.subject.count(";") > 3
  64. or "status@global-cube.com" in msg.to
  65. or "Kontenaktualisierung" in msg.subject
  66. or "Wochenbericht" in msg.subject
  67. ):
  68. # statusmail
  69. mb.delete([msg.uid])
  70. continue
  71. match = re.findall(r"\+(.*)@", msg.from_)
  72. if not match:
  73. # print(msg.from_, msg.to, msg.subject)
  74. continue
  75. domain = "@" + match[0]
  76. subfolder = "Archive." + domain.replace("@", "").replace(".", "_")
  77. if not mb.folder.exists(subfolder):
  78. mb.folder.create(subfolder)
  79. if self.is_valid_message(msg, domain):
  80. mb.move(msg.uid, subfolder)
  81. # else:
  82. # print(domain, ', '.join(msg.to), msg.subject)
  83. # print(msg.text)
  84. # print([att.filename for att in msg.attachments if att.filename.endswith('.pdf')])
  85. def is_valid_message(self, msg, domain):
  86. for to in msg.to:
  87. if domain in to or "@global-cube.de" in to or msg.from_ == to:
  88. continue
  89. if domain not in self.whitelist:
  90. return False
  91. valid_domain = [entry in to for entry in self.whitelist[domain]]
  92. if not any(valid_domain):
  93. return False
  94. return True
  95. def add_to_whitelist(self):
  96. with self.connect("archiv") as mb:
  97. mb.folder.set("whitelist")
  98. messages = mb.fetch(
  99. criteria=AND(from_="@global-cube.com"), mark_seen=True, bulk=True, limit=1000, headers_only=True
  100. )
  101. for msg in messages:
  102. match = re.findall(r"\+(.*)@", msg.from_)
  103. if not match:
  104. # print(msg.from_, msg.to, msg.subject)
  105. continue
  106. domain = "@" + match[0]
  107. if not self.is_valid_message(msg, domain):
  108. if domain not in self.whitelist:
  109. self.whitelist[domain] = []
  110. for to in msg.to:
  111. if domain not in to and to not in self.whitelist[domain]:
  112. self.whitelist[domain].append(to)
  113. with open(os.path.dirname(__file__) + "/whitelist.json", "w") as fwh:
  114. json.dump(self.whitelist, fwh, indent=2)
  115. uids = [msg.uid for msg in messages]
  116. mb.delete(uids)
  117. def remove_absence_messages(self):
  118. with self.connect("versand") as mb:
  119. messages = mb.fetch(
  120. criteria=AND(to="@global-cube.com"), mark_seen=False, bulk=True, limit=1000, headers_only=True
  121. )
  122. selected = []
  123. for msg in messages:
  124. for b in self.blacklist:
  125. if b in msg.subject or b in msg.text:
  126. selected.append(msg.uid)
  127. mb.delete(selected)
  128. def remove(self):
  129. self.remove_absence_messages()
  130. def undelivered(self):
  131. with self.conncet("versand") as mb:
  132. selected = []
  133. for msg in mb.fetch(
  134. criteria=AND(to="@global-cube.com"),
  135. mark_seen=False,
  136. bulk=True,
  137. limit=1000,
  138. ):
  139. for k in ["undeliverable", "returned", "zurückgeschickt"]:
  140. if k in msg.subject.lower():
  141. match = re.search(
  142. r"<([\w\-\+\.]+@[\w\-\.]+\.[\w\-\.]+)>(.*)",
  143. msg.text,
  144. re.DOTALL,
  145. )
  146. if match:
  147. mail_to = match[1]
  148. text = self.get_error_message(match[2])
  149. selected.append(";".join([mail_to, text]))
  150. if len(msg.attachments) > 0:
  151. msg2 = MailMessage.from_bytes(msg.attachments[0].payload)
  152. selected[-1] += ";" + ";".join([msg2.from_, msg2.subject, msg2.date_str])
  153. with open(os.path.dirname(__file__) + "/undelivered.csv", "w") as fwh:
  154. fwh.write("\n".join(selected))
  155. def get_error_message(self, text):
  156. m1 = re.search(r"550 5.1.1 (Mailbox <.*> does not exist)", text)
  157. if m1:
  158. return m1[1]
  159. m2 = re.search(r"(Host or domain name not found)", text)
  160. if m2:
  161. return m2[1]
  162. m3 = re.search(r"550 5.1.1 (User unknown: .*)\(", text)
  163. if m3:
  164. return m3[1]
  165. m4 = re.search(r"550 5.1.1 (.*)", text)
  166. if m4:
  167. return m4[1].replace("\n", "").replace("\r", "")
  168. m5 = re.search(r"(X-Microsoft-Antispam)", text)
  169. if m5:
  170. return m5[1]
  171. return text[:100]
  172. if __name__ == "__main__":
  173. plac.Interpreter.call(Imap)
  174. # Imap().undelivered()