# POP3（Post Office Protocol 3），即邮局协议的第3个版本，
#是电子邮件的第一个离线协议标准。该协议把邮件下载到本地计算机，
#不与服务器同步，缺点是更易丢失邮件或多次下载相同的邮件。
import poplib
# 引入用来解析邮件相关信息的模块
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr
#引入相关时间库
from datetime import datetime
#引入专门处理时间和日期的模块，arrow是一个轻量级Python库
import arrow


# 输入自己163的邮箱地址。
user_email_address = '17279617782@163.com'
# 邮箱的授权码，注意：不是登录密码
user_password = 'RDbwq3yRppmDn9x6'
# 这个是163邮箱的pop3的服务器地址，各个公司的邮箱平台的POP3的服务器地址都是不同的，自己网上查询下即可
#例如：qq邮箱的pop3服务器地址是：pop.qq.com
pop_server_host = 'pop.163.com'
# 邮箱对应的pop服务器的监听端口
#（如果设置POP3的SSL加密方式连接的话，则端口为：995），否则就是端口为110
pop_server_port = 995


def connect_email_by_pop3():
    try:
        # 连接pop服务器。如果没有使用SSL，将POP3_SSL()改成POP3(),且监听端口改为：110即可
        email_server = poplib.POP3_SSL(host=pop_server_host, port=pop_server_port, timeout=10)
        print("连接pop服务器-------正常，开始验证用户邮箱")
    except:
        print("连接pop服务器-------异常，退出")
        exit(1)

    try:
        # 验证用户邮箱
        email_server.user(user_email_address)
        print("用户邮箱验证-------正常，开始验证邮箱授权码")
    except:
        print("用户邮箱验证-------异常，退出")
        exit(1)

    try:
        # 验证邮箱密码是否正确，注意不是登录密码，是授权码
        email_server.pass_(user_password)
        print("邮箱授权码验证-------正常，开始接受邮箱以及附件")
    except:
        print("邮箱授权码验证-------异常，退出")
        exit(1)
    # 开始处理邮箱相关信息
    parse_email_server(email_server)

def parse_email_server(email_server):
    resp, mails, octets = email_server.list()
    num, total_size = email_server.stat()
    print("邮件数量为：" + str(num))
    # mails存储了邮件编号列表，
    index = len(mails)
    # 倒序遍历邮件
    for i in range(index, 0, -1):
        # 倒序遍历邮件，这样取到的第一封就是最新邮件
        resp, lines, octets = email_server.retr(i)
        # lines存储了邮件的原始文本的每一行,
        # 邮件的原始文本:# lines是邮件内容，列表形式使用join拼成一个byte变量
        msg_content = b'\r\n'.join(lines).decode('utf-8')
        # 解析邮件:
        msg = Parser().parsestr(msg_content)
        # 邮件时间,解析时间格式
        mail_datetime = parse_mail_time(msg.get("date"))
        max_mail_time_str = arrow.get(mail_datetime).format("YYYY-MM-DD HH:mm")
        # 这个可以作为根据时间进行邮件的过滤解析，这个把时间写死判断，比较局限，可以在第一个接收时，把最新的邮件接收时间写入到自定义的文件中，
        # 等第二次接收邮件时，再取文件中的时间，进行判断，用于过滤
        # if (max_mail_time_str > "2020-01-01 00:00:00"):
        #     continue
        print("邮件接收时间为：" + max_mail_time_str)
        # 解析邮件具体内容，包括正文，标题，和附件
        parser_content(msg, 0)
    # 别忘记退出
    email_server.quit()


def parser_content(msg, indent):
    if indent == 0:
        # 邮件的From, To, Subject存在于根对象上:
        # 调用解析邮件头部内容的函数
        parser_email_header(msg)
    # 下载附件
    for part in msg.walk():
        file_name = part.get_filename()  # 获取附件名称类型
        if file_name is None:
            continue
        # 说明不是文本，则作为附件处理
        filename = decode_str(file_name)  # 对附件名称进行解码
        data = part.get_payload(decode=True)  # 下载附件
        att_file = open('D:/pyproject/box/qq_mail/attachments' + filename, 'wb')  # 在指定目录下创建文件，注意二进制文件需要用wb模式打开
        att_file.write(data)  # 保存附件
        att_file.close()
        print("附件：" + filename + "保存成功！")

    if (msg.is_multipart()):
        # 如果邮件对象是一个MIMEMultipart,
        # get_payload()返回list，包含所有的子对象:
        parts = msg.get_payload()
        for n, part in enumerate(parts):
            # 递归打印每一个子对象:
            return parser_content(part, indent + 1)
    else:
        # 解析正文
        content_type = msg.get_content_type()
        if content_type == 'text/plain' or content_type == 'text/html':
            # 纯文本或HTML内容:
            content = msg.get_payload(decode=True)
            # 要检测文本编码:
            charset = guess_charset(msg)
            if charset:
                content = content.decode(charset)
                print('%s正文内容为: %s' % ('  ' * indent, content))


# 解析邮件
def parser_email_header(msg):
    # 解析邮件标题
    subject = msg['Subject']
    value, charset = decode_header(subject)[0]
    if charset:
        value = value.decode(charset)
    print('邮件标题： {0}'.format(value))

    # 解析发送人信息
    hdr, addr = parseaddr(msg['From'])
    # name 发送人邮箱名称， addr 发送人邮箱地址
    name, charset = decode_header(hdr)[0]
    if charset:
        name = name.decode(charset)
    print('发送人邮箱名称: {0}，发送人邮箱地址: {1}'.format(name, addr))

    # 解析接收人信息
    hdr, addr = parseaddr(msg['To'])
    # name 发送人邮箱名称， addr 发送人邮箱地址
    name, charset = decode_header(hdr)[0]
    if charset:
        name = name.decode(charset)
    print('接收人邮箱名称: {0}，接收人邮箱地址: {1}'.format(name, addr))


# 解码
def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value


# 猜测字符编码
def guess_charset(msg):
    # 先从msg对象获取编码:
    charset = msg.get_charset()
    if charset is None:
        # 如果获取不到，再从Content-Type字段获取:
        content_type = msg.get('Content-Type', '').lower()
        for item in content_type.split(';'):
            item = item.strip()
            if item.startswith('charset'):
                charset = item.split('=')[1]
                break
    return charset


# 邮件时间处理函数
def parse_mail_time(mail_datetime):
    GMT_FORMAT = "%a, %d %b %Y %H:%M:%S"
    GMT_FORMAT2 = "%d %b %Y %H:%M:%S"
    index = mail_datetime.find(' +0')
    if index > 0:
        mail_datetime = mail_datetime[:index]  # 去掉+0800
    formats = [GMT_FORMAT, GMT_FORMAT2]
    for ft in formats:
        try:
            mail_datetime = datetime.strptime(mail_datetime, ft)
            return mail_datetime
        except:
            pass
    raise Exception("邮件时间格式解析错误")

# 比较规范写法，象征着程序入口
if __name__ == "__main__":
    connect_email_by_pop3()
