Рубрики
Без рубрики

imaplib – Клиентская библиотека IMAP4

Автор оригинала: Doug Hellmann.

Цель:

Клиентская библиотека для обмена данными по протоколу IMAP4.

imaplib реализует клиент для связи с серверами протокола доступа к сообщениям Интернета (IMAP) версии 4. Протокол IMAP определяет набор команд, отправляемых на сервер, и ответов, возвращаемых клиенту. Большинство команд доступны как методы объекта IMAP4 , используемые для связи с сервером.

В этих примерах обсуждается часть протокола IMAP, но они никоим образом не полны. См. RFC 3501 для получения полной информации.

Вариации

Существует три клиентских класса для связи с серверами с использованием различных механизмов. Первый, IMAP4 , использует сокеты для открытого текста; IMAP4_SSL использует шифрованную связь через сокеты SSL; а IMAP4_stream использует стандартный ввод и стандартный вывод внешней команды. Все примеры здесь будут использовать IMAP4_SSL , но API для других классов аналогичны.

Подключение к серверу

Есть два шага для установки соединения с сервером IMAP. Сначала установите само соединение сокета. Во-вторых, авторизуйтесь как пользователь с учетной записью на сервере. В следующем примере кода будет считываться информация о сервере и пользователе из файла конфигурации.

imaplib_connect.py

import imaplib
import configparser
import os


def open_connection(verboseFalse):
    # Read the config file
    config  configparser.ConfigParser()
    config.read([os.path.expanduser('~/.pymotw')])

    # Connect to the server
    hostname  config.get('server', 'hostname')
    if verbose:
        print('Connecting to', hostname)
    connection  imaplib.IMAP4_SSL(hostname)

    # Login to our account
    username  config.get('account', 'username')
    password  config.get('account', 'password')
    if verbose:
        print('Logging in as', username)
    connection.login(username, password)
    return connection


if __name__  '__main__':
    with open_connection(verboseTrue) as c:
        print(c)

При запуске open_connection () считывает информацию о конфигурации из файла в домашнем каталоге пользователя, затем открывает соединение IMAP4_SSL и выполняет аутентификацию.

$ python3 imaplib_connect.py

Connecting to pymotw.hellfly.net
Logging in as example

В других примерах в этом разделе этот модуль используется повторно, чтобы избежать дублирования кода.

Ошибка аутентификации

Если соединение установлено, но аутентификация не удалась, возникает исключение.

imaplib_connect_fail.py

import imaplib
import configparser
import os

# Read the config file
config  configparser.ConfigParser()
config.read([os.path.expanduser('~/.pymotw')])

# Connect to the server
hostname  config.get('server', 'hostname')
print('Connecting to', hostname)
connection  imaplib.IMAP4_SSL(hostname)

# Login to our account
username  config.get('account', 'username')
password  'this_is_the_wrong_password'
print('Logging in as', username)
try:
    connection.login(username, password)
except Exception as err:
    print('ERROR:', err)

В этом примере для вызова исключения специально используется неправильный пароль.

$ python3 imaplib_connect_fail.py

Connecting to pymotw.hellfly.net
Logging in as example
ERROR: b'[AUTHENTICATIONFAILED] Authentication failed.'

Пример конфигурации

В примере учетной записи есть несколько почтовых ящиков в иерархии:

  • INBOX
  • Удаленные сообщения
  • Архив
  • Пример
      2016
  • 2016 г.

Одно непрочитанное сообщение находится в папке INBOX и одно прочитанное сообщение в Example/2016 .

Список почтовых ящиков

Чтобы получить почтовые ящики, доступные для учетной записи, используйте метод list () .

imaplib_list.py

import imaplib
from pprint import pprint
from imaplib_connect import open_connection

with open_connection() as c:
    typ, data  c.list()
    print('Response code:', typ)
    print('Response:')
    pprint(data)

Возвращаемое значение – это кортеж , содержащий код ответа и данные, возвращаемые сервером. Код ответа – OK , если не было ошибки. Данные для list () – это последовательность строк, содержащих флаги , разделитель иерархии и имя почтового ящика для каждый почтовый ящик.

$ python3 imaplib_list.py

Response code: OK
Response:
[b'(\\HasChildren) "." Example',
 b'(\\HasNoChildren) "." Example.2016',
 b'(\\HasNoChildren) "." Archive',
 b'(\\HasNoChildren) "." "Deleted Messages"',
 b'(\\HasNoChildren) "." INBOX']

Каждую строку ответа можно разделить на три части с помощью re или csv (см. Сценарий резервного копирования IMAP в ссылках в конце этого раздела для примера с использованием csv ).

imaplib_list_parse.py

import imaplib
import re

from imaplib_connect import open_connection

list_response_pattern  re.compile(
    r'\((?P.*?)\) "(?P.*)" (?P.*)'
)


def parse_list_response(line):
    match  list_response_pattern.match(line.decode('utf-8'))
    flags, delimiter, mailbox_name  match.groups()
    mailbox_name  mailbox_name.strip('"')
    return (flags, delimiter, mailbox_name)


with open_connection() as c:
    typ, data  c.list()
print('Response code:', typ)

