def _check_sent(self, verbose: bool, imap: IMAP4_SSL, timeout: int, uid_to_from_address: Dict[uuid.UUID, str]) -> None: handle_response("select INBOX", imap.select()) end = time.time() + timeout while True: if time.time() > end: # We sort the addresses to get a stable output. unresponsive = ", ".join( uid for uid in sorted(uid_to_from_address.values())) raise CommandError("timeout; did not get answers for " f"{unresponsive}") remaining_uids = {} # Sending a noop prompts some IMAP servers to *really* check # whether there have been new emails delivered. Without it, it # takes longer for us to find the emails we are looking for. handle_response("noop", imap.noop()) # It may be possible to reduce the number of queries to the IMAP # server by ORing the SUBJECT queries. However, it complicates the # code quite a bit. for uid, from_address in uid_to_from_address.items(): if verbose: self.stdout.write(f"Searching for: {uid} from " f"{from_address}...") mess_uid = \ handle_response("search", imap.uid( "search", f'SUBJECT "{uid}" ' # type: ignore f'FROM "{from_address}"')) if mess_uid[0] == b"": remaining_uids[uid] = from_address else: if verbose: self.stdout.write(f"Found {uid}!") answer = handle_response( "store", imap.uid( "store", mess_uid[0], "+FLAGS", # type: ignore r"(\Deleted \Seen)" # type: ignore )) if verbose: self.stdout.write(repr(answer)) if len(remaining_uids) == 0: break uid_to_from_address = remaining_uids # Give a bit of respite to the IMAP server. time.sleep(1)
def load(con: imaplib.IMAP4_SSL) -> Optional[dict]: try: con.select('INBOX') status, data = con.uid('SEARCH', None, 'ALL') if status == 'OK': uids = data[0].split() msgs = dict() for uid in uids: status, data = con.uid('FETCH', uid, '(RFC822)') if status == 'OK': msg = email.message_from_bytes(data[0][1]) msgs[uid] = msg return msgs except Exception as error: logging.error(error)
def get_unread_email_uids(imap_connection: imaplib.IMAP4_SSL, sender: str = '', subject: str = '') -> List[str]: """Retrieve unique email ids for each unread message matching given criteria. Parameters ---------- imap_connection : imaplib.IMAP4_SSL object SSL connection to an IMAP enabled server sender : string, default '' Desired sender email address to filter messages by subject : string, default '' Desired subject to filter messages by Returns ------- list of strings Unique email IDs of all unread emails matching `sender` and `subject` present at the IMAP server. IDs are ordered from newest to oldest Notes ----- * Currently only tested with gmail """ sender_ = 'FROM ' + sender if sender else None subject_ = 'SUBJECT ' + subject if subject else None email_uid = imap_connection.uid('SEARCH', sender_, subject_, 'UNSEEN')[1][0].split() return email_uid
def del_msg(uid: str, con: imaplib.IMAP4_SSL) -> Optional[bool]: try: status, data = con.uid('STORE', uid, '+FLAGS', '(\Deleted)') if status == 'OK': status, data = con.expunge() return status == "OK" except Exception as error: logging.error(error)
def copy(uid: str, con: imaplib.IMAP4_SSL) -> Optional[bool]: try: con.create('ARCHIVE') con.select('INBOX') status, data = con.uid('COPY', uid, 'ARCHIVE') return status == 'OK' except Exception as error: logging.error(error)
def _find_attachment(self, attachment_name: str, mailbox: IMAP4_SSL, search_group: list): """ I know, i'm sorry. At least it's fast """ extractor = lambda uid: mailbox.uid( 'FETCH', str(uid), '(BODYSTRUCTURE)' )[1][0].decode().split('"NAME" "')[1].split('"')[0] return [uid for uid in search_group if extractor(uid).lower() == attachment_name.lower()]
def _search_mailbox(self, mailbox: IMAP4_SSL, subject: str = None) -> list: date = self.date.strftime('%d-%b-%Y') sender = self.MAIL_CONFIG['sender'] if not subject: subject = self.MAIL_CONFIG['subject'] self.update('mailbox', 'searching') response = mailbox.uid('SEARCH', None, f'(SENTON {date} HEADER FROM "{sender}" SUBJECT "{subject}")') return [int(uid) for uid in response[1][0].decode().split(' ')]
def download_all_email_attachments(imap_connection: imaplib.IMAP4_SSL, email_uid: str, output_dir: str) -> List[str]: """Download all file attachments present at unique email id. Parameters ---------- imap_connection : imaplib.IMAP4_SSL object SSL connection to an IMAP enabled email server email_uid : string Unique email ID to download all attachments from output_dir : string Directory to download all files to. Attachment names determine filenames to use Returns ------- list of string File paths of downloaded attachments. All file paths are returned, regardless of whether the downloads were successful or not Notes ----- * Currently only tested with gmail * 'RFC822' is the protocol used, so all files are marked as read See Also -------- * `connect_to_imap_server` * `get_unread_email_uids` * `imaplib.IMAP4_SSL` """ # todo: add temporary file option (or not an option and return temp dir containing files) file_paths = [] email_body = imap_connection.uid('FETCH', email_uid, '(RFC822)')[1][0][1] # read the message message = email.message_from_bytes(email_body) for part in message.walk(): if part.get_content_maintype() != 'MULTIPART' and part.get( 'CONTENT-DISPOSITION'): file_path = os_lib.join_path(output_dir, part.get_filename()) file_paths.append(file_path) with open(file_path, 'wb') as output_file: output_file.write(part.get_payload(decode=True)) return file_paths
def _get_message(self, mailbox: IMAP4_SSL, uid: int) -> email.message.Message: self.update('mailbox', 'retrieving mail') return email.message_from_bytes(mailbox.uid('FETCH', str(uid), '(RFC822)')[1][0][1])