/
Carton
/
python-course-tasks
Обзор
Документация
Войти
/
Carton
/
python-course-tasks
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
task_13_02_03/mailer.py
303 строки
12 KB
Yuri Petrov
+ перенос заданий в отдельный репозиторий
02 июл 2020, 02:05
02 июл 2020, 02:05
5312992
Код
Авторство
О чём код?
# Программирование на языке высокого уровня (Python). # https://www.yuripetrov.ru/edu/python # Задание task_13_02_03. # # Выполнил: Фамилия И.О. # Группа: !!! # E-mail: !!! import sys import re import imaplib import smtplib import email from email.mime.text import MIMEText from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.header import decode_header import utils class Mailer: """Класс Mailer отвечает за чтение и отправку почты. Поля: - self.login (str): логин (email) почтового сервиса; - self.password (str): пароль для почтового сервиса; - self.imap_server (imaplib.IMAP4_SSL): объект - IMAP-сервер; - self.imap_server_url (str): адрес IMAP-сервера; - self.imap_port (int): порт IMAP-сервера; - self.smtp_server (smtplib.SMTP): объект - SMTP-сервер; - self.smtp_server_url (str): адрес SMTP-сервера; - self.smtp_port (int): порт SMTP-сервера; - self.msg (email.Message): электронное письмо. Методы: - см. описание методов. """ def __init__(self): """Инициализация класса. - задать значения: - self.login, - self.password, - self.imap_server_url, - self.imap_port, - self.smtp_server_url, - self.smtp_port, - self.msg (None); - создать self.imap_server, self.smtp_server и выполнить подключение. """ self.login = !!! self.password = !!! self.imap_server_url = !!! self.imap_port = !!! self.smtp_server_url = !!! self.smtp_port = !!! """Выполнить подключение к IMAP- и SMTP серверам.""" raise NotImplementedError # Уберите raise и дополните код # Сообщение self.msg = !!! def __str__(self): """Вернуть информацию о классе.""" return "Mailer v 0.1" def noop(self): """Выполнить noop для IMAP- и SMTP-серверов.""" raise NotImplementedError # Уберите raise и дополните код def disconnect(self): """Отключиться от IMAP- и SMTP-серверов.""" raise NotImplementedError # Уберите raise и дополните код @staticmethod def _get_request_info(message): """Вернуть информацию о запросе клиента. Параметры: - message (email.Message): письмо. Письмо должно соответствовать определенному шаблону: * тема: **Просьба прислать актуальные вакансии**; * в тексте письма: * **Поиск: {}.** * **Регион: {}.** * **Опыт работы (лет): {}.** * **З/п (руб.): {}.** Данные о строке поиска и регионе являются обязательными. Результат: - словарь с ключами (значение = None, если не найдено): - "email" (str): e-mail клиента, - "datetime" (str): дата сообщения; - "subject" (str): тема сообщения; - "text" (str): текст сообщения; - "title" (str): строка поиска; - "area" (str): регион поиска; - "experience" (int): опыт работы; - "salary" (int): уровень зарплаты; - или None, если обязательные данные не найдены. """ # 1. Информация о письме text, encoding, mime = get_message_info(message) info = !!! # 2. Поиск необходимой информации # Проверка темы raise NotImplementedError # Уберите raise и дополните код # Проверка строки поиска и региона raise NotImplementedError # Уберите raise и дополните код # Проверка опыта и уровня з/п raise NotImplementedError # Уберите raise и дополните код return info def check_requests(self): """Получить список новых запросов от клиентов.""" requests = [] try: self.imap_server.select() response, messages_nums = self.imap_server.search(None, "(UNSEEN)") if response != "OK": raise imaplib.IMAP4.error("Не удалось получить список писем.") messages_nums = messages_nums[0].split() utils.log("Найдено новых писем: {}".format(len(messages_nums))) # 2.3. Чтение новых сообщений for message_num in reversed(messages_nums): # message_parts = "(RFC822)" - получение письма целиком response, data = \ self.imap_server.fetch(message_num, message_parts="(RFC822)") if response != "OK": utils.log("Не удалось получить письмо №".format(int(message_num.decode()))) continue # Отметить "прочитанность" обратно # Она будет отмечена при успешной отпраке письма self.imap_server.store(message_num, '-FLAGS', '\Seen') # Преобразование сообщения в объект email.Message raise NotImplementedError # Уберите raise и дополните код # Получение информации из письма, используя # Mailer._get_request_info() # Если возвращается не None, необходимо: # - добавить ключ "id" с номером message_num; # - добавить 'info' в 'requests'. raise NotImplementedError # Уберите raise и дополните код return requests except imaplib.IMAP4.error as err: utils.log(f"Возникла следующая ошибка: {err}") raise def send_mail(self, info, filename): """Отправить файл `filename` на почтовый адрес 'email' из 'info' c сопровождающим текстом. Параметры: - info (словарь _get_request_info()): запрос клиента; - filename(str): имя файла. """ # 1. Формирование сообщения # Заголовки "Subject", "From", "To" raise NotImplementedError # Уберите raise и дополните код # 1.1. MIMEText и MIMEApplication # Прикрепить 'text' к 'self.msg' как 'text/plain' text = "Здравствуйте!\n\n" \ "Ответ на Ваш запрос содержится во вложении\n\n" \ "С уважением, агентство \"{}\"".format(utils.agency_name) raise NotImplementedError # Уберите raise и дополните код # 1.2. MIMEApplication # Прикрепить 'filename' к 'self.msg' raise NotImplementedError # Уберите raise и дополните код # 2. Подклюение к серверу и отправка письма try: # Отправить письмо 'self.msg' self.smtp_server.send_message(self.msg) # Отметить письмо как прочитанное, чтобы не проверять его повторно raise NotImplementedError # Уберите raise и дополните код except smtplib.SMTPException as err: utils.log(f"Возникла следующая ошибка: {err}") raise def do_decode_header(header): """Декодировать заголовок по ключу 'header'. Параметры: header (str): заголовок вида "=?UTF-8?B?UmU6INCT0LvQtdCxINCf0L7Rh9GC0LA=?=". Базовые стандарты RFC 822 и RFC 2822 предусматривают, что любой заголовок представляет собой набор ASCII-символов, и до сих пор все символы обязательно преобразуются из/в этот формат. Python позволяет декодировать заголовки с использованием функции decode_header модуля email.header. """ header_parts = decode_header(header) # 'decode_header' возвращает список кортежей вида # [(b'\xd0\x9f\xd0\xb0\xd0\xb2\xd0\xb5\xd0\xbb # \xd0\x9f\xd0\xb0\xd0\xbd\xd1\x84\xd0\xb8\xd0\xbb # \xd0\xbe\xd0\xb2', 'utf-8'), # (b' <***@gmail.com>', None)] # Которые необходимо преобразовать в правильную кодировку res = [] for decoded_string, encoding in header_parts: if encoding: decoded_string = decoded_string.decode(encoding) elif isinstance(decoded_string, bytes): decoded_string = decoded_string.decode("ascii") res.append(decoded_string) # На выходе 'res' содержит заголовок в "привычном", декодированном виде return "".join(res) def get_part_info(part): """Получить текст сообщения в правильной кодировке. Параметры: - part: часть сообщения email.Message. Результат: - message (str): сообщение; - encoding (str): кодировка сообщения; - mime (str): MIME-тип. """ encoding = part.get_content_charset() # Если кодировку определить не удалось, используется по умолчанию if not encoding: encoding = sys.stdout.encoding mime = part.get_content_type() message = part.get_payload(decode=True).decode(encoding, errors="ignore").strip() return message, encoding, mime def get_message_info(message): """Получить текст сообщения в правильной кодировке. Параметры: - message: сообщение email.Message. Результат: - message (str): сообщение или строка "Нет тела сообщения"; - encoding (str): кодировка сообщения или "-"; - mime (str): MIME-тип или "-". """ # Алгоритм получения текста письма: # - если письмо состоит из нескольких частей # (message.is_multipart()) - необходимо пройти по составным # частям письма: "text/plain" или "text/html" # - если нет - текст можно получить напрямую message_text, encoding, mime = "Нет тела сообщения", "-", "-" if message.is_multipart(): for part in message.walk(): if part.get_content_type() in ("text/html", "text/plain"): message_text, encoding, mime = get_part_info(part) break # Только первое вхождение else: message_text, encoding, mime = get_part_info(message) return message_text, encoding, mime