for line in data:
    print('Server response:', line)
    flags, delimiter, mailbox_name  parse_list_response(line)
    print('Parsed response:', (flags, delimiter, mailbox_name))

Сервер цитирует имя почтового ящика, если оно включает в себя пробелы, но эти кавычки необходимо удалить, чтобы использовать имя почтового ящика в других вызовах сервера позже.

$ python3 imaplib_list_parse.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Parsed response: ('\\HasChildren', '.', 'Example')
Server response: b'(\\HasNoChildren) "." Example.2016'
Parsed response: ('\\HasNoChildren', '.', 'Example.2016')
Server response: b'(\\HasNoChildren) "." Archive'
Parsed response: ('\\HasNoChildren', '.', 'Archive')
Server response: b'(\\HasNoChildren) "." "Deleted Messages"'
Parsed response: ('\\HasNoChildren', '.', 'Deleted Messages')
Server response: b'(\\HasNoChildren) "." INBOX'
Parsed response: ('\\HasNoChildren', '.', 'INBOX')

list () принимает аргументы для указания почтовых ящиков в части иерархии. Например, чтобы перечислить подпапки Example , передайте "Example" в качестве аргумента directory .

imaplib_list_subfolders.py

import imaplib

from imaplib_connect import open_connection

with open_connection() as c:
    typ, data  c.list(directory'Example')

print('Response code:', typ)

for line in data:
    print('Server response:', line)

Родитель и подпапка возвращаются.

$ python3 imaplib_list_subfolders.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Server response: b'(\\HasNoChildren) "." Example.2016'

В качестве альтернативы, чтобы перечислить папки, соответствующие шаблону, передайте аргумент pattern .

imaplib_list_pattern.py

import imaplib

from imaplib_connect import open_connection

with open_connection() as c:
    typ, data  c.list(pattern'*Example*')

print('Response code:', typ)

for line in data:
    print('Server response:', line)

В этом случае в ответ включаются и Example , и Example.2016 .

$ python3 imaplib_list_pattern.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Server response: b'(\\HasNoChildren) "." Example.2016'

Статус почтового ящика

Используйте status () , чтобы запросить агрегированную информацию о содержимом. в таблице ниже перечислены условия состояния, определенные стандартом.

Условия состояния почтового ящика IMAP 4

Условие

Смысл

СООБЩЕНИЯ

Количество сообщений в почтовом ящике.

НЕДАВНИЙ

Количество сообщений с

\Недавний

установлен флаг.

UIDNEXT

Следующее значение уникального идентификатора почтового ящика.

UIDVALIDITY

Значение достоверности уникального идентификатора почтового ящика.

НЕВИДИМЫЙ

Количество сообщений, не имеющих

\Видимый

установлен флаг.

Условия состояния должны быть отформатированы как строка, разделенная пробелами, заключенная в круглые скобки, кодировка для «списка» в спецификации IMAP4. Имя почтового ящика заключено в " в случае, если какое-либо из имен включает пробелы или другие символы, которые могут быть выброшены анализатором.

imaplib_status.py

import imaplib
import re

from imaplib_connect import open_connection
from imaplib_list_parse import parse_list_response

with open_connection() as c:
    typ, data  c.list()
    for line in data:
        flags, delimiter, mailbox  parse_list_response(line)
        print('Mailbox:', mailbox)
        status  c.status(
            '"{}"'.format(mailbox),
            '(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)',
        )
        print(status)

Возвращаемое значение – обычный кортеж , содержащий код ответа и список информации с сервера. В этом случае список содержит одну строку, отформатированную с именем почтового ящика в кавычках, а затем условиями статуса и значениями в круглых скобках.

