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)
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
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
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'])
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()
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)
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)
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)
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
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
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()
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'
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)))
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)
#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()
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
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)
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)
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()