**
之前项目系统中包含了一个邮箱下载模块,其中对接的是腾讯企业邮箱 ,这个模块前后也维护了不短时间,想写下这篇文章来聊聊具体问题,如果有需要对接腾讯企业邮箱的需求,同时官方给予的开发文档无法满足需求,希望本篇文章可以作为参考。 网络上有不少对邮箱进行开发的文章,但都简略的太多了,远不能处理实际情况。 文章也说明了腾讯企业邮箱开发中的很多坑。
**
先谈谈前提吧
需求是需要从邮箱中下载所有的数据(数据用于分析等等,whatever,总之需要down下来 腾讯企业邮箱给开发者们提供了详细的企业邮开发文档 ,但是存在一个问题,想要按照腾讯企业邮的开发规范来开发邮箱,必须获取企业邮箱的最高权限,即开发文档中称之为CorpID和CorpSecret的密钥(or账户密码?)。如果不是希望搭建公司内部的企业邮箱管理,这一开发规范是存在安全问题的。例如张三需要对其中某一个或多个邮箱进行开发,而却需要得到企业邮的最高权限,这对于企业管理者来说是不能容忍的。至此,这一文章就为处理这一局限而来。
基于什么开发?
腾讯企业邮箱 python(2.7) Linux(and file system imap协议 (RFC 3501 、RFC 822 腾讯提供信息:邮箱服务器信息
让我们开始吧
1. python内置库对于imap协议进行了支持,有一个库名为imaplib
import imaplib
class ESCnn ( imaplib. IMAP4_SSL) :
def __init__ ( self, user, password,
port= 993 , host= "imap.exmail.qq.com" ) :
imaplib. IMAP4_SSL. __init__( self, host, port)
self. email_user = user
self. email_password = password
self. login( user= self. email_user, password= self. email_password)
2. 明确我们需要下载什么?
发件人 收件人 抄送人 邮件时间 正文内容 附件
这是作为普通邮箱使用者,在看到一封邮件时,所能看到的数据信息,但是实际情况有所出入。 在一份实际邮件当中,其实应该有如下信息:
发件人 收件人 抄送人 邮件发件时间 邮件收件时间 正文内容 邮件途径的服务器信息 附件
对比两者的差异在于邮件时间 、邮件途径的服务器信息 ,邮件时间对于普通用户来说用户所看到的其实是发件时间 ,而邮件途径的服务器信息,这一数据对于普通用户而言,又有什么用处呢?所以自然也就没有展示给用户了。
就这些了吗?远不如此,电子邮件的诸多国际协议中对于邮件数据还规定了非常多的规范,例如Message-Header 、RFC2821协议 、RFC2822协议 ……,但!咱们就此打住,这已经足够我们的需求了。如果要展开那得是好几本书才能说清楚的。
我们明确了需要在下载什么,那么是不是可以给邮件进行一个封装类了? 等等,还差一点东西。
我们需要知道邮件对于一个邮箱账户来说是如何管理的。那么多邮件,在服务器内部是如何确定其唯一性的呢? UID : 邮箱服务器通过UID来确定邮件的唯一性,邮箱中有不同的文件夹,例如收件箱、垃圾箱、草稿箱和一些自定义的文件夹,而UID的唯一性是针对这些文件夹来说的,每一个文件夹中都有一个唯一的UID来对邮件进行标记,即便被删除了也不会复用这一UID,当然,这是协议所规定的,至于落实到具体的服务器实现那就是另一回事了,也当然作为邮箱服务器的开发者需要遵循这一标准。记住是UID不是Message-ID,这两者是有区别的,在此不展开说明了,有兴趣可以打开以下链接资料自行了解。 ?Message-ID wiki
?what-is-the-difference-between-imapmessage-getuid-and-message-id-header stackoverflow
那么,至此,我们可以对一个邮件进行数据封装了。
_TimeFormat = "%Y-%m-%d %H:%M:%S"
_DateFormat = _TimeFormat[ : 8 ]
class RecvMail :
def __init__ ( self, ) :
self. _box = None
self. _uid = None
self. _re0( )
def _re0 ( self) :
self. _attachments = [ ]
self. _mail_subject = None
self. _mail_recv_time = None
self. _mail_send_time = None
self. _mail_text = [ ]
self. _mail_sender = None
self. _mail_cc = set ( )
self. _mail_to = set ( )
self. _antetype = None
self. _set_to_invoke = 0
self. _set_cc_invoke = 0
@property
def send_date ( self) :
assert self. _mail_send_time is not None
return self. _mail_send_time. strftime( _DateFormat)
@property
def send_time ( self) :
assert self. _mail_send_time is not None
return self. _mail_send_time. strftime( _TimeFormat)
@property
def recv_date ( self) :
assert self. _mail_recv_time is not None
return self. _mail_recv_time. strftime( _DateFormat)
@property
def recv_time ( self) :
assert self. _mail_recv_time is not None
return self. _mail_recv_time. strftime( _TimeFormat)
@property
def attachements ( self) :
return self. _attachments
@property
def uid ( self)
assert self. _uid is not None
return self. _uid
@property
def box ( self) :
assert self. _box is not None
return self. _box
@property
def antetype ( self) :
return self. _antetype
@property
def sender ( self)
assert self. _mail_sender is not None
return self. _mail_sender
@property
def receivers ( self) :
assert self. _set_to_invoke != 0
return list ( self. _mail_to)
@property
def cc ( self) :
assert self. _set_cc_invoke != 0
return list ( self. _mail_cc)
@property
def set_box ( self, box) :
self. _re0( )
self. _box = box
self. _uid = None
@property
def set_uid ( self, uid) :
self. _re0( )
self. _uid = uid
@property
def set_subject ( self, subject) :
self. _mail_subject = subject
@property
def set_to ( self, to) :
self. _mail_to. update( to)
self. _set_to_invoke += 1
@property
def set_cc ( self, cc)
self. _mail_cc. update( cc)
self. _set_cc_invoke += 1
@property
def set_send_time ( self, datetime)
self. _mail_send_time = datetime
@property
def set_recv_time ( self, datetime) :
self. _mail_recv_time = datetime
def mount ( self, mail_message) :
self. _antetype = mail_message
def append_mailtext ( self, content) :
self. _mail_text. append( content)
def append_attachment ( self, attach, filename) :
self. _attachment. append(
{ "filename" : filename,
"raw" : attach}
)
def get_common_attrs ( self) :
return {
"mail_box" : self. box,
"mail_uid" : self. uid,
"mail_sender" : self. sender,
"mail_subject" : self. subject,
"mail_cc" : self. cc,
"mail_to" : self. receivers,
"mail_send_time" : self. send_time,
"mail_send_date" : self. send_date,
"mail_recv_time" : self. recv_time,
"mail_recv_date" : self. recv_date,
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
3. 我们如何下载?
至此,我们已经有了和邮箱之间的连接,也明确了邮件需要下载什么。 那么到了这里就是如何去下载一封邮件了。
class EMCtrl ( object ) :
def __init__ ( self, ecnn) :
self. _re0( )
self. _ecnn = ecnn
self. _carrier = RecvMail( )
@property
def ECnn ( self) :
return self. _ecnn
def _re0 ( self) :
self. _workfor_box = None
self. _workfor_uid = None
def get_boxes_list ( self) :
status, boxes_list = self. _ecnn. list ( )
if status == 'OK' :
return [ box. split( '"/"' ) [ - 1 ] . replace( '"' , '' ) . strip( )
for box in boxes_list]
else :
return [ ]
def select ( self, mailbox= 'INBOX' , readonly= False ) :
status, _ = self. _ecnn. select( mailbox, readonly)
if status == 'OK' :
self. _workfor_box = mail_box
self. _carrier. set_box = ( mail_box)
else :
pass
def get_uids ( self, box= None ) :
if box is not None :
self. select( box)
status, raw_uids = self. _ecnn. uid( 'SEARCH' , '1:*' )
if status == 'OK' :
return raw_uids[ 0 ] . split( )
else :
pass
def query_mail ( self, uid) :
status, mail_raw_message = self. _ecnn. uid( 'FETCH' , uid, '(RFC822)' )
if not ( ( status== 'OK' ) and
( mail_raw_message is not None ) and
( mail_raw_message[ 0 ] is not None ) ) :
status, mail_raw_message = self. _ecnn. fetch( uid, '(BODY.PEEK[])' )
if not ( ( status== 'OK' ) and
( mail_raw_message is not None ) and
( mail_raw_message[ 0 ] is not None ) ) :
pass
return
email_message = EMCtrl. _convert_raw_message( mail_raw_message[ 0 ] [ 1 ] )
self. _carrier. set_uid( uid)
self. _carrier. mount( email_message)
return self. _carrier. antetype
@staticmethod
def _convert_raw_message ( raw_message) :
return email. message_from_string( raw_message)
def _get_mail_times ( self) :
received_fields = self. _carrier. antetype. get_all( 'received' [ ] )
if received_fields:
mailtime_tuples = [
email. uitils. parsedate(
re. sub( parttern= '[
]' ,
repl= '' ,
string= recv. split( ';' ) [ - 1 ] . strip( ) ,
) [ : 31 ]
)
for recv in received_fields
]
mailtime_tuples. sort( )
else :
mailtime_tuple = [ email. utils. parsedate( self. _carrier. antetype. get( 'date' ) ) ]
def get_mail_from ( self) :
mail_from = self. _carrier. antetype. get( 'from' )
mail_from = parseaddr( mail_from) [ - 1 ] )
self. _carrier. set_sender( mail_from)
return self. _carrier. sender
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
eeeeeeeeeeeeeeee
不想写了,懒癌发作,有时间再更新了,或者有人想要帮助再写
后续还有比较重要的一部分是如何解析正文内容和附件内容,其中正文内容结构体比较杂乱,需要长时间维护才知道会遇到哪些情况……ending
其实腾讯的企业邮真的烂的一批,反正对于邮箱协议的支持很烂很烂,比如不能根据时间请求这一类命令的支持……还有挺多问题的,比如超过多大附件就会出问题等等等等等等等啊啊啊啊
就这样吧,没人看就先放着了