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

Zim*_*m3r 0 python twisted

我正在尝试使用 Twisted 在 gmail 帐户上获取电子邮件,至少可以说这很痛苦,查看电子邮件是他们清晰的解释和结构(它似乎充其量被黑客入侵)。我正在尝试抓取附件,但附件不在视线之内。

我正在使用来自扭曲和修改的示例 IMAP 客户端,我使用 fetchAll('1: ') 来获取电子邮件,然后获取第一封电子邮件,但我找不到该电子邮件中的电子邮件附件(我检查了它在谷歌中有)。还有 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 = '\n'.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 + "\nWhich 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()
Run Code Online (Sandbox Code Playgroud)

Sin*_*ion 5

首先,imap4 是一个(也许是不必要的)复杂的邮件处理协议,而 Twisted 的客户端实现(必然)复杂到完全支持该协议。为了在任何地方获得更多信息,您应该考虑花一些时间阅读解释协议的标准:rfc3501扭曲的 api的相关部分。

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