예제 #1
0
    def _send_request(
        self,
        mail: DecodedMail,
        imapc: imapclient.IMAPClient,
        method: str,
        endpoint: str,
        json_body_dict: dict,
    ):
        logger.debug(
            self,
            "Contact API on {endpoint} with method {method} with body {body}".format(
                endpoint=endpoint, method=method, body=str(json_body_dict)
            ),
        )
        if method == "POST":
            request_method = requests.post
        else:
            # TODO - G.M - 2018-08-24 - Better handling exception
            raise UnsupportedRequestMethod("Request method not supported")

        r = request_method(
            url=endpoint,
            json=json_body_dict,
            headers=self._get_auth_headers(mail.get_from_address()),
        )
        if r.status_code not in [200, 204]:
            details = r.json().get("message")
            msg = "bad status code {} (200 and 204 are valid) response when sending mail to tracim: {}"
            msg = msg.format(str(r.status_code), details)
            raise BadStatusCode(msg)
        # Flag all correctly checked mail
        if r.status_code in [200, 204]:
            imapc.add_flags((mail.uid,), IMAP_CHECKED_FLAG)
            imapc.add_flags((mail.uid,), IMAP_SEEN_FLAG)
예제 #2
0
def do_worker_qq(smtp_account, smtp_password, smtp_list):
    # 通过一下方式连接smtp服务器,没有考虑异常情况,详细请参考官方文档
    server = IMAPClient(GLB_IMAP_HOSTNAME, ssl= True)
    try:
        server.login(smtp_account, smtp_password)
        server.select_folder('INBOX')

        year, mouth, day = glb_p_date.split('-')
        date = datetime.date(int(year), int(mouth), int(day))
        date2 = date - datetime.timedelta(days=1)
        result = server.search(['UNSEEN', 'SINCE', date2])
        msgdict = server.fetch(result, ['BODY.PEEK[]'] )
        msgtuple=sorted(msgdict.items(), key=lambda e:e[0], reverse=True)

        log_ids = []
        for message_id, message in msgtuple:
            msg_content = message['BODY[]']
            for log_id, mail_from, mail_to in smtp_list:
                if log_id in log_ids: continue
                error_type, return_said = do_email(msg_content, mail_to)
                if error_type is not None:
                    log_ids.append(log_id)
                    do_worker_imap_update(log_id, mail_to, return_said, error_type)
                    server.add_flags(message_id, '\\seen')
    finally:
        server.logout()
    return
예제 #3
0
def flagged_random_mail(logged_in_client: IMAPClient, random_mail):
    logged_in_client.select_folder(EmailFolders.INBOX)
    logged_in_client.add_flags(random_mail, FLAG_TO_CHECK)
    assert FLAG_TO_CHECK in logged_in_client.get_flags(random_mail).get(
        random_mail)

    # no need to cleanup, as the message will get deleted anyway
    return random_mail
예제 #4
0
파일: models.py 프로젝트: msolomon/gus
 def update_email(self):
     '''
     Fetch email messages from the IMAP server and store them
     @return: None
     '''
     server = IMAPClient(settings.IMAP_HOST, use_uid=False, ssl=True)
     server.login(settings.IMAP_HOST_USER, settings.IMAP_HOST_PASSWORD)
     
     select_info = server.select_folder('INBOX')
     
     # get a list of messages 
     potentials = server.search(['NOT DELETED'])
     response = server.fetch(potentials, ['BODY[HEADER]', 'BODY[TEXT]'])
     
     for k, v in response.iteritems():
         message = self.parse_rfc822(v['BODY[HEADER]'],
                                     v['BODY[TEXT]'])
         # get a list of all recipients
         recip = message.recipients() + message.cc
         # split off gus users from this list for special handling
         gus_recip = []
         for r in recip:
             if r.endswith(settings.EMAIL_SUFFIX):
                 gus_recip.append(r[:-len(settings.EMAIL_SUFFIX)])
         recip = [r for r in recip if r not in gus_recip]
         
         try:
             date = parse(re.search('date:([^\n]*)\n', 
                              v['BODY[HEADER]'], re.I).group(1).strip())
             t = date.utcoffset()
             date = date.replace(tzinfo=None) - t
         except Exception, e:
             logging.debug(e)
             date = None
         
         # store the email in the DB
         em = DBEmail()
         em.fill(v['BODY[HEADER]'], bleach.clean(v['BODY[TEXT]'], tags=settings.BLEACH_ALLOWED_TAGS), date,
                 message.from_email, recip, gus_recip)
         
         # now delete from server
         server.add_flags(k, ['\Deleted'])
예제 #5
0
def get_email_code(ip):
    with sem:
        server = IMAPClient(MAIL_IMAP_HOSTNAME)
        try:
            server.login(MAIL_ADDRESS, MAIL_PASSWD)
            server.select_folder('INBOX')
            _before = datetime.datetime.now() - datetime.timedelta(minutes=15)
            result = server.search(['UNSEEN', 'SINCE', _before])
            msgdict = server.fetch(result, ['BODY.PEEK[]'])
            msgtuple = sorted(msgdict.items(),
                              key=lambda e: e[0],
                              reverse=True)
            for message_id, message in msgtuple:
                msg_content = message['BODY[]']
                auth_code = do_email(msg_content, ip)
                if auth_code:
                    server.add_flags(message_id, '\\seen')
                    return auth_code
        finally:
            server.logout()
예제 #6
0
파일: main.py 프로젝트: IOEPAS/zippy
def mark_processed(client: IMAPClient, uid: int, logger: logging.Logger):
    """Add flags to not check the emails again."""
    client.select_folder(EmailFolders.INBOX)
    flags = client.get_flags(uid)

    if uid not in flags:
        logger.warn("Mail with uid: %s does not exist", uid)
        return
    if FLAG_TO_CHECK not in flags.get(uid):
        flags = client.add_flags(uid, FLAG_TO_CHECK)
        if uid not in flags:
            logger.warn(
                "Mail (uid: %s) could not added "
                "Weights might get updated twice", uid)
        else:
            logger.info("Flag added to %s", uid)
예제 #7
0
class Mail:
    @staticmethod
    def __add_attachments(file_list):

        # 附件list
        attachments = []

        # 如果file_list是单个文件名则将其包装为list
        # 否则作为文件名list处理
        for file_name in [file_list] if isinstance(file_list,
                                                   str) else file_list:

            mime_main_type, mime_sub_type = mime.guess_type(
                file_name)[0].split('/')

            with open(file_name, 'rb') as file:

                attachments += [(file.read(), mime_main_type, mime_sub_type,
                                 os.path.basename(file_name), 'UTF-8')]

        return attachments

    def __init__(self, conf_file_name):

        # 清空imap对象
        self.imap = None

        # 读取邮箱帐户以及imap和smtp的配置
        self.__conf = Util.read_conf_from_json(conf_file_name)

        # 构造lock对象
        self.__noop_mode_lock = Lock()

        # NOOP模式标识
        self.__noop_mode = False

    def __connect(self):

        if self.imap is not None:

            return

        # 建立imap连接, 使用ssl
        self.imap = IMAPClient(self.__conf['imap']['server'],
                               self.__conf['imap']['port'],
                               ssl=True)

        # 登录邮箱
        self.imap.login(self.__conf['profile']['account'],
                        self.__conf['profile']['password'])

    def get_white_list(self):

        return self.__conf['auth_sender']

    def open_mailbox(self, readonly=False):

        self.__connect()

        # 默认以读写模式打开指定的邮箱文件夹
        self.imap.select_folder(self.__conf['imap']['folder'].decode('UTF-8'),
                                readonly)

        # 返回self以支持链式操作
        return self

    def close_mailbox(self):

        if self.imap is None:

            return

        # 除非发生IMAPClientAbortError异常, 否则应该登出邮箱
        self.imap.logout()

        # 清空imap对象
        self.imap = None

    def get_today_unread(self):

        # 指定搜索条件(未读 + 今天)
        return self.imap.search([
            'UNSEEN',
            'ON',
            # 获取今天的日期, 格式为day-month-year, 如: 30-Sep-2017
            datetime.now().strftime('%d-%b-%Y')
        ])

    def get(self, *cond):

        return self.imap.search(cond)

    def fetch_header(self, uids):

        # 未读邮件摘要列表
        mail_digests = []

        # 获取邮件头信息
        for uid, envelope in self.imap.fetch(uids, ['ENVELOPE']).items():

            envelope = envelope['ENVELOPE']
            sender = envelope.from_[0]

            # 摘要的dict, keys = (邮件UID, 收件日期, 邮件主题, 发件人, 消息ID)
            digest = {
                'uid':
                uid,
                'date':
                envelope.date,
                'subject':
                smtp.parse.decode_mail_header(
                    envelope.subject).encode('UTF-8'),
                'sender':
                (smtp.parse.decode_mail_header(sender.name).encode('UTF-8'),
                 sender.mailbox + '@' + sender.host),
                'msg_id':
                envelope.message_id
            }

            log.info('Received mail(uid: <%d>)\n'
                     'date: <%s>\n'
                     'sub:  <%s>\n'
                     'who:  <%s: %s>\n' %
                     (uid, envelope.date, digest['subject'],
                      digest['sender'][0], digest['sender'][1]))

            # 添加到邮件摘要队列
            mail_digests += [digest]

        return mail_digests

    def mark_as_read(self, uid):

        self.imap.add_flags(uid, '\\SEEN')

    def mark_as_unread(self, uid):

        self.imap.remove_flags(uid, '\\SEEN')

    def mark_as_answered(self, uid):

        self.imap.add_flags(uid, '\\ANSWERED')

    def star_message(self, uid):

        self.imap.add_flags(uid, '\\FLAGGED')

    def fetch_body(self, uid):

        # 读取邮件正文
        return smtp.PyzMessage.factory(
            self.imap.fetch(uid, ['BODY[]'])[uid]['BODY[]'])

    def enter_noop_mode(self):

        if self.__noop_mode:

            return

        def keep_imap_alive():

            # 对self.__noop_mode的读取加锁
            self.__noop_mode_lock.acquire()

            # 如果当前处于NOOP模式
            if self.__noop_mode:

                # 向imap服务器发送NOOP指令
                self.imap.noop()

                # 15秒以后再次执行
                Timer(15, keep_imap_alive).start()

            # 释放锁
            self.__noop_mode_lock.release()

        # 将NOOP模式标识打开
        self.__noop_mode = True

        # 进入NOOP模式
        keep_imap_alive()

    def quit_noop_mode(self):

        # 对self.__noop_mode的写入加锁
        self.__noop_mode_lock.acquire()

        # 将NOOP模式标识关闭(退出NOOP模式)
        self.__noop_mode = False

        # 释放锁
        self.__noop_mode_lock.release()

    def wait_new_mail(self, timeout=None):

        # 将imap服务器设置为IDLE模式(消息推送模式)
        self.imap.idle()

        log.info('Waiting for a new mail...')

        # 等待消息推送
        msg = self.imap.idle_check(timeout)

        # 退出IDLE模式
        self.imap.idle_done()

        # 等待消息超时
        if not msg:

            log.info('Wait timeout.')

            return False

        log.info('Maybe a new mail has arrived?')

        # 已获得消息推送
        return True

    def send(self, mail_header, text='', html=None, file_list=None):

        # 构造邮件
        payload, mail_from, rcpt_to, msg_id = smtp.compose_mail(
            (self.__conf['profile']['name'],
             self.__conf['profile']['mailbox']),
            mail_header['recipients'],
            mail_header['subject'],
            'UTF-8', (text, 'UTF-8'),
            None if html is None else (html, 'UTF-8'),
            [] if file_list is None else Mail.__add_attachments(file_list),
            headers=mail_header['others']
            if mail_header.has_key('others') else [])

        # 发送邮件
        ret = smtp.send_mail(payload, mail_from, rcpt_to,
                             self.__conf['smtp']['server'],
                             self.__conf['smtp']['port'],
                             self.__conf['smtp']['mode'],
                             self.__conf['profile']['account'],
                             self.__conf['profile']['password'])

        # 发生错误
        if not isinstance(ret, dict):

            log.error('Send error: ' + ret)

            return False

        else:

            # 发送至某些收件人失败
            if ret:

                log.warning('Failed recipients: ')

                for recipient, (code, msg) in ret.iteritems():

                    log.warning('code: %d recipient: %s error: %s', code,
                                recipient, msg)

                return False

            # 发送成功
            log.info('Send to %s success.' % (rcpt_to))

            return True

    def reply(self,
              mail_digest,
              subject=None,
              text='',
              html=None,
              file_list=None):

        return self.send(
            {
                'recipients': [mail_digest['sender']],
                'subject':
                subject if subject is not None else '回复: ' +
                mail_digest['subject'],
                'others': [('IN-REPLY-TO', mail_digest['msg_id']),
                           ('REFERENCES', mail_digest['msg_id'])]
            }, text, html, file_list)
