Почтовый клиент командной строки

pochtovyj klient komandnoj stroki Сценарии на стороне клиента

Теперь объединим вместе все, что мы узнали о получении, отправке, анализе и составлении сообщений электронной почты, в простом, но функциональном инструменте командной строки для электронной почты. Сценарий в примере 13.20 реализует интерактивный сеанс электронной почты — пользователи могут вводить команды для чтения, отправки и удаления электронных писем. Для получения и отправки писем он использует модули poplib и smtplib, а для анализа и составления новых сообщений — пакет email.

Пример 13.20. PP4E\Internet\Email\pymail.py

#!/usr/local/bin/python """

########################################################################## pymailпростой консольный клиент электронной почты на языке Python;

использует модуль Python poplib для получения электронных писем, smtplib для отправки новых писем и пакет email для извлечения заголовков с содержимым и составления новых сообщений;

########################################################################## import poplib, smtplib, email.utils, mailconfig

from email.parser import Parser

from email.message import Message

fetchEncoding = mailconfig.fetchEncoding

def decodeToUnicode(messageBytes, fetchEncoding=fetchEncoding)

4E, Py3.1: декодирует извлекаемые строки bytes в строки str Юникода для отображения или анализа; использует глобальные настройки (или значения по умолчанию для платформы, исследует заголовки, делает обоснованные предположения); в Python 3.2/3.3 этот шаг может оказаться необязательным: в этом случае достаточно будет просто вернуть сообщение нетронутым;

return [line.decode(fetchEncoding) for line in messageBytes] def splitaddrs(field):

4E: разбивает список адресов по запятым, учитывает возможность появления запятых в именах

pairs = email.utils.getaddresses([field]) # [(name,addr)]

return [email.utils.formataddr(pair) for pair in pairs] # [name <addr>]

def inputmessage():

import sys

From = input(‘From? ‘).strip()

To = input(‘To? ‘).strip() # заголовок Date

# устанавливается автоматически

To = splitaddrs(To) # допускается множество, name+<addr>

Subj = input(‘Subj? ‘).strip() # не разбивать вслепую по ‘,’ или ‘;’

print(‘Type message text, end with line="."’) text = » while True:

line = sys.stdin.readline()

if line == ‘.\n’: break text += line

return From, To, Subj, text

def sendmessage():

From, To, Subj, text = inputmessage()

msg = Message()

msg[‘From’] = From

msg[‘To’] = ‘, ‘.join(To) # для заголовка, не для отправки msg[‘Subject’] = Subj

msg[‘Date’] = email.utils.formatdate() # текущие дата

# и время, rfc2822

msg.set_payload(text)

server = smtplib.SMTP(mailconfig.smtpservername)

try:

failed = server.sendmail(From, To, str(msg)) # может также

except: # возбудить исключение

print(‘Error send failed’)

else:

if failed: print(‘Failed:’, failed)

def connect(servername, user, passwd):

print(‘Connecting…’)

server = poplib.POP3(servername)

server.user(user) # соединиться, зарегистрироваться на сервере

server.pass_(passwd) # pass — зарезервированное слово

print(server.getwelcome()) # print выведет возвращаемое приветствие return server

def loadmessages(servername, user, passwd, loadfrom=1):

server = connect(servername, user, passwd)

try:

print(server.list())

(msgCount, msgBytes) = server.stat()

print(‘There are’, msgCount, ‘mail messages in’, msgBytes, ‘bytes’) print(‘Retrieving…’)

msgList = [] # получить почту

for i in range(loadfrom, msgCount+1): # пусто, если low >= high (hdr, message, octets) = server.retr(i) # сохранить текст # в списке

message = decodeToUnicode(message) # 4E, Py3.1: bytes в str

msgList.append(‘\n’.join(message)) # оставить письмо на сервере finally:

server.quit() # разблокировать почтовый ящик

assert len(msgList) == (msgCount loadfrom) + 1 # нумерация с 1 return msgList

def deletemessages(servername, user, passwd, toDelete, verify=True): print(‘To be deleted:’, toDelete)

if verify and input(‘Delete?’)[:1] not in [‘y’, ‘Y’]:

print(‘Delete cancelled.’) else:

server = connect(servername, user, passwd) try:

print(‘Deleting messages from server…’) for msgnum in toDelete: # повторно соединиться

#   для удаления писем

server.dele(msgnum) # ящик будет заблокирован

#   до вызова quit() finally:

server.quit()

def showindex(msgList): count = 0 # вывести некоторые заголовки

for msgtext in msgList:

msghdrs = Parser().parsestr(msgtext, headersonly=True) # ожидается # тип

count += 1 # str в 3.1

print(‘%d:\t%d bytes’ % (count, len(msgtext))) for hdr in (‘From’, ‘To’, ‘Date’, ‘Subject’): try:

print(‘\t%-8s=>%s’ % (hdr, msghdrs[hdr])) except KeyError:

