sender-tk.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import tkinter as tk
  2. import tkinter.messagebox as msg
  3. import tkinter.filedialog as fd
  4. import tkinter.scrolledtext as st
  5. from typing import Optional
  6. import smtplib
  7. import json
  8. import os.path
  9. from sender.email import Email
  10. from sender.smtp import Sender
  11. # 顶层Frame
  12. FRAME_PADX = 5
  13. # 顶层窗口按钮
  14. BUTTON_WIDTH = 40
  15. BUTTON_WIDTH_L = 20
  16. BUTTON_HEIGHT = 1
  17. # 邮件输入框
  18. TEXT_WIDTH = 40
  19. TEXT_HEIGHT = 20
  20. # 邮件信息显示框
  21. EMAIL_INFO_BUTTON_WIDTH = 20
  22. EMAIL_INFO_BUTTON_HEIGHT = 1
  23. EMAIL_INFO_WIDTH = 100
  24. EMAIL_INFO_HEIGHT = 40
  25. # 登录框
  26. LOGIN_LABEL_WIDTH = 10
  27. LOGIN_LABEL_HEIGHT = 1
  28. LOGIN_ENTRY_WIDTH = 35
  29. LOGIN_BTN_WIDTH = 20
  30. LOGIN_RBTN_WIDTH = 6
  31. # 邮件框
  32. CREATE_LABEL_WIDTH = 15
  33. CREATE_LABEL_HEIGHT = 1
  34. CREATE_ENTRY_WIDTH = 35
  35. # 收件人添加
  36. ADD_RC_LABEL_WIDTH = 10
  37. ADD_RC_LABEL_HEIGHT = 1
  38. ADD_RC_ENTRY_WIDTH = 25
  39. ADD_RC_BUTTON_WIDTH = 10
  40. ADD_RC_BUTTON_HEIGHT = 1
  41. ADD_RC_LISTBOX_WIDTH = 35
  42. ADD_RC_LISTBOX_HEIGHT = 10
  43. class SenderGUI(tk.Tk):
  44. def __init__(self):
  45. super().__init__()
  46. self.sender: Optional[Sender] = None
  47. self.email: Optional[Email] = None
  48. self.iconbitmap('static/HuanMail.ico')
  49. self.title("HuanMail (SMTP)")
  50. self.resizable(False, False)
  51. self.login_frame = tk.Frame(self)
  52. self.email_create_frame = tk.Frame(self)
  53. self.add_to_frame = tk.Frame(self)
  54. self.text_frame = tk.Frame(self)
  55. self.file_frame = tk.Frame(self)
  56. self.config_frame = tk.Frame(self)
  57. self.control_frame = tk.Frame(self)
  58. for i in (self.login_frame, self.email_create_frame, self.add_to_frame, self.text_frame,
  59. self.file_frame, self.config_frame, self.control_frame):
  60. i.pack(side="top", padx=FRAME_PADX)
  61. button_arg = dict(width=BUTTON_WIDTH_L, height=BUTTON_HEIGHT, border=2)
  62. button_big_arg = dict(width=BUTTON_WIDTH, height=BUTTON_HEIGHT, border=2)
  63. self.login = tk.Button(self.login_frame, text="Login", command=self.show_login, **button_arg)
  64. self.user_show = tk.Button(self.login_frame, text="Show User", command=self.show_user, **button_arg)
  65. self.create = tk.Button(self.email_create_frame, text="Create", command=self.show_create, **button_arg)
  66. self.email_show = tk.Button(self.email_create_frame, text="Show Email", command=self.show_email, **button_arg)
  67. self.add_to = tk.Button(self.add_to_frame, text="Add Recipient", command=self.show_add_rc, **button_big_arg)
  68. self.text = st.ScrolledText(self.text_frame, width=TEXT_WIDTH, height=TEXT_HEIGHT)
  69. self.text_control_frame = tk.Frame(self.text_frame)
  70. self.add_as_html = tk.Button(self.text_control_frame, text="Add as HTML",
  71. command=self.add_as_html, **button_arg)
  72. self.add_as_text = tk.Button(self.text_control_frame, text="Add as TEXT",
  73. command=self.add_as_text, **button_arg)
  74. self.text.pack(side="top")
  75. self.text_control_frame.pack(side="top")
  76. self.load = tk.Button(self.config_frame, text="Load", command=self.load_config, **button_arg)
  77. self.save = tk.Button(self.config_frame, text="Save", command=self.save_config, **button_arg)
  78. self.send_btn = tk.Button(self.control_frame, text="Send", command=self.send_mail, **button_arg)
  79. self.quit_btn = tk.Button(self.control_frame, text="Quit", command=self.destroy, **button_arg)
  80. for i in (self.login, self.user_show, self.create, self.email_show, self.add_as_html,
  81. self.add_as_text, self.load, self.save, self.send_btn, self.quit_btn):
  82. i.pack(side="left", pady=2.5, ipadx=2)
  83. self.add_to.pack(side="left", pady=2.5, ipadx=8)
  84. self.add_html = tk.Button(self.file_frame, text="Add HTML From File", command=self.add_html, **button_big_arg)
  85. self.add_text = tk.Button(self.file_frame, text="Add TEXT From File", command=self.add_text, **button_big_arg)
  86. self.add_file = tk.Button(self.file_frame, text="Add File", command=self.add_file, **button_big_arg)
  87. for i in (self.add_html, self.add_text, self.add_file):
  88. i.pack(side="top", pady=2.5, ipadx=8)
  89. def show_login(self):
  90. win = tk.Toplevel(self)
  91. win.title("User Login")
  92. win.resizable(False, False)
  93. label_arg = dict(width=LOGIN_LABEL_WIDTH, height=LOGIN_LABEL_HEIGHT)
  94. entry_arg = dict(width=LOGIN_ENTRY_WIDTH)
  95. host_frame = tk.Frame(win)
  96. host_label = tk.Label(host_frame, text="Host: ", **label_arg)
  97. host = tk.Entry(host_frame, **entry_arg)
  98. host_label.pack(side="left")
  99. host.pack(side="left")
  100. host_frame.pack(side="top", pady=2, padx=5)
  101. port_frame = tk.Frame(win)
  102. port_label = tk.Label(port_frame, text="Port: ", **label_arg)
  103. port = tk.Entry(port_frame, **entry_arg)
  104. port_label.pack(side="left")
  105. port.pack(side="left")
  106. port_frame.pack(side="top", pady=2, padx=5)
  107. user_frame = tk.Frame(win)
  108. user_label = tk.Label(user_frame, text="User: ", **label_arg)
  109. user = tk.Entry(user_frame, **entry_arg)
  110. user_label.pack(side="left")
  111. user.pack(side="left")
  112. user_frame.pack(side="top", pady=2, padx=5)
  113. passwd_frame = tk.Frame(win)
  114. passwd_label = tk.Label(passwd_frame, text="Passwd: ", **label_arg)
  115. passwd = tk.Entry(passwd_frame, **entry_arg)
  116. passwd_label.pack(side="left")
  117. passwd.pack(side="left")
  118. passwd_frame.pack(side="top", pady=2, padx=5)
  119. ssl_frame = tk.Frame(win)
  120. ssl_label = tk.Label(ssl_frame, text="SSL: ", **label_arg)
  121. ssl = tk.IntVar(ssl_frame)
  122. ssl.set(1)
  123. ssl_btn = (tk.Radiobutton(ssl_frame, variable=ssl, value=1, text="No", width=LOGIN_RBTN_WIDTH),
  124. tk.Radiobutton(ssl_frame, variable=ssl, value=2, text="SSL", width=LOGIN_RBTN_WIDTH),
  125. tk.Radiobutton(ssl_frame, variable=ssl, value=3, text="StartSSL", width=LOGIN_RBTN_WIDTH))
  126. ssl_label.pack(side="left")
  127. for i in ssl_btn:
  128. i.pack(side="left")
  129. ssl_frame.pack(side="top", pady=2, padx=5)
  130. login_btn = tk.Button(win, text="Login", width=LOGIN_BTN_WIDTH,
  131. command=lambda: self.login_user(host.get(), port.get(), user.get(), passwd.get(),
  132. ssl.get(), win=win))
  133. login_btn.pack(side="top", pady=2)
  134. def login_user(self, host, port, user, passwd, ssl: int, win=None):
  135. try:
  136. port = int(port)
  137. except (TypeError, ValueError):
  138. msg.showerror("Login error", "Port must be int.")
  139. return
  140. if ssl == 1:
  141. ssl = False
  142. start_ssl = False
  143. elif ssl == 2:
  144. ssl = True
  145. start_ssl = False
  146. else:
  147. ssl = False
  148. start_ssl = True
  149. try:
  150. self.sender = Sender(user, passwd, host=host, port=port, debug=False, ssl=ssl, start_ssl=start_ssl)
  151. except smtplib.SMTPAuthenticationError:
  152. msg.showerror("Login error", "Sorry, SMTP Authentication error. Please check your user and password.")
  153. except smtplib.SMTPException:
  154. msg.showerror("Login error", "Sorry, SMTP error.")
  155. except Exception:
  156. msg.showerror("Login error", "Sorry, Unknown error.")
  157. else:
  158. if win:
  159. win.destroy()
  160. msg.showinfo("Login", "Success!")
  161. def show_user(self):
  162. if self.sender:
  163. msg.showinfo("User",
  164. f"""Host: {self.sender.host}:{self.sender.port}
  165. User: {self.sender.user}
  166. Passwd: {self.sender.passwd}""")
  167. else:
  168. msg.showwarning("Not login", "You should login first.")
  169. def show_create(self):
  170. win = tk.Toplevel(self)
  171. win.title("Email Create")
  172. win.resizable(False, False)
  173. label_arg = dict(width=CREATE_LABEL_WIDTH, height=CREATE_LABEL_HEIGHT)
  174. entry_arg = dict(width=CREATE_ENTRY_WIDTH)
  175. from_name_frame = tk.Frame(win)
  176. from_name_label = tk.Label(from_name_frame, text="From Name: ", **label_arg)
  177. from_name = tk.Entry(from_name_frame, **entry_arg)
  178. from_name_label.pack(side="left")
  179. from_name.pack(side="left")
  180. from_name_frame.pack(side="top", pady=2, padx=5)
  181. from_address_frame = tk.Frame(win)
  182. from_address_label = tk.Label(from_address_frame, text="From Address: ", **label_arg)
  183. from_address = tk.Entry(from_address_frame, **entry_arg)
  184. from_address_label.pack(side="left")
  185. from_address.pack(side="left")
  186. from_address_frame.pack(side="top", pady=2, padx=5)
  187. title_frame = tk.Frame(win)
  188. title_label = tk.Label(title_frame, text="Title: ", **label_arg)
  189. title = tk.Entry(title_frame, **entry_arg)
  190. title_label.pack(side="left")
  191. title.pack(side="left")
  192. title_frame.pack(side="top", pady=2, padx=5)
  193. create_btn = tk.Button(win, text="Create", width=LOGIN_BTN_WIDTH,
  194. command=lambda: self.create_email(from_name.get(), from_address.get(),
  195. title.get(), win=win))
  196. create_btn.pack(side="top", pady=2)
  197. def create_email(self, from_name, from_address, title, win=None):
  198. self.email = Email((from_name, from_address), title)
  199. if win:
  200. win.destroy()
  201. msg.showinfo("Create", "Success!")
  202. def show_email(self):
  203. if not self.email:
  204. msg.showwarning("Not email", "You should create email first.")
  205. return
  206. win = tk.Toplevel(self)
  207. win.title("Email info")
  208. win.resizable(False, False)
  209. arg = dict(width=EMAIL_INFO_WIDTH)
  210. from_addr = tk.Label(win, text=f"From: {self.email.from_addr[0]} <{self.email.from_addr[1]}>", **arg)
  211. title = tk.Label(win, text=f"Title: {self.email.subject}", **arg)
  212. info = st.ScrolledText(win, height=EMAIL_INFO_HEIGHT, state=tk.DISABLED, **arg)
  213. btn_frame = tk.Frame(win)
  214. for i in (from_addr, title, info, btn_frame):
  215. i.pack(side="top", pady=2.5, ipadx=8, padx=2)
  216. button_arg = dict(width=EMAIL_INFO_BUTTON_WIDTH, height=EMAIL_INFO_BUTTON_HEIGHT, border=2)
  217. text = tk.Button(btn_frame, text="TEXT", command=lambda: self.show_email_text(info), **button_arg)
  218. html = tk.Button(btn_frame, text="HTML", command=lambda: self.show_email_html(info), **button_arg)
  219. file = tk.Button(btn_frame, text="FILE", command=lambda: self.show_email_file(info), **button_arg)
  220. for i in (text, html, file):
  221. i.pack(side="left", ipadx=0.5)
  222. self.show_email_text(info)
  223. def show_email_text(self, text: st.ScrolledText):
  224. if not self.email:
  225. msg.showwarning("Not email", "You should create email first.")
  226. return
  227. text.config(state=tk.NORMAL)
  228. text.delete("0.0", tk.END)
  229. if len(self.email.text) == 0:
  230. text.insert(tk.END, "Not any TEXT ...")
  231. else:
  232. for i in self.email.text:
  233. text.insert(tk.END, i + "\n")
  234. text.insert(tk.END, "-" * 5 + "\n")
  235. text.config(state=tk.DISABLED)
  236. def show_email_html(self, text: st.ScrolledText):
  237. if not self.email:
  238. msg.showwarning("Not email", "You should create email first.")
  239. return
  240. text.config(state=tk.NORMAL)
  241. text.delete("0.0", tk.END)
  242. if len(self.email.html) == 0:
  243. text.insert(tk.END, "Not any HTML ...")
  244. else:
  245. for i in self.email.html:
  246. text.insert(tk.END, i + "\n")
  247. text.insert(tk.END, "-" * 5 + "\n")
  248. text.config(state=tk.DISABLED)
  249. def show_email_file(self, text: st.ScrolledText):
  250. if not self.email:
  251. msg.showwarning("Not email", "You should create email first.")
  252. return
  253. text.config(state=tk.NORMAL)
  254. text.delete("0.0", tk.END)
  255. if len(self.email.file) == 0:
  256. text.insert(tk.END, "Not any FILE ...")
  257. else:
  258. for i in self.email.file:
  259. text.insert(tk.END, i[0] + "\n")
  260. text.config(state=tk.DISABLED)
  261. def show_add_rc(self):
  262. if not self.email:
  263. msg.showwarning("Not email", "You should create email first.")
  264. return
  265. win = tk.Toplevel(self)
  266. win.title("Add Recipient")
  267. win.resizable(False, False)
  268. label_arg = dict(width=ADD_RC_LABEL_WIDTH, height=ADD_RC_LABEL_HEIGHT)
  269. entry_arg = dict(width=ADD_RC_ENTRY_WIDTH)
  270. name_frame = tk.Frame(win)
  271. name_label = tk.Label(name_frame, text="Name: ", **label_arg)
  272. name = tk.Entry(name_frame, **entry_arg)
  273. name_label.pack(side="left")
  274. name.pack(side="left")
  275. name_frame.pack(side="top", pady=2, padx=5)
  276. address_frame = tk.Frame(win)
  277. address_label = tk.Label(address_frame, text="Address: ", **label_arg)
  278. address = tk.Entry(address_frame, **entry_arg)
  279. address_label.pack(side="left")
  280. address.pack(side="left")
  281. address_frame.pack(side="top", pady=2, padx=5)
  282. lb = tk.Listbox(win, width=ADD_RC_LISTBOX_WIDTH, height=ADD_RC_LISTBOX_HEIGHT, selectmode=tk.SINGLE)
  283. lb.pack(side="top", pady=2.5, padx=2)
  284. self.show_rc(lb)
  285. btn_frame = tk.Frame(win)
  286. btn_frame.pack(side="top", pady=2.5, padx=2)
  287. button_arg = dict(width=ADD_RC_BUTTON_WIDTH, height=ADD_RC_BUTTON_HEIGHT, border=2)
  288. rc = tk.Button(btn_frame, text="Add RC",
  289. command=lambda: self.rc_add_to(name.get(), address.get(), lb), **button_arg)
  290. cc = tk.Button(btn_frame, text="Add CC",
  291. command=lambda: self.rc_add_cc(name.get(), address.get(), lb), **button_arg)
  292. bcc = tk.Button(btn_frame, text="Add BCC",
  293. command=lambda: self.rc_add_bcc(name.get(), address.get(), lb), **button_arg)
  294. for i in (rc, cc, bcc):
  295. i.pack(side="left")
  296. def rc_add_to(self, name, address, lb):
  297. if self.email:
  298. if msg.askyesno("Add rc", f"Sure to add {name} <{address}> as rc?"):
  299. self.email.add_to_addr(name, address)
  300. self.show_rc(lb)
  301. else:
  302. msg.showwarning("Not email", "You should create email first.")
  303. def rc_add_cc(self, name, address, lb):
  304. if self.email:
  305. if msg.askyesno("Add cc", f"Sure to add {name} <{address}> as cc?"):
  306. self.email.add_cc_addr(name, address)
  307. self.show_rc(lb)
  308. else:
  309. msg.showwarning("Not email", "You should create email first.")
  310. def rc_add_bcc(self, name, address, lb):
  311. if self.email:
  312. if msg.askyesno("Add bcc", f"Sure to add {name} <{address}> as bcc?"):
  313. self.email.add_bcc_addr(name, address)
  314. self.show_rc(lb)
  315. else:
  316. msg.showwarning("Not email", "You should create email first.")
  317. def show_rc(self, lb: tk.Listbox):
  318. lb.delete("0", tk.END)
  319. if self.email:
  320. for i in self.email.to_addr:
  321. lb.insert(tk.END, f"TO: {i[0]} <{i[1]}>")
  322. for i in self.email.cc_addr:
  323. lb.insert(tk.END, f"CC: {i[0]} <{i[1]}>")
  324. for i in self.email.bcc_addr:
  325. lb.insert(tk.END, f"BCC: {i[0]} <{i[1]}>")
  326. else:
  327. msg.showwarning("Not email", "You should create email first.")
  328. def add_as_text(self):
  329. if not self.email:
  330. msg.showwarning("Not email", "You should create email first.")
  331. return
  332. if msg.askyesno("Sure?", "Sure to add text?"):
  333. self.email.add_text(self.text.get("0.0", tk.END))
  334. self.text.delete("0.0", tk.END)
  335. msg.showinfo("Add", "Success!")
  336. def add_as_html(self):
  337. if not self.email:
  338. msg.showwarning("Not email", "You should create email first.")
  339. return
  340. if msg.askyesno("Sure?", "Sure to add html?"):
  341. self.email.add_html(self.text.get("0.0", tk.END))
  342. self.text.delete("0.0", tk.END)
  343. msg.showinfo("Add", "Success!")
  344. def add_html(self):
  345. if not self.email:
  346. msg.showwarning("Not email", "You should create email first.")
  347. return
  348. file = fd.askopenfilename(title="Loading HTML", filetypes=[("HTML", ".html"),
  349. ("TXT", ".txt"),
  350. ("*", "*")])
  351. if not file:
  352. return
  353. self.email.add_html_from_file(file)
  354. msg.showinfo("Add", "Success!")
  355. def add_text(self):
  356. if not self.email:
  357. msg.showwarning("Not email", "You should create email first.")
  358. return
  359. file = fd.askopenfilename(title="Loading TEXT", filetypes=[("TXT", ".txt"),
  360. ("*", "*")])
  361. if not file:
  362. return
  363. self.email.add_text_from_file(file)
  364. msg.showinfo("Add", "Success!")
  365. def add_file(self):
  366. if not self.email:
  367. msg.showwarning("Not email", "You should create email first.")
  368. return
  369. file = fd.askopenfilename(title="Loading FILE", filetypes=[("*", "*")])
  370. if not file:
  371. return
  372. self.email.add_from_file(os.path.split(file)[1], file)
  373. msg.showinfo("Add", "Success!")
  374. def save_config(self):
  375. file = None
  376. conf = {}
  377. if self.sender:
  378. conf["sender"] = {}
  379. conf["sender"]["user"] = self.sender.user
  380. conf["sender"]["passwd"] = self.sender.passwd
  381. conf["sender"]["host"] = self.sender.host
  382. conf["sender"]["port"] = self.sender.port
  383. conf["sender"]["debug"] = self.sender.debug
  384. conf["sender"]["ssl"] = self.sender.ssl
  385. conf["sender"]["start_ssl"] = self.sender.start_ssl
  386. try:
  387. file = fd.asksaveasfile(mode="w", title="Saving config file", filetypes=[("JSON", ".json")],
  388. defaultextension=".json")
  389. if file is not None:
  390. file.write(json.dumps(conf))
  391. except IOError:
  392. msg.showerror("Load config", "Sorry, IO Error.")
  393. except Exception:
  394. msg.showerror("Load config", "Sorry, Unknown error.")
  395. finally:
  396. if file:
  397. file.close()
  398. msg.showinfo("Saving success", "Success!")
  399. def load_config(self):
  400. file = None
  401. try:
  402. file = fd.askopenfile(mode="r", title="Loading config file", filetypes=[("JSON", ".json")])
  403. if file is None:
  404. return
  405. conf = json.load(file)
  406. sender = conf.get("sender")
  407. if sender:
  408. self.sender = Sender(
  409. user=sender["user"],
  410. passwd=sender["passwd"],
  411. host=sender.get("host", "localhost"),
  412. port=sender.get("port", 465),
  413. debug=sender.get("debug", False),
  414. ssl=sender.get("ssl", True),
  415. start_ssl=sender.get("start_ssl", False)
  416. )
  417. except smtplib.SMTPAuthenticationError:
  418. msg.showerror("Load config", "Sorry, SMTP Authentication error. Please check your user and password.")
  419. except smtplib.SMTPException:
  420. msg.showerror("Load config", "Sorry, SMTP Error.")
  421. except KeyError:
  422. msg.showerror("Load config", "Sorry, Key Error.")
  423. except IOError:
  424. msg.showerror("Load config", "Sorry, IO Error.")
  425. except Exception:
  426. msg.showerror("Load config", "Sorry, Unknown error.")
  427. finally:
  428. if file:
  429. file.close()
  430. msg.showinfo("Loading success", "Success!")
  431. def send_mail(self):
  432. if not self.sender:
  433. msg.showwarning("Not login", "You should login first.")
  434. return
  435. if not self.email:
  436. msg.showwarning("Not email", "You should create email first.")
  437. return
  438. try:
  439. self.sender.send(self.email)
  440. msg.showinfo("Send email", "Success!")
  441. except smtplib.SMTPException:
  442. msg.showerror("Send fail", "SMTP error.")
  443. if __name__ == '__main__':
  444. window = SenderGUI()
  445. window.mainloop()