#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 datetime import time #Import getpass for password input import getpass #Import subprocess for validation of if a user is in a group, and running #a sendmail process 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, SuccessForm, catch_error_with, error_form from library.emails import format_reminder_email # email testing folder creation import os #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 (default " + str(DEFAULT_DAY_VALUE) + ")"] def _return_values(self): ret = self.entries[0].value if ret is "": return DEFAULT_DAY_VALUE else: #If we didn't get valid input, noisily fail assert ret.isdigit() and int(ret) > 0, \ "Max signed out days is not positive: " + ret.__repr__() return int(ret) 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: assert self.entries[0].value.isprintable() return self.entries[0].value #Private functions def _send_email(quest_id: str, signed_out_date: str, max_days_can_be_signed_out: int, librarian_name: str, book_name: str, testing=False) -> bool: """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. Set testing to true to output emails to the current directory, instead of actually sending them. Returns whether an email was sent. """ # Determine the days the book has been signed out d = datetime.strptime(signed_out_date, "%Y-%m-%d") days_signed_out = (d.today() - d).days if days_signed_out > max_days_can_be_signed_out: #Librarian also gets a copy, so that unreplied emails are more apparent. email_addresses = [quest_id + "@csclub.uwaterloo.ca", "librarian@csclub.uwaterloo.ca"] email_message = format_reminder_email(quest_id, days_signed_out, librarian_name, book_name) if testing: assert book_name.isprintable() os.makedirs("test_emails", exist_ok=True) #Know that book titles such as "FORTRAN/77" exist, sanitize slashes. filename = ("test_emails/{}_{}.txt" .format(quest_id, book_name.replace("/", "-"))) with open(filename, "w") as f: print(email_message, file=f) else: #Equivalent to: #echo | sendmail questid@csclub.uwaterloo.ca sendmail_process = subprocess.Popen(["sendmail"] + email_addresses, stdin=subprocess.PIPE) sendmail_process.communicate(input=email_message.encode("utf-8")) return True return False #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() if days == {}: return "" #User cancelled the dialog box #Get the name of the librarian 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() if librarianName == {}: return "" #User cancelled the dialog box #Don't send out emails if this gets set to True testing = False if librarianName == "TESTING12345": testing = True #Get the books that are signed out signed_out_books = db.get_checkedout_books() earliest_checkout = datetime.now() emails_sent = 0 for data in signed_out_books: quest_id = data["uwid"] date = str(data["date"]).split(" ")[0] book_name_with_spaces = data["title"] if _send_email( str(quest_id), str(date), days, str(librarianName), book_name_with_spaces, testing): emails_sent += 1 earliest_checkout = min(earliest_checkout, datetime.strptime(date, "%Y-%m-%d")) #A success message about what emails we just sent assert earliest_checkout > datetime(2010,1,1) time_since_earliest_checkout = "" days_since_earliest_checkout = (datetime.now().toordinal() - earliest_checkout.toordinal()) assert days >= 0 month_length = 30 #It's wrong because it's an approximation if days_since_earliest_checkout < month_length: time_since_earliest_checkout = "{} days ago".format(days_since_earliest_checkout) else: time_since_earliest_checkout = "{:0.1f} months ago".format(days_since_earliest_checkout / month_length) if emails_sent == 0: success_text = """ No books are overdue, so all zero reminder emails were vacuously sent successfully. Either members are (hopefully) good at returning books or members don't take books out.""" else: success_text = """ Automatically sent out {} individual reminder emails, one per overdue book. The most overdue book was checked out approximately {}.""".format( emails_sent, time_since_earliest_checkout ) step4 = SuccessForm(w, hb, success_text, width=mx - 20) (r, c) = w.getmaxyx() w.mvwin(cy - r // 2, cx - c // 2) step4.event_loop() step4.clear() return ""