#!/usr/bin/env python3 import sys import imaplib import os import email import email.header import smtplib from email.message import EmailMessage import re import datetime as dt import time EMAIL_ACCOUNT = "bzigr02" EMAIL_FOLDER = "INBOX" EMAIL_ADDRESS = 'etho2020registration@biologie.uni-tuebingen.de' EMAIL_REPLY = 'etho2020@biologie.uni-tuebingen.de' SMTP_SERVER = 'smtpserv.uni-tuebingen.de' IMAP_SERVER = 'mailserv.uni-tuebingen.de' EMAIL_PSWD = "r.8-*0Fc" PARTICIPANTS_FOLDER = 'participants' reg_fees = {'early': {"proMember": 95, "proNonMember":115, "studentMember": 55, "studentNonMember": 65}, 'late': {"proMember": 115, "proNonMember":135, "studentMember": 65, "studentNonMember": 75}} farewell_fee = {"senior": 45, "student": 25} early_bird = d = dt.datetime.strptime("2020-01-16T00:00:00", "%Y-%m-%dT%H:%M:%S") class Participation(object): field_mapping = [("First name", "_first_name"), ("Last name", "_last_name"), ("Institution", "_institution", True), ("Street", "_address_street"), ("City", "_address_city"), ("ZIP code", "_address_zip"), ("Phone", "_phone"), ("e-Mail", "_email"), ("Country", "_address_country"), ("Fee", "_role"), ("Messages", "_message"), ("Food preferences", "_food_preference"), ("Farewell event", "_farewell"), ("Gwinner", "_gwinner"), ("Lab Tour (Fri 21 Feb)", "_lab_tour", True)] def __init__(self, email_message): self._registration_date = "" self._first_name = "" self._last_name = "" self._phone = "" self._email = "" self._farewell = "" self._gwinner = "" self._lab_tour = "" self._food_preference = "" self._food_vegi = False self._food_vegan = False self._food_gluten = False self._food_normal = True self._address_street = "" self._address_city = "" self._address_zip = "" self._address_country = "" self._institution = "" self._role = "" self._message = "" self._fee = 0.0 self._conflicts = [] self.__parse_message(email_message) def __parse_message(self, message): date_tuple = email.utils.parsedate_tz(message['Date']) if date_tuple: local_date = dt.datetime.fromtimestamp( email.utils.mktime_tz(date_tuple)) self._registration_date = local_date msg_body = message.get_payload()[0].as_string() msg_body = re.sub(r'=20', ' ', msg_body) msg_body = re.sub(r'=\n', '', msg_body) msg_body = re.sub(r'=E2=82=AC', 'EUR', msg_body) msg_body = re.sub(r'&', 'and', msg_body) lines = msg_body.split('\n') for fm in Participation.field_mapping: self.__parse_lines(lines, fm) self.__process_food(lines) self.__check() def __check(self): if "student" not in self._role and self.gwinner_award: self._conflicts.append("Gwinner award selected but not student!") if (self._food_vegan or self._food_gluten or self._food_vegi) and self._food_normal: self._conflicts.append("Conflict in food preferences!") def __calc_fee(self): fee = .0 early_or_late = "early" if self.registration_date < early_bird else "late" pro_or_stud = "senior" if "pro" in self._role else "student" fee += reg_fees[early_or_late][self._role] fee += farewell_fee[pro_or_stud] if self.farewell_event else 0.0 return fee def __parse_lines(self, lines, field_map): assert(len(field_map) >= 2) ls = [l for l in lines if l.lower().startswith(field_map[0].lower())] if len(ls) < 1: return if len(field_map) == 2: setattr(self, field_map[1], ls[0].split(field_map[0])[-1].strip()) else: setattr(self, field_map[1], ', '.join(map(lambda x:x.split(field_map[0])[-1].strip(), ls))) def __process_food(self, lines): if len(self._food_preference) > 0: prefs = self._food_preference.lower() self._food_normal = "no special" in prefs self._food_vegan = "vegan" in prefs self._food_vegi = "vegetarian" in prefs self._food_gluten = "gluten" in prefs @property def registration_date(self): return self._registration_date @property def fee(self): return self.__calc_fee() @property def farewell_event(self): return len(self._farewell) > 0 @property def gwinner_award(self): return len(self._gwinner) > 0 @property def labtour(self): return self._lab_tour.split(",") @property def valid(self): return len(self._conflicts) == 0 @property def name(self): return "%s %s" % (self._first_name, self._last_name) def save_to_csv(self, filename=None): if not os.path.exists(PARTICIPANTS_FOLDER): os.mkdir(PARTICIPANTS_FOLDER) if not filename: filename = os.path.join(PARTICIPANTS_FOLDER, "".join(self.name.split())) + ".csv" header = ["LastName", "FirstName", "eMail", "Phone", "Institute", "Street", "City", "RegDate", "CareerStage", "Fee", "FarewellEvent", "GwinnerAward", "LabTour", "FoodVegetarian", "FoodVegan", "FoodGlutenFree", "FoodNoPref"] values = [self._first_name, self._last_name, self._email, self._phone, self._institution, self._address_street, "%s %s" % (self._address_zip, self._address_city), self.registration_date.isoformat(), self._role, str(self.fee), str(self.farewell_event), str(self.gwinner_award), str(self.labtour), str(self._food_vegi), str(self._food_vegan), str(self._food_gluten), str(self._food_normal)] with open(filename, "w") as f: f.write("; ".join(header)) f.write("\n") f.write("; ".join(values)) return filename def __str__(self): str = "" str += "Name: %s %s\n" % (self._first_name, self._last_name) str += "Institution: %s\n" % self._institution str += "Phone: %s\n" % self._phone str += "e-Mail: %s\n" % self._email str += "Career stage: %s %s\n" % ("senior" if "pro" in self._role else "student", "non-member" if "NonMember" in self._role else "member of Ethological Soc.") str += "Farewell event: %s\n" % "Yes" if self.farewell_event else "No" str += "Fee: %3.2f EUR\n" % self.fee str += "Lab tour: %s\n" % ( "No" if len(self.labtour) == 0 else "Yes, " + ", ".join(self.labtour)) str += "Gwinner award: %s\n" % "Yes" if self.gwinner_award else "No" str += "Food preferences: %s, %s, %s, %s\n" % ("no prefs" if self._food_normal else "", "vegetarian" if self._food_vegi else "", "vegan" if self._food_vegan else "", "gluten free" if self._food_gluten else "") str += "Registration date: %s\n" % self.registration_date.isoformat() str += "Message:%s\n" % self._message if not self.valid: str += "Inconsistencies: %s\n" % ", ".join(self._conflicts) return str def process_mailbox(M): """ Do something with emails messages in the folder. For the sake of this example, print some headers. """ rv, data = M.search(None, "UNSEEN") if rv != 'OK': print("No messages found!") return for num in data[0].split(): rv, data = M.fetch(num, '(RFC822)') if rv != 'OK': print("ERROR getting message", num) return msg = email.message_from_bytes(data[0][1]) hdr = email.header.make_header(email.header.decode_header(msg['Subject'])) subject = str(hdr) print('Message %s: %s' % (num, subject)) print('Raw Date:', msg['Date']) # Now convert to local date-time date_tuple = email.utils.parsedate_tz(msg['Date']) if date_tuple: local_date = dt.datetime.fromtimestamp( email.utils.mktime_tz(date_tuple)) print ("Local Date:", \ local_date.strftime("%a, %d %b %Y %H:%M:%S")) def process_message(M, msg_index): rv, data = M.fetch(msg_index.encode('ascii'), '(RFC822)') if rv != 'OK': print("ERROR getting message", rv) return msg = email.message_from_bytes(data[0][1]) return Participation(msg) def process_registrations(M): rv, msgs = M.search(None, "SUBJECT", "etho2020", "UNSEEN") participations = [] if rv != "OK": print("ERROR searching messages", rv) return participations msgs = msgs[0].decode('ascii').split() print("Found %i registration mails" % len(msgs)) for msg_index in msgs: participations.append(process_message(M, msg_index)) return participations def send_confirmation(p=None, csv_file=None): if p is not None: assert(isinstance(p, Participation)) content = "Dear %s," % (p.name if p else "participant") with open("response.txt") as fp: content += fp.read() content += "\n" content += str(p) if p else "" msg = EmailMessage() msg.set_content(content) msg['Subject'] = 'Etho 2020 registration confirmation' msg['From'] = EMAIL_ADDRESS msg['To'] = p._email msg['Reply-to'] = EMAIL_REPLY s = smtplib.SMTP(SMTP_SERVER) s.starttls() s.login(EMAIL_ACCOUNT, EMAIL_PSWD) s.send_message(msg) if csv_file and p: csv_msg = EmailMessage() csv_msg['Subject'] = 'New Registration ' + p.name if p else "" csv_msg['From'] = EMAIL_ADDRESS csv_msg['To'] = p._email # FIXME this is wrong!!! needs to be EMAIL_REPLy with open(csv_file, 'rb') as fp: file_data = fp.read() csv_msg.add_attachment(file_data, maintype='text', subtype="comma-separated-values", filename=os.path.basename(csv_file)) s.send_message(csv_msg) s.quit() def check_for_mails(): M = imaplib.IMAP4_SSL(IMAP_SERVER) try: rv, data = M.login(EMAIL_ACCOUNT, EMAIL_PSWD) except imaplib.IMAP4.error: print("LOGIN FAILED!!! ") sys.exit(1) # rv, mailboxes = M.list() # if rv == 'OK': # print("Mailboxes:") # print(mailboxes) rv, data = M.select(EMAIL_FOLDER) if rv != 'OK': print("ERROR: Unable to open mailbox folder ", rv) new_participations = process_registrations(M) for p in new_participations: print("New registration by %s" % p.name, file=sys.stderr) csv_file = p.save_to_csv() send_confirmation(p, csv_file) M.close() M.logout() if __name__ == "__main__": while True: print("%s: Checking for new registrations mails!" % dt.datetime.now().isoformat(), file=sys.stderr) check_for_mails() time.sleep(60)