$ python3 imaplib_status.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Parsed response: ('\\HasChildren', '.', 'Example')
Server response: b'(\\HasNoChildren) "." Example.2016'
Parsed response: ('\\HasNoChildren', '.', 'Example.2016')
Server response: b'(\\HasNoChildren) "." Archive'
Parsed response: ('\\HasNoChildren', '.', 'Archive')
Server response: b'(\\HasNoChildren) "." "Deleted Messages"'
Parsed response: ('\\HasNoChildren', '.', 'Deleted Messages')
Server response: b'(\\HasNoChildren) "." INBOX'
Parsed response: ('\\HasNoChildren', '.', 'INBOX')
Mailbox: Example
('OK', [b'Example (MESSAGES 0 RECENT 0 UIDNEXT 2 UIDVALIDITY 145
7297771 UNSEEN 0)'])
Mailbox: Example.2016
('OK', [b'Example.2016 (MESSAGES 1 RECENT 0 UIDNEXT 3 UIDVALIDIT
Y 1457297772 UNSEEN 0)'])
Mailbox: Archive
('OK', [b'Archive (MESSAGES 0 RECENT 0 UIDNEXT 1 UIDVALIDITY 145
7297770 UNSEEN 0)'])
Mailbox: Deleted Messages
('OK', [b'"Deleted Messages" (MESSAGES 3 RECENT 0 UIDNEXT 4 UIDV
ALIDITY 1457297773 UNSEEN 0)'])
Mailbox: INBOX
('OK', [b'INBOX (MESSAGES 2 RECENT 0 UIDNEXT 6 UIDVALIDITY 14572
97769 UNSEEN 1)'])

Выбор почтового ящика

После аутентификации клиента основным режимом работы является выбор почтового ящика, а затем опрос сервера относительно сообщений в почтовом ящике. Соединение имеет состояние, поэтому после выбора почтового ящика все команды работают с сообщениями в этом почтовом ящике, пока не будет выбран новый почтовый ящик.

imaplib_select.py

import imaplib
import imaplib_connect

with imaplib_connect.open_connection() as c:
    typ, data  c.select('INBOX')
    print(typ, data)
    num_msgs  int(data[0])
    print('There are {} messages in INBOX'.format(num_msgs))

Данные ответа содержат общее количество сообщений в почтовом ящике.

$ python3 imaplib_select.py

OK [b'1']
There are 1 messages in INBOX

Если указан недопустимый почтовый ящик, код ответа – NO .

imaplib_select_invalid.py

import imaplib
import imaplib_connect

with imaplib_connect.open_connection() as c:
    typ, data  c.select('Does-Not-Exist')
    print(typ, data)

Данные содержат сообщение об ошибке с описанием проблемы.

$ python3 imaplib_select_invalid.py

NO [b"Mailbox doesn't exist: Does-Not-Exist"]

Поиск сообщений

После выбора почтового ящика используйте search () для получения идентификаторов сообщений в почтовом ящике.

imaplib_search_all.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    typ, mbox_data  c.list()
    for line in mbox_data:
        flags, delimiter, mbox_name  parse_list_response(line)
        c.select('"{}"'.format(mbox_name), readonlyTrue)
        typ, msg_ids  c.search(None, 'ALL')
        print(mbox_name, typ, msg_ids)

Идентификаторы сообщений назначаются сервером и зависят от реализации. Протокол IMAP4 делает различие между последовательными идентификаторами сообщений в определенный момент времени во время транзакции и идентификаторами UID для сообщений, но не все серверы реализуют и то, и другое.

$ python3 imaplib_search_all.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Parsed response: ('\\HasChildren', '.', 'Example')
Server response: b'(\\HasNoChildren) "." Example.2016'
Parsed response: ('\\HasNoChildren', '.', 'Example.2016')
Server response: b'(\\HasNoChildren) "." Archive'
Parsed response: ('\\HasNoChildren', '.', 'Archive')
Server response: b'(\\HasNoChildren) "." "Deleted Messages"'
Parsed response: ('\\HasNoChildren', '.', 'Deleted Messages')
Server response: b'(\\HasNoChildren) "." INBOX'
Parsed response: ('\\HasNoChildren', '.', 'INBOX')
Example OK [b'']
Example.2016 OK [b'1']
Archive OK [b'']
Deleted Messages OK [b'']
INBOX OK [b'1']

В этом случае, INBOX и Example.2016 имеют разные сообщения с идентификатором 1 . Остальные почтовые ящики пусты.

Критерий поиска

Можно использовать множество других критериев поиска, включая просмотр дат сообщения, флагов и других заголовков. См. Раздел 6.4.4. RFC 3501 для получения полной информации.

Чтобы искать сообщения с 'Example message 2' в теме, критерии поиска должны быть построены следующим образом:

(SUBJECT "Example message 2")

Этот пример находит все сообщения с заголовком «Пример сообщения 2» во всех почтовых ящиках:

imaplib_search_subject.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    typ, mbox_data  c.list()
    for line in mbox_data:
        flags, delimiter, mbox_name  parse_list_response(line)
        c.select('"{}"'.format(mbox_name), readonlyTrue)
        typ, msg_ids  c.search(
            None,
            '(SUBJECT "Example message 2")',
        )
        print(mbox_name, typ, msg_ids)

В учетной записи есть только одно такое сообщение, и оно находится в INBOX .

$ python3 imaplib_search_subject.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Parsed response: ('\\HasChildren', '.', 'Example')
Server response: b'(\\HasNoChildren) "." Example.2016'
Parsed response: ('\\HasNoChildren', '.', 'Example.2016')
Server response: b'(\\HasNoChildren) "." Archive'
Parsed response: ('\\HasNoChildren', '.', 'Archive')
Server response: b'(\\HasNoChildren) "." "Deleted Messages"'
Parsed response: ('\\HasNoChildren', '.', 'Deleted Messages')
Server response: b'(\\HasNoChildren) "." INBOX'
Parsed response: ('\\HasNoChildren', '.', 'INBOX')
Example OK [b'']
Example.2016 OK [b'']
Archive OK [b'']
Deleted Messages OK [b'']
INBOX OK [b'1']

Критерии поиска также можно комбинировать.

imaplib_search_from.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    typ, mbox_data  c.list()
    for line in mbox_data:
        flags, delimiter, mbox_name  parse_list_response(line)
        c.select('"{}"'.format(mbox_name), readonlyTrue)
        typ, msg_ids  c.search(
            None,
            '(FROM "Doug" SUBJECT "Example message 2")',
        )
        print(mbox_name, typ, msg_ids)

Критерии комбинируются с логической операцией и .

$ python3 imaplib_search_from.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Parsed response: ('\\HasChildren', '.', 'Example')
Server response: b'(\\HasNoChildren) "." Example.2016'
Parsed response: ('\\HasNoChildren', '.', 'Example.2016')
Server response: b'(\\HasNoChildren) "." Archive'
Parsed response: ('\\HasNoChildren', '.', 'Archive')
Server response: b'(\\HasNoChildren) "." "Deleted Messages"'
Parsed response: ('\\HasNoChildren', '.', 'Deleted Messages')
Server response: b'(\\HasNoChildren) "." INBOX'
Parsed response: ('\\HasNoChildren', '.', 'INBOX')
Example OK [b'']
Example.2016 OK [b'']
Archive OK [b'']
Deleted Messages OK [b'']
INBOX OK [b'1']

Получение сообщений

Идентификаторы, возвращаемые функцией search () , используются для извлечения содержимого или частичного содержимого сообщений для дальнейшей обработки с помощью метода fetch () . Он принимает два аргумента: идентификаторы сообщений для извлечения и части сообщения для извлечения.

Аргумент message_ids представляет собой список идентификаторов, разделенных запятыми (например, "1" , "1,2" ) или диапазонов идентификаторов (например, 1: 2 ). Аргумент message_parts – это список IMAP с именами сегментов сообщения. Как и в случае с критериями поиска для search () , протокол IMAP определяет именованные сегменты сообщения, поэтому клиенты могут эффективно извлекать только те части сообщения, которые им действительно нужны. Например, чтобы получить заголовки сообщений в почтовом ящике, используйте fetch () с аргументом BODY.PEEK [HEADER] .

Примечание

Другой способ получить заголовки – это BODY [HEADERS] , но у этой формы есть побочный эффект, заключающийся в неявной пометке сообщения как прочитанного, что во многих случаях нежелательно.

imaplib_fetch_raw.py

import imaplib
import pprint
import imaplib_connect

imaplib.Debug  4
with imaplib_connect.open_connection() as c:
    c.select('INBOX', readonlyTrue)
    typ, msg_data  c.fetch('1', '(BODY.PEEK[HEADER] FLAGS)')
    pprint.pprint(msg_data)

Возвращаемое значение fetch () было частично проанализировано, поэтому работать с ним несколько сложнее, чем с возвращаемым значением list () . Включение отладки показывает полное взаимодействие между клиентом и сервером, чтобы понять, почему это так.

$ python3 imaplib_fetch_raw.py

  19:40.68 imaplib version 2.58
  19:40.68 new IMAP4 connection, tag=b'IIEN'
  19:40.70 < b'* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN
-REFERRALS ID ENABLE IDLE
  19:40.70 > b'IIEN0 CAPABILITY'
  19:40.73 < b'* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REF
ERRALS ID ENABLE IDLE
  19:40.73 < b'IIEN0 OK Pre-login capabilities listed, post-logi
n capabilities have more.'
  19:40.73 CAPABILITIES: ('IMAP4REV1', 'LITERAL+', 'SASL-IR', 'L
OGIN-REFERRALS', 'ID', 'ENABLE', 'IDLE',)
  19:40.73 > b'IIEN1 LOGIN example "TMFw00fpymotw"'
  19:40.79 < b'* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REF
ERRALS ID ENABLE IDLE SORT
=REFS
ELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED
TORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN
STATUS SPECIAL-USE BINARY MOVE'
  19:40.79 < b'IIEN1 OK Logged in'
  19:40.79 > b'IIEN2 EXAMINE INBOX'
  19:40.82 < b'* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\
Draft)'
  19:40.82 < b'* OK [PERMANENTFLAGS ()] Read-only mailbox.'
  19:40.82 < b'* 2 EXISTS'
  19:40.82 < b'* 0 RECENT'
  19:40.82 < b'* OK [UNSEEN 1] First unseen.'
  19:40.82 < b'* OK [UIDVALIDITY 1457297769] UIDs valid'
  19:40.82 < b'* OK [UIDNEXT 6] Predicted next UID'
  19:40.82 < b'* OK [HIGHESTMODSEQ 20] Highest'
  19:40.82 < b'IIEN2 OK [READ-ONLY] Examine completed (0.000 sec
s).'
  19:40.82 > b'IIEN3 FETCH 1 (BODY.PEEK[HEADER] FLAGS)'
  19:40.86 < b'* 1 FETCH (FLAGS () BODY[HEADER] {3108}'
  19:40.86 read literal size 3108
  19:40.86 < b')'
  19:40.89 < b'IIEN3 OK Fetch completed.'
  19:40.89 > b'IIEN4 LOGOUT'
  19:40.93 < b'* BYE Logging out'
  19:40.93 BYE response: b'Logging out'
[(b'1 (FLAGS () BODY[HEADER] {3108}',
  b'Return-Path: \r\nReceived: from compu
te4.internal ('
  b'compute4.nyi.internal [10.202.2.44])\r\n\t by sloti26t01 (Cy
rus 3.0.0-beta1'
  b'-git-fastmail-12410) with LMTPA;\r\n\t Sun, 06 Mar 2016 16:1
6:03 -0500\r'
  b'\nX-Sieve: CMU Sieve 2.4\r\nX-Spam-known-sender: yes, fadd1c
f2-dc3a-4984-a0'
 ,\r\n  ea349ad0-9299-47b5-b632-6ff1e39
  b'llfly"\r\nX-Spam-score: 0.0\r\nX-Spam-hits: ALL_TRUSTED -1,
BAYES_00 -1.'
  b'9, LANGUAGES unknown, BAYES_USED global,\r\n  SA_VERSION 3.3
.2\r\nX-Spam'
  b"-source:,,, FromHead,\r\n "
  b"\r\nX-Spam-charsets:\r\nX-Re
solved-to: d"
  b'oughellmann@fastmail.fm\r\nX-Delivered-to: doug@doughellmann
.com\r\nX-Ma'
  b'il-from: doug@doughellmann.com\r\nReceived: from mx5 ([10.20
2.2.204])\r'
  b'\n  by compute4.internal (LMTPProxy); Sun, 06 Mar 2016 16:16
:03 -0500\r\nRe'
  b'ceived: from mx5.nyi.internal (localhost [127.0.0.1])\r\n\tb
y mx5.nyi.inter'
  b'nal (Postfix) with ESMTP id 47CBA280DB3\r\n\tfor ; S'
  b'un,  6 Mar 2016 16:16:03 -0500 (EST)\r\nReceived: from mx5.n
yi.internal (l'
  b'ocalhost [127.0.0.1])\r\n    by mx5.nyi.internal (Authentica
tion Milter) w'
  b'ith ESMTP\r\n    id A717886846E.30BA4280D81;\r\n    Sun, 6 M
ar 2016 16:1'
  b'6:03 -0500\r\nAuthentication-Results: mx5.nyi.internal;\r\n
  
  b' (1024-bit rsa key)
essagingengi'
  b'ne.com   
: from mailo'
  b'ut.nyi.internal (gateway1.nyi.internal [10.202.2.221])\r\n\t
(using TLSv1.2 '
  b'with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\r\n\
t(No client cer'
  b'tificate requested)\r\n\tby mx5.nyi.internal (Postfix) with
ESMTPS id 30BA4'
  b'280D81\r\n\tfor ; Sun,  6 Mar 2016 16
:16:03 -0500 (E'
  b'ST)\r\nReceived: from compute2.internal (compute2.nyi.intern
al [10.202.2.4'
  b'2])\r\n\tby mailout.nyi.internal (Postfix) with ESMTP id 174
0420D0A\r\n\tf'
  b'or ; Sun,  6 Mar 2016 16:16:03 -0500
(EST)\r\nRecei'
  b'ved: from frontend2 ([10.202.2.161])\r\n  by compute2.intern
al (MEProxy); '
  b'Sun, 06 Mar 2016 16:16:03 -0500\r\nDKIM-Signature:
a-sha1;
  b'xed/relaxed;
-encoding:conte'
  b'nt-type\r\n\t:date:from:message-id:mime-version:subject:to:x
-sasl-enc\r\n'
  b'\t:x-sasl-enc;
=Jrsm+\r\n\t'
  b'pCovRIoQIRyp8Fl0L6JHOI8sbZy2obx7O28JF2iTlTWmX33Rhlq9403XRklw
N3JA\r\n\t7KSPq'
  b'MTp30Qdx6yIUaADwQqlO+QMuQq/QxBHdjeebmdhgVfjhqxrzTbSMww/ZNhL\
r\n\tYwv/QM/oDH'
 
RWQFivGymJb8pa'
  b'4G9JGcb7k4xKn+I 1457298962\r\nReceived: from [192.168.1.14]
(75-137-1-34.d'
  b'hcp.nwnn.ga.charter.com [75.137.1.34])\r\n\tby mail.messagin
gengine.com (Po'
  b'stfix) with ESMTPA id C0B366801CD\r\n\tfor ; Sun,  6'
  b' Mar 2016 16:16:02 -0500 (EST)\r\nFrom: Doug Hellmann \r\nContent-Type: text/plain;
-Transfer-En'
  b'coding: 7bit\r\nSubject: PyMOTW Example message 2\r\nMessage
-Id: <00ABCD'
  b'46-DADA-4912-A451-D27165BC3A2F@doughellmann.com>\r\nDate: Su
n, 6 Mar 2016 '
  b'16:16:02 -0500\r\nTo: Doug Hellmann \
r\nMime-Vers'
  b'ion: 1.0 (Mac OS X Mail 9.2 \\(3112\\))\r\nX-Mailer: Apple M
ail (2.3112)'
  b'\r\n\r\n'),
 b')']

Ответ от команды FETCH начинается с флагов, затем указывает, что имеется 595 байт данных заголовка. Клиент создает кортеж

imaplib_fetch_separately.py

import imaplib
import pprint
import imaplib_connect

with imaplib_connect.open_connection() as c:
    c.select('INBOX', readonlyTrue)

    print('HEADER:')
    typ, msg_data  c.fetch('1', '(BODY.PEEK[HEADER])')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            print(response_part[1])

    print('\nBODY TEXT:')
    typ, msg_data  c.fetch('1', '(BODY.PEEK[TEXT])')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            print(response_part[1])

    print('\nFLAGS:')
    typ, msg_data  c.fetch('1', '(FLAGS)')
    for response_part in msg_data:
        print(response_part)
        print(imaplib.ParseFlags(response_part))

Получение значений по отдельности дает дополнительное преимущество, упрощая использование ParseFlags () для анализа флагов из ответа.

$ python3 imaplib_fetch_separately.py

HEADER:
b'Return-Path: \r\nReceived: from compute
4.internal (compute4.nyi.internal [10.202.2.44])\r\n\t by sloti2
6t01 (Cyrus 3.0.0-beta1-git-fastmail-12410) with LMTPA;\r\n\t Su
n, 06 Mar 2016 16:16:03 -0500\r\nX-Sieve: CMU Sieve 2.4\r\nX-Spa
m-known-sender: yes,
,\r\n \r\nX-
Spam-score: 0.0\r\nX-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, L
ANGUAGES unknown, BAYES_USED global,\r\n  SA_VERSION 3.3.2\r\nX-
Spam-source: IP=\'127.0.0.1\', Host=\'unk\', Country=\'unk\', Fr
omHeader=\'com\',\r\n  MailFrom=\'com\'\r\nX-Spam-charsets: plai
n=\'us-ascii\'\r\nX-Resolved-to: doughellmann@fastmail.fm\r\nX-D
elivered-to: doug@doughellmann.com\r\nX-Mail-from: doug@doughell
mann.com\r\nReceived: from mx5 ([10.202.2.204])\r\n  by compute4
.internal (LMTPProxy); Sun, 06 Mar 2016 16:16:03 -0500\r\nReceiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])\r\n\tby mx5.ny
i.internal (Postfix) with ESMTP id 47CBA280DB3\r\n\tfor ; Sun,  6 Mar 2016 16:16:03 -0500 (EST)\r\nReceiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])\r\n    by mx5.
nyi.internal (Authentication Milter) with ESMTP\r\n    id A71788
6846E.30BA4280D81;\r\n    Sun, 6 Mar 2016 16:16:03 -0500\r\nAuth
entication-Results: mx5.nyi.internal;\r\n   
 rsa key)
.com   
 mailout.nyi.internal (gateway1.nyi.internal [10.202.2.221])\r\n
\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/25
6 bits))\r\n\t(No client certificate requested)\r\n\tby mx5.nyi.
internal (Postfix) with ESMTPS id 30BA4280D81\r\n\tfor ; Sun,  6 Mar 2016 16:16:03 -0500 (EST)\r\nReceive
d: from compute2.internal (compute2.nyi.internal [10.202.2.42])\
r\n\tby mailout.nyi.internal (Postfix) with ESMTP id 1740420D0A\
r\n\tfor ; Sun,  6 Mar 2016 16:16:03 -050
0 (EST)\r\nReceived: from frontend2 ([10.202.2.161])\r\n  by com
pute2.internal (MEProxy); Sun, 06 Mar 2016 16:16:03 -0500\r\nDKI
M-Signature:
ngengine.com;
e:from:message-id:mime-version:subject:to:x-sasl-enc\r\n\t:x-sas
l-enc;
pCovRIoQIRyp8Fl0L6JHOI8sbZy2obx7O28JF2iTlTWmX33Rhlq9403XRklwN3JA
\r\n\t7KSPqMTp30Qdx6yIUaADwQqlO+QMuQq/QxBHdjeebmdhgVfjhqxrzTbSMw
c: 8ZJ+4ZRE8AGPzdLRWQFivGymJb8pa4G9JGcb7k4xKn+I 1457298962\r\nRe
ceived: from [192.168.1.14] (75-137-1-34.dhcp.nwnn.ga.charter.co
m [75.137.1.34])\r\n\tby mail.messagingengine.com (Postfix) with
 ESMTPA id C0B366801CD\r\n\tfor ; Sun,  6
 Mar 2016 16:16:02 -0500 (EST)\r\nFrom: Doug Hellmann \r\nContent-Type: text/plain;
ontent-Transfer-Encoding: 7bit\r\nSubject: PyMOTW Example messag
e 2\r\nMessage-Id: <00ABCD46-DADA-4912-A451-D27165BC3A2F@doughel
lmann.com>\r\nDate: Sun, 6 Mar 2016 16:16:02 -0500\r\nTo: Doug H
ellmann \r\nMime-Version: 1.0 (Mac OS X M
ail 9.2 \\(3112\\))\r\nX-Mailer: Apple Mail (2.3112)\r\n\r\n'

BODY TEXT:
b'This is the second example message.\r\n'

FLAGS:
b'1 (FLAGS ())'
()

Целые сообщения

Как было показано ранее, клиент может запрашивать у сервера отдельные части сообщения отдельно. Также возможно получить все сообщение как сообщение электронной почты в формате RFC 822 и проанализировать его с помощью классов из модуля email .

imaplib_fetch_rfc822.py

import imaplib
import email
import email.parser

import imaplib_connect


with imaplib_connect.open_connection() as c:
    c.select('INBOX', readonlyTrue)

    typ, msg_data  c.fetch('1', '(RFC822)')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            email_parser  email.parser.BytesFeedParser()
            email_parser.feed(response_part[1])
            msg  email_parser.close()
            for header in ['subject', 'to', 'from']:
                print('{:^8}: {}'.format(
                    header.upper(), msg[header]))

Анализатор в модуле email упрощает доступ к сообщениям и управление ими. В этом примере печатается лишь несколько заголовков для каждого сообщения.

$ python3 imaplib_fetch_rfc822.py

SUBJECT : PyMOTW Example message 2
   TO   : Doug Hellmann 
  FROM  : Doug Hellmann 

Отправка сообщений

Чтобы добавить новое сообщение в почтовый ящик, создайте экземпляр Message и передайте его методу append () вместе с меткой времени для сообщения.

imaplib_append.py

import imaplib
import time
import email.message
import imaplib_connect

new_message  email.message.Message()
new_message.set_unixfrom('pymotw')
new_message['Subject']  'subject goes here'
new_message['From']  'pymotw@example.com'
new_message['To']  'example@example.com'
new_message.set_payload('This is the body of the message.\n')

print(new_message)

with imaplib_connect.open_connection() as c:
    c.append('INBOX', '',
             imaplib.Time2Internaldate(time.time()),
             str(new_message).encode('utf-8'))

    # Show the headers for all messages in the mailbox
    c.select('INBOX')
    typ, [msg_ids]  c.search(None, 'ALL')
    for num in msg_ids.split():
        typ, msg_data  c.fetch(num, '(BODY.PEEK[HEADER])')
        for response_part in msg_data:
            if isinstance(response_part, tuple):
                print('\n{}:'.format(num))
                print(response_part[1])

payload , использованная в этом примере, представляет собой простое текстовое тело письма. Сообщение также поддерживает составные сообщения в кодировке MIME.

$ python3 imaplib_append.py

Subject: subject goes here
From: pymotw@example.com
To: example@example.com

This is the body of the message.


b'1':
b'Return-Path: \r\nReceived: from compute
4.internal (compute4.nyi.internal [10.202.2.44])\r\n\t by sloti2
6t01 (Cyrus 3.0.0-beta1-git-fastmail-12410) with LMTPA;\r\n\t Su
n, 06 Mar 2016 16:16:03 -0500\r\nX-Sieve: CMU Sieve 2.4\r\nX-Spa
m-known-sender: yes,
,\r\n \r\nX-
Spam-score: 0.0\r\nX-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, L
ANGUAGES unknown, BAYES_USED global,\r\n  SA_VERSION 3.3.2\r\nX-
Spam-source: IP=\'127.0.0.1\', Host=\'unk\', Country=\'unk\', Fr
omHeader=\'com\',\r\n  MailFrom=\'com\'\r\nX-Spam-charsets: plai
n=\'us-ascii\'\r\nX-Resolved-to: doughellmann@fastmail.fm\r\nX-D
elivered-to: doug@doughellmann.com\r\nX-Mail-from: doug@doughell
mann.com\r\nReceived: from mx5 ([10.202.2.204])\r\n  by compute4
.internal (LMTPProxy); Sun, 06 Mar 2016 16:16:03 -0500\r\nReceiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])\r\n\tby mx5.ny
i.internal (Postfix) with ESMTP id 47CBA280DB3\r\n\tfor ; Sun,  6 Mar 2016 16:16:03 -0500 (EST)\r\nReceiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])\r\n    by mx5.
nyi.internal (Authentication Milter) with ESMTP\r\n    id A71788
6846E.30BA4280D81;\r\n    Sun, 6 Mar 2016 16:16:03 -0500\r\nAuth
entication-Results: mx5.nyi.internal;\r\n   
 rsa key)