print(‘\t%-8s=>(unknown)’ % hdr) if count % 5 == 0:

input(‘[Press Enter key]’) # приостановка через каждые 5 писем

def showmessage(i, msgList):

if 1 <= i <= len(msgList):

#print(msgList[i-1]) # устар.: вывести целиком — заголовки+текст print(‘-‘ * 79)

msg = Parser().parsestr(msgList[i-1]) # ожидается тип str в 3.1 content = msg.get_payload() # содержимое: строка

#   или [Messages]

if isinstance(content, str): # сохранить только самый

content = content.rstrip() + ‘\n’ # последний символ

#  конца строки print(content)

print(‘-‘ * 79) # получить только текст, см. email.parsers

else:

print(‘Bad message number’)

def savemessage(i, mailfile, msgList):

if 1 <= i <= len(msgList):

savefile = open(mailfile, ‘a’, encoding=mailconfig.fetchEncoding) # 4E savefile.write(‘\n’ + msgList[i-1] + ‘-‘*80 + ‘\n’) else:

print(‘Bad message number’)

def msgnum(command): try:

return int(command.split()[1]) except:

return -1 # предполагается, что это ошибка

helptext = """

Available commands:

i index display

l n? list all messages (or just message n)

d n? mark all messages for deletion (or just message n)

s n? save all messages to a file (or just message n)

m compose and send a new mail message q quit pymail

? display this help text

def interact(msgList, mailfile):

showindex(msgList)

toDelete = [] while True:

try:

command = input(‘[Pymail] Action? (i, l, d, s, m, q, ?) ‘) except EOFError:

command = ‘q’

if not command: command = ‘*’

#завершение if command == q‘: break

# оглавление

elif command[0] == ‘i’: showindex(msgList)

# содержимое письма elif command[0] == l‘:

if len(command) == 1:

for i in range(1, len(msgList)+1): showmessage(i, msgList) else:

showmessage(msgnum(command), msgList)

# сохранение

elif command[0] == ‘s’:

if len(command) == 1:

for i in range(1, len(msgList)+1):

savemessage(i, mailfile, msgList)

else:

savemessage(msgnum(command), mailfile, msgList)

# удаление

elif command[0] == ‘d’

if len(command) == 1: # удалить все позднее

toDelete = list(range(1, len(msgList)+1)) # в 3.x требуется else: # вызвать list()

delnum = msgnum(command) if (1 <= delnum <= len(msgList))

and (delnum not in toDelete): toDelete.append(delnum)

else: print(‘Bad message number’)

# составление нового письма

elif command[0] == m‘: # отправить новое сообщение

# через SMTP sendmessage()

#execfile(‘smtpmail.py‘, {}) # альтернатива: запустить

# в собственном пространстве имен

elif command[0] == ‘?’:
print(helptext)

else:

print(‘What? type "?" for commands help’) return toDelete

if __name__ == ‘__main__’:

import getpass, mailconfig

mailserver = mailconfig.popservername # например: ‘pop.rmi.net’ mailuser = mailconfig.popusername # например: ‘lutz’

mailfile = mailconfig.savemailfile # например: r’c:\stuff\savemail’

mailpswd = getpass.getpass(‘Password for %s?’ % mailserver) print(‘[Pymail email client]’) msgList = loadmessages(mailserver, mailuser, mailpswd) # загрузить все toDelete = interact(msgList, mailfile) if toDelete: deletemessages(mailserver, mailuser, mailpswd, toDelete) print(‘Bye.’)

Нового здесь немного — просто сочетание логики интерфейса пользователя, уже знакомых нам инструментов и некоторых новых приемов:

Загрузка

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

Сохранение

По требованию сценарий pymail сохраняет необработанный текст выбранного сообщения в локальном файле, имя которого указано в модуле mailconfig из примера 13.17.

Удаление

Теперь, наконец, поддерживается удаление почты с сервера по соответствующему запросу: сценарий pymail позволяет выбирать письма для удаления по номерам, но все же физически они удаляются с сервера только при выходе и только при подтверждении операции. Благодаря удалению только при выходе из программы удается избежать изменения номеров почтовых сообщений во время сеанса — в POP удаление почты из середины списка ведет к уменьшению номеров всех сообщений, следующих за тем, которое удаляется. Так как pymail кэширует все сообщения в памяти, последующие операции с пронумерованными сообщениями в памяти могут быть неправильно применены, если осуществлять удаления незамедлительно.

Анализ и составление сообщений

По командам вывода сообщений сценарий pymail выводит только содержательный текст сообщения, а не весь исходный текст, а при выводе оглавления почтового ящика отображаются только выбранные заголовки, выделенные из каждого сообщения. Для извлечения заголовков и содержимого писем используется пакет email, как показано в предыдущем разделе. Кроме того, сценарий использует пакет email также для составления сообщений, запрашивая строку, которая будет отправлена в виде письма.

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

Использованная литература:

Марк Лутц — Программирование на Python, 4-е издание, II том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий