使用IMAP在Twisted中获取电子邮件

2022-04-18 00:00:00 python twisted

问题描述

我正在尝试使用Twisted在Gmail帐户上获取电子邮件,至少可以说这是一种痛苦,查看电子邮件是他们清晰的解释和结构(充其量似乎是被黑客攻击在一起的)。我试图抓住附件,但附件看不到任何地方。

我正在使用扭转和修改它的示例IMAP客户端,我正在使用fetchAll(‘1:’)来获取电子邮件,然后获得第一封电子邮件,但我找不到该电子邮件上的电子邮件附件(我在Google中检查了它)。另外,1:和我似乎找不到任何实际解释电子邮件的东西(好像没有人理解它)

那么Stackoverflow,我错过了什么?

代码

#!/usr/bin/env python

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.


"""
    Simple IMAP4 client which displays the subjects of all messages in a
    particular mailbox.
    """

import sys

from twisted.internet import protocol
from twisted.internet import ssl
from twisted.internet import defer
from twisted.internet import stdio
from twisted.mail import imap4
from twisted.protocols import basic
from twisted.python import util
from twisted.python import log



class TrivialPrompter(basic.LineReceiver):
    from os import linesep as delimiter

    promptDeferred = None

    def prompt(self, msg):
        assert self.promptDeferred is None
        self.display(msg)
        self.promptDeferred = defer.Deferred()
        return self.promptDeferred

    def display(self, msg):
        self.transport.write(msg)

    def lineReceived(self, line):
        if self.promptDeferred is None:
            return
        d, self.promptDeferred = self.promptDeferred, None
        d.callback(line)



class SimpleIMAP4Client(imap4.IMAP4Client):
    """
        A client with callbacks for greeting messages from an IMAP server.
        """
    greetDeferred = None

    def serverGreeting(self, caps):
        self.serverCapabilities = caps
        if self.greetDeferred is not None:
            d, self.greetDeferred = self.greetDeferred, None
            d.callback(self)



class SimpleIMAP4ClientFactory(protocol.ClientFactory):
    usedUp = False

    protocol = SimpleIMAP4Client


    def __init__(self, username, onConn):
        self.ctx = ssl.ClientContextFactory()

        self.username = username
        self.onConn = onConn


    def buildProtocol(self, addr):
        """
            Initiate the protocol instance. Since we are building a simple IMAP
            client, we don't bother checking what capabilities the server has. We
            just add all the authenticators twisted.mail has.  Note: Gmail no
            longer uses any of the methods below, it's been using XOAUTH since
            2010.
            """
        assert not self.usedUp
        self.usedUp = True

        p = self.protocol(self.ctx)
        p.factory = self
        p.greetDeferred = self.onConn

        p.registerAuthenticator(imap4.PLAINAuthenticator(self.username))
        p.registerAuthenticator(imap4.LOGINAuthenticator(self.username))
        p.registerAuthenticator(
                                imap4.CramMD5ClientAuthenticator(self.username))

        return p


    def clientConnectionFailed(self, connector, reason):
        d, self.onConn = self.onConn, None
        d.errback(reason)



def cbServerGreeting(proto, username, password):
    """
        Initial callback - invoked after the server sends us its greet message.
        """
    # Hook up stdio
    tp = TrivialPrompter()
    stdio.StandardIO(tp)

    # And make it easily accessible
    proto.prompt = tp.prompt
    proto.display = tp.display

    # Try to authenticate securely
    return proto.authenticate(password
                              ).addCallback(cbAuthentication, proto
                                            ).addErrback(ebAuthentication, proto, username, password
                                                         )


def ebConnection(reason):
    """
        Fallback error-handler. If anything goes wrong, log it and quit.
        """
    log.startLogging(sys.stdout)
    log.err(reason)
    return reason


def cbAuthentication(result, proto):
    """
        Callback after authentication has succeeded.

        Lists a bunch of mailboxes.
        """
    return proto.list("", "*"
                      ).addCallback(cbMailboxList, proto
                                    )


def ebAuthentication(failure, proto, username, password):
    """
        Errback invoked when authentication fails.

        If it failed because no SASL mechanisms match, offer the user the choice
        of logging in insecurely.

        If you are trying to connect to your Gmail account, you will be here!
        """
    failure.trap(imap4.NoSupportedAuthentication)
    return proto.prompt(
                        "No secure authentication available. Login insecurely? (y/N) "
                        ).addCallback(cbInsecureLogin, proto, username, password
                                      )


def cbInsecureLogin(result, proto, username, password):
    """
        Callback for "insecure-login" prompt.
        """
    if result.lower() == "y":
        # If they said yes, do it.
        return proto.login(username, password
                           ).addCallback(cbAuthentication, proto
                                         )
    return defer.fail(Exception("Login failed for security reasons."))


def cbMailboxList(result, proto):
    """
        Callback invoked when a list of mailboxes has been retrieved.
        """
    result = [e[2] for e in result]
    s = '
'.join(['%d. %s' % (n + 1, m) for (n, m) in zip(range(len(result)), result)])
    if not s:
        return defer.fail(Exception("No mailboxes exist on server!"))
    return proto.prompt(s + "
Which mailbox? [1] "
                        ).addCallback(cbPickMailbox, proto, result
                                      )


def cbPickMailbox(result, proto, mboxes):
    """
        When the user selects a mailbox, "examine" it.
        """
    mbox = mboxes[int(result or '1') - 1]
    return proto.examine(mbox
                         ).addCallback(cbExamineMbox, proto
                                       )


def cbExamineMbox(result, proto):
    """
        Callback invoked when examine command completes.

        Retrieve the subject header of every message in the mailbox.
        """
    # FETCH ALL HEADERS? WHERE IS A ONE FOR AN ATTACHMENT
    return proto.fetchAll('1:*').addCallback(cbFetch, proto)


def cbFetch(result, proto):
    """
        Finally, display headers.
        """
    if result:
        keys = result.keys()
        keys.sort()
        k = keys[-1]
        proto.display('%s %s' % (k, result[k]))
    else:
        print "Hey, an empty mailbox!"

    return proto.logout()


def cbClose(result):
    """
        Close the connection when we finish everything.
        """
    from twisted.internet import reactor
    reactor.stop()


def main():
    hostname = raw_input('IMAP4 Server Hostname: ')
    port = raw_input('IMAP4 Server Port (the default is 143, 993 uses SSL): ')
    username = raw_input('IMAP4 Username: ')
    password = util.getPassword('IMAP4 Password: ')

    onConn = defer.Deferred(
    ).addCallback(cbServerGreeting, username, password
                  ).addErrback(ebConnection
                               ).addBoth(cbClose)

    factory = SimpleIMAP4ClientFactory(username, onConn)

    from twisted.internet import reactor
    if port == '993':
        reactor.connectSSL(hostname, int(port), factory, ssl.ClientContextFactory())
    else:
        if not port:
            port = 143
        reactor.connectTCP(hostname, int(port), factory)
    reactor.run()


if __name__ == '__main__':
    main()

解决方案

首先,IMAP4是一个复杂的邮件处理协议(可能没有必要),而Twisted的客户端实现(必然)复杂,无法完全支持该协议。要了解更多内容,您应该考虑花一些时间阅读解释协议的标准:rfc3501和twisted's api的相关部分。

这就是说,看起来您使用的是IMAP4Client.fetchAll(),矛盾的是,它获取了信封&数据、消息的头和元数据。返回电子邮件正文的类似调用实际上也是fetchFull()

相关文章