.com   
 mailout.nyi.internal (gateway1.nyi.internal [10.202.2.221])\r\n
\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/25
6 bits))\r\n\t(No client certificate requested)\r\n\tby mx5.nyi.
internal (Postfix) with ESMTPS id 30BA4280D81\r\n\tfor ; Sun,  6 Mar 2016 16:16:03 -0500 (EST)\r\nReceive
d: from compute2.internal (compute2.nyi.internal [10.202.2.42])\
r\n\tby mailout.nyi.internal (Postfix) with ESMTP id 1740420D0A\
r\n\tfor ; Sun,  6 Mar 2016 16:16:03 -050
0 (EST)\r\nReceived: from frontend2 ([10.202.2.161])\r\n  by com
pute2.internal (MEProxy); Sun, 06 Mar 2016 16:16:03 -0500\r\nDKI
M-Signature:
ngengine.com;
e:from:message-id:mime-version:subject:to:x-sasl-enc\r\n\t:x-sas
l-enc;
pCovRIoQIRyp8Fl0L6JHOI8sbZy2obx7O28JF2iTlTWmX33Rhlq9403XRklwN3JA
\r\n\t7KSPqMTp30Qdx6yIUaADwQqlO+QMuQq/QxBHdjeebmdhgVfjhqxrzTbSMw
c: 8ZJ+4ZRE8AGPzdLRWQFivGymJb8pa4G9JGcb7k4xKn+I 1457298962\r\nRe
ceived: from [192.168.1.14] (75-137-1-34.dhcp.nwnn.ga.charter.co
m [75.137.1.34])\r\n\tby mail.messagingengine.com (Postfix) with
 ESMTPA id C0B366801CD\r\n\tfor ; Sun,  6
 Mar 2016 16:16:02 -0500 (EST)\r\nFrom: Doug Hellmann \r\nContent-Type: text/plain;
