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)
Esempio n. 2
0
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
Esempio n. 4
0
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)
Esempio n. 5
0
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)
Esempio n. 6
0
    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()]
Esempio n. 7
0
    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
Esempio n. 9
0
 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])