import imaplib import mailbox import pathlib import pprint import re from typing import List, Union, Tuple from . import import_or_install, imap_response mailbox.Maildir.colon = '!' FLAGS = re.compile(rb'FLAGS \(([^)]+?)\)') def percent_encode(c: str) -> str: return '%' + hex(ord(c))[2:] def clean_folder_name(folder_name: str, separator: str) -> str: folder_name = folder_name.replace('%', percent_encode('%')) folder_name = folder_name.replace('.', percent_encode('.')) folder_name = folder_name.replace(separator, '.') return folder_name def dirty_folder_name(folder_name: str, separator: str = '/') -> str: folder_name = folder_name.replace('.', separator) folder_name = folder_name.replace(percent_encode('.'), '.') folder_name = folder_name.replace(percent_encode('%'), '%') return folder_name class Account: def __init__(self, address: str, info: dict): appdirs = import_or_install('appdirs') data_dir = pathlib.Path(appdirs.user_data_dir(appname='ctec', appauthor=False)) self.address = address self.mailbox = mailbox.Maildir(data_dir / address) self.info = info self.connection = imaplib.IMAP4_SSL(self.info['imap host']) self.connection.login(self.address, self.info['password']) def __del__(self): self.connection.logout() def fetch_folders(self): folder_list: List[bytes] typ, folder_list = self.connection.list() for folder in folder_list: folder_info = imap_response.List.parse(folder) if folder_info.delimiter != '/': raise NotImplementedError(f'who the hell uses {repr(folder_info.delimiter)} as a delimiter') self.mailbox.add_folder(clean_folder_name(folder_info.name, folder_info.delimiter)) def fetch_inbox(self): with imaplib.IMAP4_SSL(self.info['imap host']) as M: M.login(self.address, self.info['password']) inbox = self.mailbox.add_folder('Inbox') M.select() typ, data = M.search(None, 'ALL') for num in data[0].split(): typ, data = M.fetch(num, '(FLAGS RFC822)') for prefix, message in data[:-1]: flags = FLAGS.search(prefix).group(1).split() message = mailbox.MaildirMessage(message) print(message['Subject'], flags) if rb'\Seen' in flags: message.add_flag('S') message.set_subdir('cur') if 'Message-ID' in message: message_id = message['Message-ID'].strip() else: print(message) raise KeyError('No message ID header') if not any(x['Message-ID'].strip() == message_id for x in inbox): inbox.add(message) M.close() def folders(self) -> List[Tuple[str, mailbox.Maildir]]: return [(folder, self.mailbox.get_folder(folder)) for folder in self.mailbox.list_folders()] def inbox(self) -> mailbox.Maildir: return self.mailbox.add_folder('Inbox')