ontent-Transfer-Encoding: 7bit\r\nSubject: PyMOTW Example messag
e 2\r\nMessage-Id: <00ABCD46-DADA-4912-A451-D27165BC3A2F@doughel
lmann.com>\r\nDate: Sun, 6 Mar 2016 16:16:02 -0500\r\nTo: Doug H
ellmann \r\nMime-Version: 1.0 (Mac OS X M
ail 9.2 \\(3112\\))\r\nX-Mailer: Apple Mail (2.3112)\r\n\r\n'

b'2':
b'Subject: subject goes here\r\nFrom: pymotw@example.com\r\nTo:
example@example.com\r\n\r\n'

Перемещение и копирование сообщений

Когда сообщение находится на сервере, его можно перемещать или копировать без загрузки с помощью move () или copy () . Эти методы работают с диапазонами идентификаторов сообщений, как и fetch () .

imaplib_archive_read.py

import imaplib
import imaplib_connect

with imaplib_connect.open_connection() as c:
    # Find the "SEEN" messages in INBOX
    c.select('INBOX')
    typ, [response]  c.search(None, 'SEEN')
    if typ  'OK':
        raise RuntimeError(response)
    msg_ids  ','.join(response.decode('utf-8').split(' '))

    # Create a new mailbox, "Example.Today"
    typ, create_response  c.create('Example.Today')
    print('CREATED Example.Today:', create_response)

    # Copy the messages
    print('COPYING:', msg_ids)
    c.copy(msg_ids, 'Example.Today')

    # Look at the results
    c.select('Example.Today')
    typ, [response]  c.search(None, 'ALL')
    print('COPIED:', response)

