import email from email.header import decode_header, make_header import imaplib import imapclient from bureau import Bureau, add_command, add_api class Message(object): """ This is just a convenience class for holding email message data. It could be fancier some day but for now this seems fine. """ pass class MailRoom(Bureau): """ The Mail Room handles (electronic) post for the other Bureaus of the Screenless Office. """ name = "Mail Room" prefix = "PO" version = 0 def __init__(self): Bureau.__init__(self) # TODO: multiple accounts / folders if "user" in self.config: self.login = self.config["user"]["login"] self.password = self.config["user"]["password"] self.host = self.config["user"]["host"] self.spamfolder = self.config["user"]["spamfolder"] self.trashfolder = self.config["user"]["trashfolder"] self.imap_ssl = self.config["user"]["ssl"] if self.imap_ssl.lower() == "true": self.imap_ssl = True else: self.imap_ssl = False self.imapserv = imapclient.IMAPClient(self.host, use_uid=True, ssl=self.imap_ssl) self.imapserv.login(self.login, self.password) self.imapserv.select_folder("INBOX") else: print("you need to configure an IMAP account!") print("add a [user] section to PO.ini with:") print(" login = mylogin") print(" password = mypassword") print(" host = my.imap.server.address.com") # setup db's for mapping short codes to IMAP msg ids (and reversed) self.postdb = self.dbenv.open_db(b"postdb") self.postdb_rev = self.dbenv.open_db(b"postdb_rev") def _connect_imap(self): try: self.imapserv.select_folder("INBOX") except imaplib.error: self.imapserv.login(self.login, self.password) def get_imap_id(self, msgid): """ take short code and look up the IMAP message id """ with self.dbenv.begin(db=self.postdb) as txn: imap_id = txn.get(msgid.encode()) return int(imap_id) @add_command("fax", "Send a Document Camera Image via Email") def fax(self, data): """ Takes a photograph using the document camera and sends it in an E-mail. """ photo = self.send("PXphoto.") @add_command("r", "Print full email") def read(self, data): """ Prints out the full detailed version of an email. """ shortcode, _ = data.split(".") imap_id = self.get_imap_id(shortcode) self._connect_imap() resp = self.imapserv.fetch([imap_id], ['INTERNALDATE', 'RFC822']) internaldate = resp[imap_id][b'INTERNALDATE'] msg_data = resp[imap_id][b'RFC822'].decode('utf-8') msg_obj = email.message_from_string(msg_data) print("message fields:", msg_obj.keys()) # format and tidy header data msg = Message() msg.fromstr = make_header(decode_header(msg_obj['From'])) msg.tostr = make_header(decode_header(msg_obj['To'])) msg.subject = make_header(decode_header(msg_obj['Subject'])) if 'Cc' in msg_obj: msg.cc = make_header(decode_header(msg_obj['Cc'])) else: msg.cc = None msg.date = internaldate msg.shortcode = shortcode msg.attachments = [] # TODO: should use msg_obj.get_body and deal with HTML msg.content = "" # extract other sub-messages / attachments for part in msg_obj.walk(): # TODO: save interesting attachments to files # should clean these up on delete if part.get_content_type() == "text/plain": msg.content = part.get_payload(decode=True).decode("utf-8") self.print_full("email.html", msg=msg, shortcode=shortcode) @add_command("d", "Delete email") def delete(self, data): """ Deletes an email and moves it to the trash folder. """ shortcode, _ = data.split(".") imap_id = self.get_imap_id(shortcode) self._connect_imap() self.imapserv.add_flags(imap_id, [imapclient.SEEN]) self.imapserv.copy((imap_id), self.trashfolder) self.imapserv.delete_messages((imap_id)) self.imapserv.expunge() @add_command("sp", "Mark as spam") def mark_spam(self, data): """ Flags an email as spam, mark as read and move it to the configured SPAM folder. """ shortcode, _ = data.split(".") imap_id = self.get_imap_id(shortcode) self._connect_imap() self.imapserv.add_flags(imap_id, [imapclient.SEEN]) self.imapserv.copy((imap_id), self.spamfolder) self.imapserv.delete_messages((imap_id)) self.imapserv.expunge() @add_command("re", "Reply with scan") def reply_scan(self, data): """ Reply to the sender of a mail with the PDF currently queued in the document scanner. """ # look up short code to get IMAP ID # extract the sender and title # put together the reply pass @add_api("unread", "Get unread mails") def unread(self): """ Polls the currently configured IMAP server and returns a dict containing unread emails. """ messages = self.imapserv.sort("ARRIVAL", ["UNSEEN"]) print("%d unread messages in INBOX" % len(messages)) return self.imapserv.fetch(messages, ['FLAGS', 'INTERNALDATE', 'ENVELOPE', 'RFC822.SIZE']) def main(): mr = MailRoom() mr.run() if __name__ == "__main__": main()