예제 #8
0
class IMAP():
    """
    Central class for IMAP server communication
    """
    Retval = namedtuple('Retval', 'code data')

    def __init__(self, logger, username, password,
                 server='localhost',
                 port=143,
                 starttls=False,
                 imaps=False,
                 tlsverify=True,
                 test=False,
                 timeout=None):
        self.logger = logger
        self.username = username
        self.password = password
        self.server = server
        self.port = port
        self.imaps = imaps
        self.starttls = starttls
        self.timeout = timeout

        self.sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)  # TODO add proto arg
        if tlsverify:
            self.sslcontext.verify_mode = ssl.CERT_REQUIRED
        else:
            self.sslcontext.verify_mode = ssl.CERT_NONE  # TODO improve?

        self.test = test

        self.conn = None

    def do_select_mailbox(func):
        """
        Decorator to do a fresh mailbox SELECT
        """

        def wrapper(*args, **kwargs):
            if len(args) != 1:
                raise AttributeError(
                    'Size of *args tuple "{0}" isn\'t 1. It looks like you haven\'t specified all '
                    'method arguments as named arguments!'.format(
                        args))

            mailbox = None
            for key in ['mailbox', 'source']:
                if key in kwargs.keys():
                    mailbox = kwargs[key]
                    break

            if mailbox is None:
                raise KeyError('Unable to SELECT a mailbox, kwargs "{0}" doesn\'t contain a mailbox name'.format(kwargs))

            result = args[0].select_mailbox(mailbox)
            if not result.code:
                raise RuntimeError(result.data)
            return func(*args, **kwargs)

        return wrapper

    def process_error(self, exception, simple_return=False):
        """
        Process Python exception by logging a message and optionally showing traceback
        """
        trace_info = exc_info()
        err_msg = str(exception)

        if isinstance(exception, IMAPClient.Error):
            err_msg = Helper().byte_to_str(exception.args[0])

        self.logger.error('Catching IMAP exception %s: %s', type(exception), err_msg)

        if self.logger.isEnabledFor(loglevel_DEBUG):
            print_exception(*trace_info)
        del trace_info

        if simple_return:
            return exception
        else:
            return self.Retval(False, err_msg)

    def connect(self, retry=True, logout=False):
        """
        Connect to IMAP server and login
        """
        if self.starttls:
            self.logger.debug('Establishing IMAP connection using STARTTLS/%s to %s and logging in with user %s', self.port, self.server,
                              self.username)
        elif self.imaps:
            self.logger.debug('Establishing IMAP connection using SSL/%s (imaps) to %s and logging in with user %s', self.port, self.server,
                              self.username)

        login = ''
        err_return = None
        try:
            self.conn = IMAPClient(host=self.server,
                                   port=self.port,
                                   use_uid=True,
                                   ssl=self.imaps,
                                   ssl_context=self.sslcontext,
                                   timeout=self.timeout)

            if self.starttls:
                self.conn.starttls(ssl_context=self.sslcontext)

            login = self.conn.login(self.username, self.password)
            login_response = Helper().byte_to_str(login)

            # Test login/auth status
            login_success = False
            noop = self.noop()
            if noop.code and noop.data:
                login_success = True

            if logout:
                return self.disconnect()
            elif login_success:
                return self.Retval(True, login_response)
            else:
                return self.Retval(False, login_response)  # pragma: no cover

        except Exception as e:
            err_return = self.process_error(e)

            if err_return.data == '[AUTHENTICATIONFAILED] Authentication failed.':
                return err_return

            if retry:
                self.logger.error('Trying one more time to login')
                sleep(2)
                return self.connect(retry=False, logout=logout)
            return err_return

    def noop(self):
        """
        Do a noop to test login status
        """
        try:
            noop = self.conn.noop()
            noop_response = Helper().byte_to_str(noop[0])
            noop_resp_pattern_re = regex_compile('^(Success|NOOP completed)')
            login_success = noop_resp_pattern_re.match(noop_response)
            return self.Retval(True, login_success)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def disconnect(self):
        """
        Disconnect from IMAP server
        """
        result = self.conn.logout()
        response = Helper().byte_to_str(result)
        return self.Retval(response == 'Logging out', response)

    def list_mailboxes(self, directory='', pattern='*'):
        """
        Get a listing of folders (mailboxes) on the server
        """
        try:
            raw_list = self.conn.list_folders(directory, pattern)
            nice_list = []

            for mailbox in raw_list:
                flags = []
                for flag in mailbox[0]:
                    flags.append(flag.decode('utf-8'))

                nice_list.append({'name': mailbox[2], 'flags': flags, 'delimiter': mailbox[1].decode("utf-8")})
            return self.Retval(True, nice_list)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def select_mailbox(self, mailbox):
        """
        Select a mailbox to work on
        """
        self.logger.debug('Switching to mailbox %s', mailbox)
        try:
            result = self.conn.select_folder(mailbox)
            response = {}
            for key, value in result.items():
                unicode_key = Helper().byte_to_str(key)
                if unicode_key == 'FLAGS':
                    flags = []
                    for flag in value:
                        flags.append(Helper().byte_to_str(flag))
                    response[unicode_key] = tuple(flags)
                else:
                    response[unicode_key] = value
            return self.Retval(True, response)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def add_mail(self, mailbox, message, flags=(), msg_time=None):
        """
        Add/append a mail to a mailbox
        """
        self.logger.debug('Adding a mail into mailbox %s', mailbox)
        try:
            if not isinstance(message, Mail):
                message = Mail(logger=self.logger, mail_native=message)
            message_native = message.get_native()

            #self.conn.append(mailbox, message, flags, msg_time)
            self._append(mailbox, str(message_native), flags, msg_time)

            # According to rfc4315 we must not return the UID from the response, so we are fetching it ourselves
            uids = self.search_mails(mailbox=mailbox, criteria='HEADER Message-Id "{0}"'.format(message.get_header('Message-Id'))).data[0]

            return self.Retval(True, uids)
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def search_mails(self, mailbox, criteria='ALL', autocreate_mailbox=False):
        """
        Search for mails in a mailbox
        """
        self.logger.debug('Searching for mails in mailbox %s and criteria=\'%s\'', mailbox, criteria)
        try:
            return self.Retval(True, list(self.conn.search(criteria=criteria)))
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def fetch_mails(self, uids, mailbox, return_fields=None):
        """
        Retrieve mails from a mailbox
        """
        self.logger.debug('Fetching mails with uids %s', uids)

        return_raw = True
        if return_fields is None:
            return_raw = False
            return_fields = [b'RFC822']

        mails = {}
        try:
            for uid in uids:
                result = self.conn.fetch(uid, return_fields)

                if not result:
                    continue

                if return_raw:
                    mails[uid] = result[uid]
                else:
                    #mails[uid] = Mail(logger=self.logger, uid=uid, mail_native=email.message_from_bytes(result[uid][b'RFC822']))
                    mails[uid] = Mail(logger=self.logger, mail_native=email.message_from_bytes(result[uid][b'RFC822']))
            return self.Retval(True, mails)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def get_mailflags(self, uids, mailbox):
        """
        Retrieve flags from mails
        """
        try:
            result = self.conn.get_flags(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error('Failed to get flags for mail with uid=%s: %s', uid, result)
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def set_mailflags(self, uids, mailbox, flags=[]):
        """
        Set and retrieve flags from mails
        """
        if self.test:
            self.logger.info('Would have set mail flags on message uids "%s"', str(uids))
            return self.Retval(True, None)
        else:
            self.logger.debug('Setting flags=%s on mails uid=%s', flags, uids)
            try:
                result = self.conn.set_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error('Failed to set and get flags for mail with uid=%s: %s', uid, result)
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def add_mailflags(self, uids, mailbox, flags=[]):
        """
        Add and retrieve flags from mails
        """
        if self.test:
            self.logger.info('Would have added mail flags on message uids "%s"', str(uids))
            return self.Retval(True, None)
        else:
            self.logger.debug('Adding flags=%s on mails uid=%s', flags, uids)
            try:
                result = self.conn.add_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error('Failed to add and get flags for mail with uid=%s: %s', uid, result)
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def move_mail(self, message_ids, source, destination, delete_old=True, expunge=True, add_flags=None, set_flags=None):
        """
        Move a mail from a mailbox to another
        """
        return self.copy_mails(message_ids=message_ids,
                               source=source,
                               destination=destination,
                               delete_old=delete_old,
                               expunge=expunge,
                               add_flags=add_flags,
                               set_flags=set_flags)

    @do_select_mailbox
    def copy_mails(self, source, destination, message_ids=None, delete_old=False, expunge=False, add_flags=None, set_flags=None):
        """
        Copies one or more mails from a mailbox into another
        """
        if self.test:
            if delete_old:
                self.logger.info('Would have moved mail Message-Ids="%s" from "%s" to "%s", skipping because of beeing in testmode',
                                 message_ids, source, destination)
            else:
                self.logger.info('Would have copied mails with Message-Ids="%s" from "%s" to "%s", skipping because of beeing in testmode',
                                 message_ids, source, destination)
            return self.Retval(True, None)
        else:
            try:
                if delete_old:
                    self.logger.debug('Moving mail Message-Ids="%s" from "%s" to "%s"', message_ids, source, destination)
                else:
                    self.logger.debug('Copying mail Message-Ids="%s" from "%s" to "%s"', message_ids, source, destination)

                #if message_ids is None:
                #    message_ids = []
                #    result = self.fetch_mails(uids=uids, mailbox=source)
                #    if not result.code:
                #        self.logger.error('Failed to determine Message-Id by uids for mail with uids "%s"', uids)
                #        return result
                #    message_ids.append(result.data.keys())

                if not self.mailbox_exists(destination).data:
                    self.logger.info('Destination mailbox %s doesn\'t exist, creating it for you', destination)

                    result = self.create_mailbox(mailbox=destination)
                    if not result.code:
                        self.logger.error('Failed to create the mailbox %s: %s', source, result.data)  # pragma: no cover
                        return result  # pragma: no cover

                uids = []
                for message_id in message_ids:
                    result = self.search_mails(mailbox=source, criteria='HEADER Message-Id "{0}"'.format(message_id))

                    if not result.code or len(result.data) == 0:
                        self.logger.error('Failed to determine uid by Message-Id for mail with Message-Id "%s"', message_id)
                        return self.Retval(False, result.data)
                    uids.append(result.data[0])

                result = self.select_mailbox(source)
                if not result.code:
                    return result  # pragma: no cover

                self.conn.copy(uids, destination)

                if delete_old:
                    result = self.delete_mails(uids=uids, mailbox=source)
                    if not result.code:
                        self.logger.error('Failed to remove old mail with Message-Id="%s"/uids="%s": %s', message_ids, uids,
                                          result.data)  # pragma: no cover
                        return result  # pragma: no cover

                    if expunge:  # TODO don't expunge by default
                        result = self.expunge(mailbox=source)
                        if not result.code:
                            self.logger.error('Failed to expunge on mailbox %s: %s', source, result.data)  # pragma: no cover
                            return result  # pragma: no cover

                dest_uids = []
                for message_id in message_ids:
                    result = self.search_mails(mailbox=destination, criteria='HEADER Message-Id "{0}"'.format(message_id))
                    if not result.code:
                        self.logger.error('Failed to determine uid by Message-Id for mail with Message-Id "%s"',
                                          message_id)  # pragma: no cover
                        return result  # pragma: no cover
                    dest_uids.append(result.data[0])

                if isinstance(set_flags, list):
                    self.set_mailflags(uids=dest_uids, mailbox=destination, flags=set_flags)
                if add_flags:
                    self.add_mailflags(uids=dest_uids, mailbox=destination, flags=add_flags)

                return self.Retval(True, dest_uids)

            except IMAPClient.Error as e:
                return self.process_error(e)

    def _append(self, folder, msg, flags=(), msg_time=None):  # TODO
        """
        FORKED FORM IMAPCLIENT
        """
        if msg_time:
            if not msg_time.tzinfo:  # pragma: no cover
                msg_time = msg_time.replace(tzinfo=FixedOffset.for_system())  # pragma: no cover

            time_val = '"{0}"'.format(msg_time.strftime("%d-%b-%Y %H:%M:%S %z"))
            time_val = imapclient.imapclient.to_unicode(time_val)
        else:
            time_val = None

        return self.conn._command_and_check('append', self.conn._normalise_folder(folder), imapclient.imapclient.seq_to_parenstr(flags),
                                            time_val, Helper.str_to_bytes(msg),
                                            unpack=True)

    @do_select_mailbox
    def expunge(self, mailbox):
        """
        Expunge mails form a mailbox
        """
        self.logger.debug('Expunge mails from mailbox %s', mailbox)
        try:
            return self.Retval(True, b'Expunge completed.' in self.conn.expunge())
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    def create_mailbox(self, mailbox):
        """
        Create a mailbox
        """
        self.logger.debug('Creating mailbox %s', mailbox)
        try:
            return self.Retval(True, self.conn.create_folder(mailbox) == b'Create completed.')
        except IMAPClient.Error as e:
            return self.process_error(e)

    def mailbox_exists(self, mailbox):
        """
        Check whether a mailbox exists
        """
        try:
            return self.Retval(True, self.conn.folder_exists(mailbox))
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    @do_select_mailbox
    def delete_mails(self, uids, mailbox):
        """
        Delete mails
        """
        self.logger.debug('Deleting mails with uid="%s"', uids)
        try:
            result = self.conn.delete_messages(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error('Failed to get flags for mail with uid=%s after deleting it: %s', uid, result)
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)
        except IMAPClient.Error as e:
            return self.process_error(e)
예제 #9
0
class AccountChecker(threading.Thread):
    def __init__(self, host, username, password):
        super().__init__()
        self.host = host
        self.username = username
        self.password = password

        self.messages = list()

        self.socket_timeout = SocketTimeout(connect=10, read=5) # timeout en secondes sur le socket
        self.idle_timeout = Config.IDLE_TIMEOUT # pour renouvellement low level connections
        self._idle_check_timeout = Config.IDLE_CHECK_TIMEOUT # C'est un:  while not timeout: socket.poll (pas d'échanges avec le serveur)
        
        self._server = None

        self._stop_check = False
        self._network_is_down = False
        self._needs_reconnect = True
        self._mark_has_read = False


    def get_state(self, state_has_changed, is_network_down):
        self._network_is_down = is_network_down

        if state_has_changed:
            self._needs_reconnect = True


    def check(self):
        self._reconnect_if_needed()

        while True:
            try:
                self._idle_check()
            except Exception as e:
                self._needs_reconnect = True
            
            self._reconnect_if_needed()
                
            if self._stop_check:
                break

        self._server.logout()
    

    def mark_all_read(self):
        self._mark_has_read = True


    def stop(self):
        self._stop_check = True


    def _connect_and_fetch(self):
        self._server = IMAPClient(self.host, timeout=self.socket_timeout)
        self._server.login(self.username, self.password)

        self._server.select_folder('INBOX')

        self._fetch_unseen()


    # suite à timeout sur self._server.idle_done()
    # je suppose qu'il faut se reconnecter
    def _reconnect_if_needed(self, idle=False):
        nbr_tentatives = 0
        
        while self._needs_reconnect:
            if not self._network_is_down:
                nbr_tentatives += 1
                try:
                    self._server.idle_done()
                    self._server.logout()
                except:
                    pass

                try:
                    self._connect_and_fetch()
                    if idle:
                        self._server.idle()
                    self._needs_reconnect = False
                except Exception as e:
                    print("Sleeping {}".format(pow(nbr_tentatives, 3)))
                    time.sleep((pow(nbr_tentatives, 3)))
            else:
                time.sleep(1)


    # La boucle principale
    def _idle_check(self):
        started_at = time.time()
        self._server.idle()
    
        while True:
            response = self._server.idle_check(timeout=self._idle_check_timeout)

            if response:
                if not response ==  [(b'OK', b'Still here')]:
                    self._fetch_unseen(idle=True)

            if self._needs_reconnect:
                break

            if self._mark_has_read:
                self._messages_seen(idle=True)
                

            if self._stop_check or time.time() - started_at > self.idle_timeout:
                self._server.idle_done()
                break
        
        
    def _messages_seen(self, idle=False):
        if len(self.messages):
            if idle:
                self._server.idle_done()

            for msgid, _ in self.messages:
                self._server.add_flags(msgid, [SEEN])

            self._fetch_unseen()

            if idle:
                self._server.idle()
        
        self._mark_has_read = False

    def _fetch_unseen(self, idle=False):
        if idle:
            self._server.idle_done()

        self.messages = list()
        non_lus = self._server.search('UNSEEN')

        for msgid, data in self._server.fetch(non_lus, ['ENVELOPE']).items():
            envelope = data[b'ENVELOPE']
            subject = self._decode_header(envelope.subject.decode())
            user = self.username.split("@")[0]
            msg_str = "{user} - '{subject}' de  {from_}".format(user=user, subject=subject, from_=envelope.from_[0].mailbox.decode() + "@" + envelope.from_[0].host.decode())
            self.messages.append((msgid, msg_str))
        
        if idle:
            self._server.idle()


    def _decode_header(self, txt):
        result = ""

        for part in email.header.decode_header(txt):
            if not part[1]:
                if isinstance(part[0], bytes):
                    result += part[0].decode()
                else:
                    result += part[0]
            else:
                result += part[0].decode(part[1])
        
        return result
예제 #10
0
def checkmail():
    global lastEmailCheck
    global lastFeed
    global feedInterval
    
    if (time.time() > (lastEmailCheck + MAILCHECKDELAY)):  # Make sure that that a tleast MAILCHECKDELAY time has passed
        lastEmailCheck = time.time()
        server = IMAPClient(GMAILHOSTNAME, use_uid=True, ssl=True)  # Create the server class from IMAPClient with HOSTNAME mail server
        server.login(GMAILUSER, GMAILPASSWD)
        server.select_folder(MAILBOX)


        ##############    Mensajes WHEN    #####################
        

        # See if there are any messages with subject "When" that are unread
        whenMessages = server.search([u'UNSEEN', u'SUBJECT', u'When'])

        # Respond to the when messages
        if whenMessages:
            for msg in whenMessages:
                msginfo = server.fetch([msg], ['BODY[HEADER.FIELDS (FROM)]'])
                fromAddress = str(msginfo).replace("<class 'dict'>", "").split('<')[1].split('>')[0]          
                
                msgBody = "The last feeding was done on " + time.strftime("%b %d at %I:%M %p", time.localtime(lastFeed))

                if (time.time() - lastFeed) > feedInterval:
                    msgBody = msgBody + "\nReady to feed now!"
                else:
                    msgBody = msgBody + "\nThe next feeding can begin on " + time.strftime("%b %d at %I:%M %p", time.localtime(lastFeed + feedInterval))
                                              
                sendemail(fromAddress, "Thanks for your feeding query", msgBody)
                server.add_flags(whenMessages, [SEEN])


        ##############    Mensajes SET    #####################        


        # See if there are any messages with subject "SET" that are unread
        setMessages = server.search([u'UNSEEN', u'SUBJECT', u'Set'])

        # Respond to the set messages
        if setMessages:
            for msg in setMessages:
                msginfo = server.fetch([msg], ['BODY[HEADER.FIELDS (FROM)]'])
                fromAddress = str(msginfo).replace("<class 'dict'>", "").split('<')[1].split('>')[0]
                                   
                setLastFeed()
                
                msgBody = "The last feeding date was set on " + time.strftime("%b %d at %I:%M %p", time.localtime(lastFeed))
                                     
                sendemail(fromAddress, "\nThanks for setting time", msgBody)
                server.add_flags(whenMessages, [SEEN])          


        ##############    Mensajes Close    #####################


        # See if there are any messages with subject "Close" that are unread
        closeMessages = server.search([u'UNSEEN', u'SUBJECT', u'Close'])

        # Respond to the close messages
        if closeMessages:
            for msg in closeMessages:
                                                   
                sys.exit()


        ##############    Mensajes Feed    #####################    


        # See if there are any messages with subject "Feed" that are unread
        feedMessages = server.search([u'UNSEEN', u'SUBJECT', u'Feed'])
        
        # Respond to the feed messages and then exit
        if feedMessages:
            for msg in feedMessages:
                msginfo = server.fetch([msg], ['BODY[HEADER.FIELDS (FROM)]'])                
                fromAddress = str(msginfo).replace("<class 'dict'>", "").split('<')[1].split('>')[0]

                msgBody = "The last feeding was done at " + time.strftime("%b %d at %I:%M %p", time.localtime(lastFeed))
                if (time.time() - lastFeed) > feedInterval:
                    msgBody = msgBody + "\nReady to be fed, will be feeding Mila shortly"
                else:
                    msgBody = msgBody + "\nThe next feeding can begin at " + time.strftime("%b %d at %I:%M %p", time.localtime(lastFeed + feedInterval))
                                   
                sendemail(fromAddress, "Thanks for your feeding request", msgBody)

                server.add_flags(feedMessages, [SEEN])
            
            return True
    return False
예제 #11
0
class Miner:
    """create a new email miner instance that can be used to traverse mails"""

    imap: IMAPClient = None

    def __init__(self,
                 hostname: str,
                 username: str,
                 password: str,
                 port: int = imaplib.IMAP4_SSL_PORT,
                 use_ssl: bool = True,
                 verify: bool = True,
                 log_level: int = None):
        """
        Create a new instance of the miner.

        :param hostname: the hostname of the imap server to connect to
        :param username: the user to login as
        :param password:
        :param port: the port to connect to. (defaults to 993)
        :param use_ssl: whether to use SSL to connect (defaults to True)
        :param verify: whether to verify the SSL certificates (defaults to False)
        """

        if log_level is not None:
            logging.basicConfig(
                format='%(asctime)s - %(levelname)s: %(message)s',
                level=log_level)

        ssl_context = ssl.create_default_context()

        if not verify:
            # disable hostname check. certificate may not match hostname.
            ssl_context.check_hostname = False
            # disable certificate authority verification. certificate maybe issued by unknown CA
            ssl_context.verify_mode = ssl.CERT_NONE

        self.imap = IMAPClient(host=hostname,
                               port=port,
                               ssl=use_ssl,
                               ssl_context=ssl_context)
        self.imap.login(username, password)

    @contextlib.contextmanager
    def folder(self, folder_name: str, read_only: bool = True):
        """
        Switch to a specific folder.

        :param folder_name: name of the folder to switch to
        :param read_only: read-only mode will not mark emails as read even after retrieval
        :return:
        """
        try:
            yield self.imap.select_folder(folder_name, read_only)
        finally:
            self.imap.close_folder()

    @contextlib.contextmanager
    def inbox(self, read_only: bool = True):
        """
        Switch to the inbox folder.

        :param read_only: read-only mode will not mark emails as read even after retrieval
        :return:
        """
        try:
            yield self.imap.select_folder('inbox', read_only)
        finally:
            self.imap.close_folder()

    def mark_as_unread(self, message_ids: List[int]):
        """
        Mark the given message IDs as unread by removing the SEEN flag.

        :param message_ids:
        :return:
        """
        self.imap.remove_flags(message_ids, [SEEN])

    def mark_as_read(self, message_ids: List[int]):
        """
        Mark the given message IDs as read by adding the SEEN flag.

        :param message_ids:
        :return:
        """
        self.imap.add_flags(message_ids, [SEEN])

    def delete(self, message_ids: List[int]):
        """
        Delete the given message IDs

        :param message_ids:
        :return:
        """
        self.imap.delete_messages(message_ids, True)

    def archive(self, message_ids: List[int]):
        """
        Archive the given message IDs

        :param message_ids:
        :return:
        """
        self.imap.copy(message_ids, br'\Archive')
        self.delete(message_ids)

    def get_emails(self,
                   unread_only: bool = True,
                   with_body: bool = False,
                   keep_as_unread: bool = False,
                   in_memory: bool = True) -> List[Email]:
        """
        Get emails from the selected folder.

        :param keep_as_unread: keep any retrieved emails as unread in the mailbox.
        :param unread_only: choose only to retrieve unread mails
        :param with_body: read-only mode will not mark emails as read even after retrieval
        :param in_memory: store the parsed attachments in-memory as bytes or to a temp file locally
        :return:
        """
        ids = self.imap.search('(UNSEEN)' if unread_only else 'ALL')
        flags = ['ENVELOPE', 'FLAGS', 'UID', 'INTERNALDATE']

        if with_body:
            flags.append('BODY[]')

        response = self.imap.fetch(ids, flags)

        try:
            if keep_as_unread:
                self.mark_as_unread(ids)
            else:
                self.mark_as_read(ids)
        except Exception:
            # will throw an exception if folder in read-only mode. so ignore.
            pass

        return parse_emails(response, in_memory)

    def __enter__(self):
        """
        return the instance of the miner for use as a context manager.

        :return:
        """
        return self

    def __exit__(self, *args):
        """
        Close folder and logout on exit when used as a context manager.

        :param args:
        :return:
        """
        if self.imap is not None:
            try:
                self.imap.close_folder()
            except:
                pass
            self.imap.logout()
예제 #12
0
def loop():
  try:  
    server = IMAPClient(HOSTNAME, use_uid=True, ssl=True)
    server.login(USERNAME, PASSWORD)
    
    select_info = server.select_folder(MAILBOX) 

    if DEBUG:
        print('Logging in as ' + USERNAME)
        print('%d messages in "Secret messages"' % select_info['EXISTS'])
 
    folder_status = server.folder_status(MAILBOX, 'UNSEEN')
    newmails = int(folder_status['UNSEEN'])
 
    if DEBUG:
        print "You have", newmails, "new recent emails!"


    messages = server.search(['UNSEEN']) #NOT DELETED to see all email available 

    if DEBUG:
        print("There are %d messages that aren't read" % len(messages))  
        print("Messages:")

    response = server.fetch(messages, ['FLAGS', 'RFC822.SIZE'])
    mail_array = []
    
    for msgid, data in response.iteritems():
        if DEBUG:
            print('   ID %d: %d bytes, flags=%s' % (msgid, data[b'RFC822.SIZE'], data[b'FLAGS']))    
        mail_array.append(msgid)
    
    if DEBUG:
       for elements  in mail_array:
           print(elements)
       

    
    if newmails > NEWMAIL_OFFSET:
        strip.setPixelColorRGB(0,0,0,255) #turn blue our neopixel if we have something to print
        strip.show()


        printer.wake()    
        time.sleep(5)
        printer.feed(1)

        for elements in mail_array:
            if printer.hasPaper() == True: #verify is printer has paper before printing.
               body = server.fetch(elements,['BODY.PEEK[1]']) #IMPORTANT you might have problems with these, try using "1", "1.1", "1.2" or "2". 
               if DEBUG:
                  print(body) #prints in console original text.
                  print(' ')
               body2 = decode_email(str(body)) #process the received text.
               if DEBUG:
                  print (body2) #prints in console processed text.
               #ticket_summary(body2) #call the ticket printing routine
               printer.println(body) #prints in paper the original text of the email. Change to "body2" for printing the processed version.
               if printer.hasPaper() == True: #verify is printer has paper after printing.
                  server.add_flags(elements, '\\Seen')  #mark as read if printed sucesfully.
               else:
                  strip.setPixelColorRGB(0,255,255,0) #show yellow neopixel if there is no paper in the printer.
                  strip.show()
                  if DEBUG:
                     print 'There is no paper in the printer.'
               if DEBUG:
                  print(' ')
                  time.sleep(12)
            else:
              strip.setPixelColorRGB(0,255,255,0) #show yellow neopixel if there is no paper in the printer.
              strip.show()
              if DEBUG:
                 print 'There is no paper in the printer.'
        printer.sleep() 
        
    else:
        strip.setPixelColorRGB(0,255,0,0) #show red neopixel if there are no emails for printing.
        strip.show()
        
  except:
    strip.setPixelColorRGB(0,255,0,255) #show purple neopixel if an error occurs reading the email.
    strip.show()
    if DEBUG:
        print 'Cant check email or an error has ocurred. Verify your internet conection or this script parameters'
예제 #13
0
    def _notify_tracim(self, mails: typing.List[DecodedMail],
                       imapc: imapclient.IMAPClient) -> None:
        """
        Send http request to tracim endpoint
        :param mails: list of mails to send
        :return: none
        """
        logger.debug(
            self, 'Notify tracim about {} new responses'.format(len(mails), ))
        # TODO BS 20171124: Look around mail.get_from_address(), mail.get_key()
        # , mail.get_body() etc ... for raise InvalidEmailError if missing
        #  required informations (actually get_from_address raise IndexError
        #  if no from address for example) and catch it here
        while mails:
            mail = mails.pop()
            body = mail.get_body(
                use_html_parsing=self.use_html_parsing,
                use_txt_parsing=self.use_txt_parsing,
            )
            from_address = mail.get_from_address()

            # don't create element for 'empty' mail
            if not body:
                logger.warning(
                    self,
                    'Mail from {} has not valable content'.format(
                        from_address),
                )
                continue

            msg = {
                'token': self.token,
                'user_mail': from_address,
                'content_id': mail.get_key(),
                'payload': {
                    'content': body,
                }
            }
            try:
                logger.debug(
                    self,
                    'Contact API on {} with body {}'.format(
                        self.endpoint,
                        json.dumps(msg),
                    ),
                )
                r = requests.post(self.endpoint, json=msg)
                if r.status_code not in [200, 204]:
                    details = r.json().get('msg')
                    log = 'bad status code {} response when sending mail to tracim: {}'  # nopep8
                    logger.error(self, log.format(
                        str(r.status_code),
                        details,
                    ))
                # Flag all correctly checked mail
                if r.status_code in [200, 204, 400]:
                    imapc.add_flags((mail.uid, ), IMAP_CHECKED_FLAG)
                    imapc.add_flags((mail.uid, ), IMAP_SEEN_FLAG)
            # TODO - G.M - Verify exception correctly works
            except requests.exceptions.Timeout as e:
                log = 'Timeout error to transmit fetched mail to tracim : {}'
                logger.error(self, log.format(str(e)))
            except requests.exceptions.RequestException as e:
                log = 'Fail to transmit fetched mail to tracim : {}'
                logger.error(self, log.format(str(e)))
예제 #14
0
class IMAP():
    """
    Central class for IMAP server communication
    """
    Retval = namedtuple('Retval', 'code data')

    def __init__(self,
                 logger,
                 username,
                 password,
                 server='localhost',
                 port=143,
                 starttls=False,
                 imaps=False,
                 tlsverify=True,
                 test=False,
                 timeout=None):
        self.logger = logger
        self.username = username
        self.password = password
        self.server = server
        self.port = port
        self.imaps = imaps
        self.starttls = starttls
        self.timeout = timeout

        self.sslcontext = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
        if tlsverify:
            self.sslcontext.verify_mode = ssl.CERT_REQUIRED
        else:
            self.sslcontext.check_hostname = False
            self.sslcontext.verify_mode = ssl.CERT_NONE

        self.test = test
        self.conn = None

    def do_select_mailbox(func):
        """
        Decorator to do a fresh mailbox SELECT
        """
        def wrapper(*args, **kwargs):
            if len(args) != 1:
                raise AttributeError(
                    'Size of *args tuple "{0}" isn\'t 1. It looks like you haven\'t specified all '
                    'method arguments as named arguments!'.format(args))

            mailbox = None
            for key in ['mailbox', 'source']:
                if key in kwargs.keys():
                    mailbox = kwargs[key]
                    break

            if mailbox is None:
                raise KeyError(
                    'Unable to SELECT a mailbox, kwargs "{0}" doesn\'t contain a mailbox name'
                    .format(kwargs))

            result = args[0].select_mailbox(mailbox)
            if not result.code:
                raise RuntimeError(result.data)
            return func(*args, **kwargs)

        return wrapper

    def process_error(self, exception, simple_return=False):
        """
        Process Python exception by logging a message and optionally showing traceback
        """
        trace_info = exc_info()
        err_msg = str(exception)

        if isinstance(exception, IMAPClient.Error):
            err_msg = Helper().byte_to_str(exception.args[0])

        self.logger.error("Catching IMAP exception {}: {}".format(
            type(exception), err_msg))

        if self.logger.isEnabledFor(loglevel_DEBUG):
            print_exception(*trace_info)
        del trace_info

        if simple_return:
            return exception
        else:
            return self.Retval(False, err_msg)

    def connect(self, retry=True, logout=False):
        """
        Connect to IMAP server and login
        """
        if self.starttls:
            self.logger.debug(
                'Establishing IMAP connection using STARTTLS/{} to {} and logging in with user {}'
                .format(self.port, self.server, self.username))
        elif self.imaps:
            self.logger.debug(
                'Establishing IMAP connection using SSL/{} (imaps) to {} and logging in with user {}'
                .format(self.port, self.server, self.username))
        try:
            self.conn = IMAPClient(host=self.server,
                                   port=self.port,
                                   use_uid=True,
                                   ssl=self.imaps,
                                   ssl_context=self.sslcontext,
                                   timeout=self.timeout)

            if self.starttls:
                self.conn.starttls(ssl_context=self.sslcontext)

            login = self.conn.login(self.username, self.password)
            login_response = Helper().byte_to_str(login)

            # Test login/auth status
            login_success = False
            noop = self.noop()
            if noop.code and noop.data:
                login_success = True

            if logout:
                return self.disconnect()
            elif login_success:
                return self.Retval(True, login_response)
            else:
                return self.Retval(False, login_response)  # pragma: no cover

        except exceptions.LoginError as e:
            return self.process_error(e)

        except Exception as e:
            err_return = self.process_error(e)

            if retry:
                self.logger.error('Trying one more time to login')
                sleep(2)
                return self.connect(retry=False, logout=logout)
            return err_return

    def noop(self):
        """
        Do a noop to test login status
        """
        try:
            noop = self.conn.noop()
            noop_response = Helper().byte_to_str(noop[0])
            noop_resp_pattern_re = regex_compile('^(Success|NOOP completed)')
            login_success = noop_resp_pattern_re.match(noop_response)
            return self.Retval(True, login_success)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def disconnect(self):
        """
        Disconnect from IMAP server
        """
        result = self.conn.logout()
        response = Helper().byte_to_str(result)
        return self.Retval(response == 'Logging out', response)

    def list_mailboxes(self, directory='', pattern='*'):
        """
        Get a listing of folders (mailboxes) on the server
        """
        try:
            raw_list = self.conn.list_folders(directory, pattern)
            nice_list = []

            for mailbox in raw_list:
                flags = []
                for flag in mailbox[0]:
                    flags.append(flag.decode('utf-8'))

                nice_list.append({
                    'name': mailbox[2],
                    'flags': flags,
                    'delimiter': mailbox[1].decode("utf-8")
                })
            return self.Retval(True, nice_list)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def select_mailbox(self, mailbox):
        """
        Select a mailbox to work on
        """
        self.logger.debug('Switching to mailbox {}'.format(mailbox))
        try:
            result = self.conn.select_folder(mailbox)
            response = {}
            for key, value in result.items():
                unicode_key = Helper().byte_to_str(key)
                if unicode_key == 'FLAGS':
                    flags = []
                    for flag in value:
                        flags.append(Helper().byte_to_str(flag))
                    response[unicode_key] = tuple(flags)
                else:
                    response[unicode_key] = value
            return self.Retval(True, response)
        except IMAPClient.Error as e:
            return self.process_error(e)

    def add_mail(self, mailbox, message, flags=(), msg_time=None):
        """
        Add/append a mail to a mailbox
        """
        self.logger.debug('Adding a mail into mailbox {}'.format(mailbox))
        try:
            if not isinstance(message, Mail):
                message = Mail(logger=self.logger, mail_native=message)

            self.conn.append(mailbox, str(message.get_native()), flags,
                             msg_time)

            # According to rfc4315 we must not return the UID from the response, so we are fetching it ourselves
            uids = self.search_mails(mailbox=mailbox,
                                     criteria='HEADER Message-Id "{}"'.format(
                                         message.get_message_id())).data[0]

            return self.Retval(True, uids)
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def search_mails(self, mailbox, criteria='ALL', autocreate_mailbox=False):
        """
        Search for mails in a mailbox
        """
        self.logger.debug(
            'Searching for mails in mailbox {} and criteria=\'{}\''.format(
                mailbox, criteria))
        try:
            return self.Retval(True, list(self.conn.search(criteria=criteria)))
        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def fetch_mails(self, uids, mailbox, return_fields=None):
        """
        Retrieve mails from a mailbox
        """
        self.logger.debug('Fetching mails with uids {}'.format(uids))

        return_raw = True
        if return_fields is None:
            return_raw = False
            return_fields = [b'RFC822']

        mails = {}
        try:
            for uid in uids:
                result = self.conn.fetch(uid, return_fields)

                if not result:
                    continue

                if return_raw:
                    mails[uid] = result[uid]
                else:
                    # mails[uid] = Mail(logger=self.logger, uid=uid, mail_native=email.message_from_bytes(result[uid][b'RFC822']))
                    mails[uid] = Mail(logger=self.logger,
                                      mail_native=email.message_from_bytes(
                                          result[uid][b'RFC822']))
            return self.Retval(True, mails)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def get_mailflags(self, uids, mailbox):
        """
        Retrieve flags from mails
        """
        try:
            result = self.conn.get_flags(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error(
                        'Failed to get flags for mail with uid={}: {}'.format(
                            uid, result))
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)

        except IMAPClient.Error as e:
            return self.process_error(e)

    @do_select_mailbox
    def set_mailflags(self, uids, mailbox, flags=[]):
        """
        Set and retrieve flags from mails
        """
        if self.test:
            self.logger.info(
                'Would have set mail flags on message uids "{}"'.format(
                    str(uids)))
            return self.Retval(True, None)
        else:
            self.logger.debug('Setting flags={} on mails uid={}', flags, uids)
            try:
                result = self.conn.set_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error(
                            'Failed to set and get flags for mail with uid={}: {}'
                            .format(uid, result))
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def add_mailflags(self, uids, mailbox, flags=[]):
        """
        Add and retrieve flags from mails
        """
        if self.test:
            self.logger.info(
                'Would have added mail flags on message uids "{}"'.format(
                    str(uids)))
            return self.Retval(True, None)
        else:
            self.logger.debug('Adding flags={} on mails uid={}', flags, uids)
            try:
                result = self.conn.add_flags(uids, flags)

                _flags = {}
                for uid in uids:
                    _flags[uid] = []
                    if uid not in result.keys():
                        self.logger.error(
                            'Failed to add and get flags for mail with uid={}: {}'
                            .format(uid, result))
                        return self.Retval(False, None)
                    for flag in result[uid]:
                        _flags[uid].append(flag.decode('utf-8'))
                return self.Retval(True, _flags)
            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def move_mail(self,
                  message_ids,
                  source,
                  destination,
                  delete_old=True,
                  expunge=True,
                  add_flags=None,
                  set_flags=None):
        """
        Move a mail from a mailbox to another
        """
        return self.copy_mails(message_ids=message_ids,
                               source=source,
                               destination=destination,
                               delete_old=delete_old,
                               expunge=expunge,
                               add_flags=add_flags,
                               set_flags=set_flags)

    @do_select_mailbox
    def copy_mails(self,
                   source,
                   destination,
                   message_ids=None,
                   delete_old=False,
                   expunge=False,
                   add_flags=None,
                   set_flags=None):
        """
        Copies one or more mails from a mailbox into another
        """
        if self.test:
            if delete_old:
                self.logger.info(
                    'Would have moved mail Message-Ids="{}" from "{}" to "{}", skipping because of beeing in testmode'
                    .format(message_ids, source, destination))
            else:
                self.logger.info(
                    'Would have copied mails with Message-Ids="{}" from "{}" to "{}", skipping because of beeing in testmode'
                    .format(message_ids, source, destination))
            return self.Retval(True, None)
        else:
            try:
                if delete_old:
                    self.logger.debug(
                        'Moving mail Message-Ids="{}" from "{}" to "{}"'.
                        format(message_ids, source, destination))
                else:
                    self.logger.debug(
                        'Copying mail Message-Ids="{}" from "{}" to "{}"'.
                        format(message_ids, source, destination))

                # if message_ids is None:
                #    message_ids = []
                #    result = self.fetch_mails(uids=uids, mailbox=source)
                #    if not result.code:
                #        self.logger.error('Failed to determine Message-Id by uids for mail with uids "{}"', uids)
                #        return result
                #    message_ids.append(result.data.keys())

                if not self.mailbox_exists(destination).data:
                    self.logger.info(
                        'Destination mailbox {} doesn\'t exist, creating it for you'
                        .format(destination))

                    result = self.create_mailbox(mailbox=destination)
                    if not result.code:
                        self.logger.error(
                            'Failed to create the mailbox {}: {}'.format(
                                source, result.data))  # pragma: no cover
                        return result  # pragma: no cover

                uids = []
                for message_id in message_ids:
                    result = self.search_mails(
                        mailbox=source,
                        criteria='HEADER Message-Id "{}"'.format(message_id))

                    if not result.code or len(result.data) == 0:
                        self.logger.error(
                            'Failed to determine uid by Message-Id for mail with Message-Id "{}"'
                            .format(message_id))
                        return self.Retval(False, result.data)
                    uids.append(result.data[0])

                result = self.select_mailbox(source)
                if not result.code:
                    return result  # pragma: no cover

                self.conn.copy(uids, destination)

                if delete_old:
                    result = self.delete_mails(uids=uids, mailbox=source)
                    if not result.code:
                        self.logger.error(
                            'Failed to remove old mail with Message-Id="{}"/uids="{}": {}'
                            .format(message_ids, uids,
                                    result.data))  # pragma: no cover
                        return result  # pragma: no cover

                    if expunge:  # TODO don't expunge by default
                        result = self.expunge(mailbox=source)
                        if not result.code:
                            self.logger.error(
                                'Failed to expunge on mailbox {}: {}'.format(
                                    source, result.data))  # pragma: no cover
                            return result  # pragma: no cover

                dest_uids = []
                for message_id in message_ids:
                    result = self.search_mails(
                        mailbox=destination,
                        criteria='HEADER Message-Id "{}"'.format(message_id))
                    if not result.code:
                        self.logger.error(
                            'Failed to determine uid by Message-Id for mail with Message-Id "{}"'
                            .format(message_id))  # pragma: no cover
                        return result  # pragma: no cover
                    dest_uids.append(result.data[0])

                if isinstance(set_flags, list):
                    self.set_mailflags(uids=dest_uids,
                                       mailbox=destination,
                                       flags=set_flags)
                if add_flags:
                    self.add_mailflags(uids=dest_uids,
                                       mailbox=destination,
                                       flags=add_flags)

                return self.Retval(True, dest_uids)

            except IMAPClient.Error as e:
                return self.process_error(e)

    @do_select_mailbox
    def expunge(self, mailbox):
        """
        Expunge mails form a mailbox
        """
        self.logger.debug('Expunge mails from mailbox {}'.format(mailbox))
        try:
            return self.Retval(True, b'Expunge completed.'
                               in self.conn.expunge())
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    def create_mailbox(self, mailbox):
        """
        Create a mailbox
        """
        self.logger.debug('Creating mailbox {}'.format(mailbox))
        try:
            return self.Retval(
                True,
                self.conn.create_folder(mailbox) == b'Create completed.')
        except IMAPClient.Error as e:
            return self.process_error(e)

    def mailbox_exists(self, mailbox):
        """
        Check whether a mailbox exists
        """
        try:
            return self.Retval(True, self.conn.folder_exists(mailbox))
        except IMAPClient.Error as e:  # pragma: no cover
            return self.process_error(e)  # pragma: no cover

    @do_select_mailbox
    def delete_mails(self, uids, mailbox):
        """
        Delete mails
        """
        self.logger.debug('Deleting mails with uid="{}"'.format(uids))
        try:
            result = self.conn.delete_messages(uids)
            flags = {}

            for uid in uids:
                flags[uid] = []
                if uid not in result.keys():
                    self.logger.error(
                        'Failed to get flags for mail with uid={} after deleting it: {}'
                        .format(uid, result))
                    return self.Retval(False, None)
                for flag in result[uid]:
                    flags[uid].append(flag.decode('utf-8'))
            return self.Retval(True, flags)
        except IMAPClient.Error as e:
            return self.process_error(e)
예제 #15
0
파일: imap.py 프로젝트: huayra/makrobe
         #print "  " +mess.get_content_type()
         if mess.get_content_type() == "application/pdf":
            filename = mess.get_filename().split('/')[-1]     # get rid of path
            filename = re.sub('[^a-zA-Z\.0-9_]','_',filename) # take no chances, accept only alpahnum!
            filename = TMPPATH + filename
            #print mess.get_payload().decode()
            print "%s" % filename
            if not REAL:
               continue
               
            f = open(filename, 'w')
            pdf = mess.get_payload(decode=True)
            f.write(pdf)
            f.close()
            count = count + 1
            # now mark as downloaded and processed
            #print "progress: %d" % count
            #print ("pdftotext -nopgbrk -raw " + filename + " - | perl cultura.pl")

      if REAL:
         #print("setting flags on message")
         server.add_gmail_labels(msgid, (done_label))
         server.add_flags(msgid,(done_flag))
         server.remove_gmail_labels(msgid, ('\\Inbox'))

   #else:
      #print msg.get_content_type()
      #print("meh")

   #exit()
예제 #16
0
def checkMail():

    global lastEmailCheck
    global latestFeed
    global feedInterval

    if (time.time() > (lastEmailCheck + 40)):

        lastEmailCheck = time.time()
        server = IMAPClient('imap.gmail.com', use_uid=True, ssl=True)
        server.login('*****@*****.**', '*****')
        server.select_folder('Inbox')

        whenMessages = server.search([u'UNSEEN', u'SUBJECT', u'Son'])

        if whenMessages:

            for msg in whenMessages:

                msginfo = server.fetch([msg], ['BODY[HEADER.FIELDS (FROM)]'])
                fromAddress = str(msginfo[msg].get(
                    'BODY[HEADER.FIELDS (FROM)]')).split('<')[1].split('>')[0]
                msgBody = "Son besleme " + time.strftime(
                    "tarihi %d-%m-%y, saati %X ", time.localtime(latestFeed)
                ) + "\n\nCanavarlarin henuz mamaya ihtiyaclari yok.\n\nBir sonraki besleme " + time.strftime(
                    "tarihi %d-%m-%y, saati %X olacak.",
                    time.localtime(latestFeed + feedInterval))

                sendEmail(fromAddress, "En son besleme bilgileri su sekilde",
                          msgBody)

                server.add_flags(whenMessages, [SEEN])

        feedMessages = server.search([u'UNSEEN', u'SUBJECT', u'Besle'])

        if feedMessages:

            for msg in feedMessages:

                msginfo = server.fetch([msg], ['BODY[HEADER.FIELDS (FROM)]'])
                fromAddress = str(msginfo[msg].get(
                    'BODY[HEADER.FIELDS (FROM)]')).split('<')[1].split('>')[0]
                msgBody = "Bir onceki besleme " + time.strftime(
                    "tarihi %d-%m-%y, saati %X.", time.localtime(latestFeed))
                msgBody = msgBody + "\n\nCanavarlarin henuz mamaya ihtiyaclari yok. \nBir sonraki otomatik besleme " + time.strftime(
                    "tarihi %d-%m-%y, saati %X olacak.",
                    time.localtime(latestFeed + feedInterval)
                ) + "\n\nYine de beslemek istiyorsan bu mesaji konu kismina 'Onay' yazarak cevapla."

                sendEmail(fromAddress, "Besleme icin biraz daha beklemelisin",
                          msgBody)

                server.add_flags(feedMessages, [SEEN])

        confirmMessages = server.search([u'UNSEEN', u'SUBJECT', u'Onay'])

        if confirmMessages:

            for msg in confirmMessages:

                msginfo = server.fetch([msg], ['BODY[HEADER.FIELDS (FROM)]'])
                fromAddress = str(msginfo[msg].get(
                    'BODY[HEADER.FIELDS (FROM)]')).split('<')[1].split('>')[0]
                msgBody = "Bir onceki besleme " + time.strftime(
                    "tarihi %d-%m-%y, saati %X.", time.localtime(latestFeed)
                ) + "\n\nYeni besleme saati " + time.strftime(
                    "%X", time.localtime()
                ) + " olarak kaydedildi." + "\n\nBir sonraki otomatik besleme " + time.strftime(
                    "tarihi %d-%m-%y, saati %X olacak.",
                    time.localtime(latestFeed + feedInterval))
                sendEmail(fromAddress, "Mmm mamalar lezizmis!", msgBody)
                server.add_flags(confirmMessages, [SEEN])

            return True

    return False
예제 #17
0
class MailFetcher(object):

    def __init__(self, host, username, password, ssl, image_folder):
        self.host = host
        self.username = username
        self.password = password
        self.ssl = ssl
        self.image_folder = image_folder
        self.connection = None

    def connect(self):
        self.connection = IMAPClient(self.host, use_uid=True, ssl=self.ssl)
        self.connection.login(self.username, self.password)

    def fetch(self):
        if not exists(self.image_folder):
            makedirs(self.image_folder)
            LOG.info('Created image folder at %r' % self.image_folder)

        self.connection.select_folder('INBOX')
        messages = self.connection.search(['NOT DELETED'])
        response = self.connection.fetch(messages, ['FLAGS', 'BODY'])
        for msgid, data in response.iteritems():
            if SEEN in data['FLAGS']:
                LOG.debug('Skipping already processed message #%r', msgid)
                continue
            body = data['BODY']
            el = []
            extract_elements(body, el)
            fetch_meta = [(i, data) for i, data in enumerate(el)
                          if (data[0], data[1]) in IMAGE_TYPES]
            if fetch_meta:
                self.download(msgid, fetch_meta)

    def in_index(self, md5sum):
        indexfile = join(self.image_folder, 'index')
        if not exists(indexfile):
            LOG.debug('%r does not exist. Assuming first run.', indexfile)
            return False
        hashes = [line.strip() for line in open(indexfile)]
        return md5sum in hashes

    def add_to_index(self, md5sum):
        if not md5sum.strip():
            return
        indexfile = join(self.image_folder, 'index')
        with open(indexfile, 'a+') as fptr:
            fptr.write(md5sum + '\n')

    def download(self, msgid, metadata):
        LOG.debug('Downloading images for mail #%r', msgid)
        has_error = False
        for index, header in metadata:
            LOG.debug('Processing part #%r in mail #%r', index, msgid)
            index = index + 1
            try:
                (major, minor, params, _, _, encoding, size) = header
                element_name = 'BODY[%d]' % index

                response = self.connection.fetch([msgid], [element_name])
                item = response.values()[0]
                bindata = item[element_name].decode(encoding)
                md5sum = md5(bindata).hexdigest()
                if self.in_index(md5sum):
                    LOG.debug('Ignored duplicate file.')
                    continue

                params = dict(zip(params[0::2], params[1::2]))
                filename = params.get('name', '')
                unique_name = 'image_{}_{}_{}'.format(msgid, index, filename)

                fullname = join(self.image_folder, unique_name)

                if not exists(fullname):
                    with open(fullname, 'wb') as fptr:
                        fptr.write(bindata)
                    LOG.info('File written to %r', fullname)
                    self.add_to_index(md5sum)
                else:
                    has_error = True
                    LOG.warn('%r already exists. Not downloaded!' % fullname)
            except:
                LOG.error('Unable to process mail #%r', msgid, exc_info=True)
                has_error = True

        if has_error:
            self.connection.add_flags([msgid], FLAGGED)
        else:
            self.connection.add_flags([msgid], SEEN)
예제 #18
0
class MailFetcher(object):

    def __init__(self, host, username, password, use_ssl, image_folder,
                 force=False):
        self.host = host
        self.username = username
        self.password = password
        self.use_ssl = use_ssl
        self.image_folder = image_folder
        self.connection = None
        self.context = create_default_context()
        self.context.verify_mode = ssl.CERT_NONE
        self.force = force

    def connect(self):
        LOG.debug('Connecting to mail host...')
        self.connection = IMAPClient(self.host, use_uid=True, ssl=self.use_ssl,
                                     ssl_context=self.context)
        LOG.debug('Logging in...')
        self.connection.login(self.username, self.password)

    def fetch(self):
        LOG.debug('Fetching mail...')
        if not exists(self.image_folder):
            makedirs(self.image_folder)
            LOG.info('Created image folder at %r' % self.image_folder)

        self.connection.select_folder('INBOX')
        messages = self.connection.search(['NOT', 'DELETED'])
        response = self.connection.fetch(messages, ['FLAGS', 'BODY'])
        for msgid, data in response.items():
            is_read = SEEN in data[b'FLAGS']
            if is_read and not self.force:
                LOG.debug('Skipping already processed message #%r', msgid)
                continue
            else:
                # Add a "forced" note only if the message would not have been
                # processed otherwise.
                LOG.debug('Processing message #%r%s', msgid,
                          ' (forced override)' if is_read else '')
            body = data[b'BODY']
            el = []
            extract_elements(body, el)
            fetch_meta = [(i, data) for i, data in enumerate(el)
                          if (data[0], data[1]) in IMAGE_TYPES]
            if fetch_meta:
                self.download(msgid, fetch_meta)

    def in_index(self, md5sum):
        indexfile = join(self.image_folder, 'index')
        if not exists(indexfile):
            LOG.debug('%r does not exist. Assuming first run.', indexfile)
            return False
        hashes = [line.strip() for line in open(indexfile)]
        return md5sum in hashes

    def add_to_index(self, md5sum):
        if not md5sum.strip():
            return
        indexfile = join(self.image_folder, 'index')
        with open(indexfile, 'a+') as fptr:
            fptr.write(md5sum + '\n')

    def download(self, msgid, metadata):
        LOG.debug('Downloading images for mail #%r', msgid)
        has_error = False

        parts = flatten_parts(self.connection.fetch(
            [msgid], [b'BODY'])[msgid][b'BODY'][0])
        images = [part for part in parts if part[1][0] == b'image']
        for image_id, header in images:
            try:
                (major, minor, params, _, _, encoding, size) = header
                # Convert "params" into a more conveniend dictionary
                params = dict(zip(params[::2], params[1::2]))
                filename = params[b'name'].decode('ascii', errors='ignore')
                unique_name = 'image_{}_{}_{}'.format(msgid, image_id, filename)
                encoding = encoding.decode('ascii')
                LOG.debug('Processing part #%r in mail #%r', image_id, msgid)
                element_id = ('BODY[%d]' % image_id).encode('ascii')
                response = self.connection.fetch([msgid], [element_id])
                content = response[msgid][element_id]
                if not content:
                    LOG.error('Attachment data was empty for '
                              'message #%r', msgid)
                    has_error = True
                    continue

                if encoding == 'base64':
                    bindata = b64decode(content)
                else:
                    bindata = content.decode(encoding)
                md5sum = md5(bindata).hexdigest()
                if self.in_index(md5sum) and not self.force:
                    LOG.debug('Ignored duplicate file (md5=%s).', md5sum)
                    continue
                elif self.in_index(md5sum) and self.force:
                    LOG.debug('Bypassing index check (force=True)')

                fullname = join(self.image_folder, unique_name)
                if not exists(fullname) or self.force:
                    suffix = ' (forced overwrite)' if exists(fullname) else ''
                    with open(fullname, 'wb') as fptr:
                        fptr.write(bindata)
                    LOG.info('File written to %r%s', fullname, suffix)
                    self.add_to_index(md5sum)
                else:
                    has_error = True
                    LOG.warn('%r already exists. Not downloaded!' % fullname)
            except:
                LOG.error('Unable to process mail #%r', msgid, exc_info=True)
                has_error = True

        if has_error:
            self.connection.add_flags([msgid], FLAGGED)
        else:
            self.connection.add_flags([msgid], SEEN)
예제 #19
0
def getMailContentbyIMAP(mail, password, imap_server):
    conn = IMAPClient(imap_server, ssl=True)
    try:
        conn.login(mail, password)
        print("success connect")
    except conn.Error as e:
        print('Could not log in')
        print(e)
        sys.exit(1)
    else:
        conn.select_folder('INBOX', readonly=False)
        # 利用select_folder()函数选择文件夹,'INBOX'为收件箱

        result = conn.search('UNSEEN')
        conn.add_flags(result, [SEEN])
        msgdict = conn.fetch(result, ['BODY.PEEK[]'])

        # 现在已经把邮件取出来了,下面开始解析邮件
        for message_id, message in msgdict.items():
            e = email.message_from_bytes(message[b'BODY[]'])  # 生成Message类型

            # 由于'From', 'Subject' header有可能有中文,必须把它转化为中文
            subject = email.header.make_header(
                email.header.decode_header(e['SUBJECT']))
            mail_from = email.header.make_header(
                email.header.decode_header(e['From']))

            # 解析邮件正文
            maintype = e.get_content_maintype()
            if maintype == 'multipart':
                for part in e.get_payload():
                    if part.get_content_maintype() == 'text':
                        mail_content = part.get_payload(decode=True).strip()
            elif maintype == 'text':
                mail_content = e.get_payload(decode=True).strip()

            # content转化成中文
            try:
                if mail_content != None:
                    print(mail_content)
                    mail_content = mail_content.decode('utf8')
                    print(mail_content)
            except UnicodeDecodeError as e:
                print('decode error')
                print(e)

            else:
                # print('new message')
                # print('From: ', mail_from)
                # print('Subject: ', subject)
                # print('-' * 10, 'mail content', '-' * 10)
                # print(mail_content.replace('<br>', '\n'))
                # print('-' * 10, 'mail content', '-' * 10)
                if stringMatch(str(subject)) == 'Y' or stringMatch(
                        str(mail_content)) == 'Y':
                    # TODO: 如果msg中有相关信息则通知
                    msg = 'From: ' + str(mail_from) + '\n' + '标题: ' + str(
                        subject) + '\n' + '正文: ' + str(
                            mail_content.replace('<br>', '\n'))
                    # wechatNotify(msg)
                    print("有业务了")
    finally:
        conn.logout()