Этот пример сценария создает новый почтовый ящик в Example и копирует в него прочитанные сообщения из INBOX .

$ python3 imaplib_archive_read.py

CREATED Example.Today: [b'Completed']
COPYING: 2
COPIED: b'1'

Повторный запуск того же сценария показывает важность проверки кодов возврата. Вместо того, чтобы вызывать исключение, вызов create () для создания нового почтового ящика сообщает, что почтовый ящик уже существует.

$ python3 imaplib_archive_read.py

CREATED Example.Today: [b'[ALREADYEXISTS] Mailbox already exists
']
COPYING: 2
COPIED: b'1 2'

Удаление сообщений

Хотя многие современные почтовые клиенты используют модель «Папка для мусора» для работы с удаленными сообщениями, сообщения обычно не перемещаются в настоящую папку. Вместо этого их флаги обновляются, чтобы добавить \ Deleted . Операция по «очистке» корзины реализуется с помощью команды EXPUNGE . Этот пример сценария находит заархивированные сообщения с «Lorem ipsum» в теме, устанавливает флаг удаления, а затем показывает, что сообщения все еще присутствуют в папке, повторно запрашивая сервер.

imaplib_delete_messages.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    c.select('Example.Today')

    # What ids are in the mailbox?
    typ, [msg_ids]  c.search(None, 'ALL')
    print('Starting messages:', msg_ids)

    # Find the message(s)
    typ, [msg_ids]  c.search(
        None,
        '(SUBJECT "subject goes here")',
    )
    msg_ids  ','.join(msg_ids.decode('utf-8').split(' '))
    print('Matching messages:', msg_ids)

    # What are the current flags?
    typ, response  c.fetch(msg_ids, '(FLAGS)')
    print('Flags before:', response)

    # Change the Deleted flag
    typ, response  c.store(msg_ids, '+FLAGS', r'(\Deleted)')

    # What are the flags now?
    typ, response  c.fetch(msg_ids, '(FLAGS)')
    print('Flags after:', response)

    # Really delete the message.
    typ, response  c.expunge()
    print('Expunged:', response)

    # What ids are left in the mailbox?
    typ, [msg_ids]  c.search(None, 'ALL')
    print('Remaining messages:', msg_ids)

Явный вызов expunge () удаляет сообщения, но вызов close () имеет тот же эффект. Разница в том, что клиент не уведомляется об удалении при вызове close () .

$ python3 imaplib_delete_messages.py

Response code: OK
Server response: b'(\\HasChildren) "." Example'
Parsed response: ('\\HasChildren', '.', 'Example')
Server response: b'(\\HasNoChildren) "." Example.Today'
Parsed response: ('\\HasNoChildren', '.', 'Example.Today')
Server response: b'(\\HasNoChildren) "." Example.2016'
Parsed response: ('\\HasNoChildren', '.', 'Example.2016')
Server response: b'(\\HasNoChildren) "." Archive'
Parsed response: ('\\HasNoChildren', '.', 'Archive')
Server response: b'(\\HasNoChildren) "." "Deleted Messages"'
Parsed response: ('\\HasNoChildren', '.', 'Deleted Messages')
Server response: b'(\\HasNoChildren) "." INBOX'
Parsed response: ('\\HasNoChildren', '.', 'INBOX')
Starting messages: b'1 2'
Matching messages: 1,2
Flags before: [b'1 (FLAGS (\\Seen))', b'2 (FLAGS (\\Seen))']
Flags after: [b'1 (FLAGS (\\Deleted \\Seen))', b'2 (FLAGS (\\Del
eted \\Seen))']
Expunged: [b'2', b'1']
Remaining messages: b''

Смотрите также