From 6ed3c35554f93385d222cf31b12c74ce160c7aa0 Mon Sep 17 00:00:00 2001 From: Connor Murphy Date: Fri, 3 Feb 2017 23:55:51 -0500 Subject: [PATCH] Added ability for libcom to send emails to those with overdue books --- librarian | 7 ++ library/interface/form.py | 17 ++++ library/interface/sendemails.py | 148 ++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 library/interface/sendemails.py diff --git a/librarian b/librarian index a0abf61..7b3626d 100755 --- a/librarian +++ b/librarian @@ -6,6 +6,7 @@ import library.interface.browser as browser import library.interface.form as form import library.interface.help_bar as helpBar import library.interface.checkout as co +import library.interface.sendemails as sendemails from library import book_data @@ -186,6 +187,11 @@ def catMenu(): cat.eventLoop() cat.clear() +def email_menu(): + w=curses.newwin(1,1) + (my,mx)=stdscr.getmaxyx() + sendemails.sendemails_procedure(w,hb,my//2,mx//2,mx) + if __name__ == "__main__": db.initializeDatabase() @@ -197,6 +203,7 @@ if __name__ == "__main__": ("",exit), ("Check Out a Book", co_menu), ("Return a Book", return_menu), + ("Send Overdue Email Reminders", email_menu), ("",exit), ("View Checked Out Books", checkedout_menu), ("View On Shelf Books", onshelf_menu), diff --git a/library/interface/form.py b/library/interface/form.py index 652f377..7dc9d37 100644 --- a/library/interface/form.py +++ b/library/interface/form.py @@ -107,6 +107,13 @@ class TextEntry: self.value = self.value[:c] + self.value[c+1:] self._mv_cursor(0) +class PasswordEntry(TextEntry): + def redraw(self): + self.w.addnstr(self.y,self.x, " "*self.width, self.width) + if self.focus: + self.w.chgat(self.y, self.x, self.width, curses.A_UNDERLINE) + curses.curs_set(1) + class FormWindow: @@ -276,8 +283,18 @@ class FormWindow: if self.bt==-1: self.entries[self.hl].handle_input(ch) +class LoginForm(FormWindow): + caption = "Enter your csclub login to access the mail server" + blabel = "Login" + labels = ["ID", "Password"] + def _make_entries(self): + self.entries = [] + self.entries.append(TextEntry(self.w)) + self.entries.append(PasswordEntry(self.w)) + def _return_values(self): + return [self.entries[0].value, self.entries[1].value] class BookForm(FormWindow): caption = "Add a Book" diff --git a/library/interface/sendemails.py b/library/interface/sendemails.py new file mode 100644 index 0000000..2356f16 --- /dev/null +++ b/library/interface/sendemails.py @@ -0,0 +1,148 @@ +#Import smtp for the email sending function +import smtplib + +#Import argparse to parse command line args +import argparse + +#Import sys so that we can exit when given bad command line args +import sys + +#Import datetime and time to check dates +from datetime import date +import time + +#Import getpass for password input +import getpass + +#Import subprocess for validation of if a user is in a group +import subprocess + +#Import librarian permissions +from library import permissions +from library.exceptions import * +import library.database as db +from library.interface.form import FormWindow,BookForm, LoginForm, catch_error_with, error_form + +#Constants +DEFAULT_DAY_VALUE = 21 +MAX_LOGIN_ATTEMPTS = 3 + +class DaysForm(FormWindow): + caption = "Enter the max number of days a book can be signed out for" + blabel = "Enter" + labels = ["Days"] + + def _return_values(self): + return self.entries[0].value + + +class NameForm(FormWindow): + caption = "Enter the name you want in the signature line for the email" + blabel = "Enter" + labels = ["Name"] + + def _return_values(self): + if self.entries[0].value is "": + return "Librarian" + else: + return self.entries[0].value + +#Private functions +def _send_email(quest_id: str, signed_out_date: str, max_days_can_be_signed_out:int, librarianName: str, book_name_list) -> None: + """Sends an email to quest_id@csclub.uwaterloo.ca if the date the book + was signed out exceeds the days it is supposed to be signed out for""" + + email_address = quest_id + "@csclub.uwaterloo.ca" + book_name = "".join(book_name_list) + + #Determine the days the book has been signed out + date_tokens = signed_out_date.split("-") + date_signed_out = date(int(date_tokens[0]), int(date_tokens[1]), int(date_tokens[2])) + days_signed_out = date.fromtimestamp(time.time()) - date_signed_out + + if days_signed_out.days > max_days_can_be_signed_out: + email_message_body = ("Hi " + quest_id + ",\n\n" + "Our records indicate that you " + "have had the book " + book_name + " signed out for " + str(str(days_signed_out).split(" ")[:1])[2:-2] + + " days.\n\n" + "Please return the book to the CS Club office " + "(MC 3036) at your earliest convenience.\n\n" + "Thanks,\n\n" + + librarianName + "\n" + "Computer Science Club | University of Waterloo\n" + "librarian@csclub.uwaterloo.ca") + + email_message_subject = "Overdue book: {}".format(book_name) + email_message = "Subject: {}\n\n{}".format(email_message_subject, email_message_body) + + server.sendmail("librarian@csclub.uwaterloo.ca", email_address, email_message) + +#Public functions +@permissions.check_permissions(permissions.PERMISSION_LIBCOM) +@catch_error_with(lambda w, hb, *args : (w, hb, None)) +def sendemails_procedure(w, hb, cy, cx, mx): + """Procedure to send emails to those with overdue books + + w: ncurses window for the routine + cy,cx: centre coordinates of the screen + mx: max width of screen + """ + + #Get the max days a book can be signed out for + step1 = DaysForm(w,hb,width=mx-20) + (r,c)=w.getmaxyx() + w.mvwin(cy-r//2,cx-c//2) + days = step1.event_loop() + step1.clear() + + #Set the days to a default value if the value given is less than 0 + if not isinstance(days, int) or days < 0: + days = DEFAULT_DAY_VALUE + + #Get the name of the librarain + step2 = NameForm(w,hb,width=mx-20) + (r,c)=w.getmaxyx() + w.mvwin(cy-r//2,cx-c//2) + librarianName = step2.event_loop() + step2.clear() + + #Set up email + global server + server = smtplib.SMTP_SSL("mail.csclub.uwaterloo.ca", 465) + + #Attempt to login 3 times + loginAttempts = 0 + while loginAttempts < MAX_LOGIN_ATTEMPTS: + + #Get the user's login info to login to the mail server + step3 = LoginForm(w,hb,width=mx-20) + (r,c)=w.getmaxyx() + w.mvwin(cy-r//2,cx-c//2) + loginInfo = step3.event_loop() + step3.clear() + + csclubID = loginInfo[0] + csclubPwd = loginInfo[1] + + try: + server.login(csclubID, csclubPwd) + break + except smtplib.SMTPAuthenticationError: + loginAttempts += 1 + + #Check to see if login failed + if loginAttempts >= MAX_LOGIN_ATTEMPTS: + return "" + + #Get the books that are signed out + signed_out_books = db.get_checkedout_books() + + for data in signed_out_books: + quest_id = data["uwid"] + date = str(data["date"]).split(" ")[0] + book_name_with_spaces = data["title"] + + _send_email(str(quest_id), str(date), days, str(librarianName), book_name_with_spaces) + + #Exit from the email server + server.quit() + + return "" + +