Как я учил Алису отправлять почту

от автора

Всем привет! Меня зовут Иван Чечиков, я QA-lead в МТС Digital, работаю над проектом стримингового сервиса WASD.TV. В этой статье я расскажу о своем пет-проекте по созданию навыка «Умный почтальон» для Алисы, ассистента Яндекса.

Немного истории:

В декабре прошлого года я приобрел умную колонку Яндекса, Станцию Лайт. Еще не успел насладиться приобретением, а уже заметил, что, к сожалению, некоторого функционала Алисе не хватает. Погуглил и узнал, что существует витрина навыков, в которой представлены продукты сторонних разработчиков, – навыки для Алисы, расширяющие ее возможности. Идея этой витрины показалась мне интересной и я решил создать для нее новый навык. Мне хотелось научить Алису отправлять почту по команде с колонки, так как по дефолту она этого не умеет.

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

Итак, приступим.

Чтобы реализовать эту задумку, мне нужно было:

  • поднять локальный сервер на Python с пробросом хоста на удаленную машину с https;

  • реализовать авторизацию в Яндекс.Почта по imap и smtp;

  • создать приложение в Яндекс OAuth и навык в Яндекс.Диалогах;

  • написать сценарии для Алисы: логику получения списка контактов, формирование темы и текста письма, отправки письма;

  • протестировать созданный функционал;

  • поместить проект на виртуальный хостинг.

Пойдем по порядку.

Я решил создать свое приложение на python3.8 с использованием веб фреймворка Bottle. Локальный сервер я поднял, используя метод run.

from bottle import route, run  @route('/') def index():     print("Тест")  run(host='127.0.0.1', port=8080)

Пробросил локальный хост через ngrok. Это достаточно простой и удобный в использовании сервис. Необходимо зарегистрироваться, cкачать zip-архив и выполнить ряд команд в терминале:

$ unzip /path/to/ngrok.zip $ ngrok config add-authtoken {authtoken} $ ngrok http 8080

Результат:

При отправке GET запроса на https://1cc0-213-24-134-136.eu.ngrok.io вернется 200 код ответа http.

Переходим к реализации авторизации в Яндекс.Почта по imap и smtp. Для начала нужно дать доступ в своей учетной записи на коннект по imap и smtp.

Заходим в свой аккаунт Яндекс.Почта. Почта->Все настройки.

Ставим галочку напротив Пароли приложений и OAuth-токены. Готово, идем дальше.

У Яндекса есть API для разработчиков, позволяющее реализовать свои мечты и идеи в экосистеме сервисов. Для OAuth2-авторизации по imap и smtp есть свой мануал. Регистрируем приложение и изучаем мануал.

Заходим в наше зарегистрированное приложение в Яндекс.OAuth. В созданном приложении указываем Callback URL – такие, как на скрине.

Авторизация в навыке может осуществляться напрямую через приложение Яндекса, либо через свой собственный сервис, который должен реализовывать логику получения: верификационный код -> access_token. Мне не захотелось писать OAuth-сервер, поэтому я выбрал первый вариант, так как это по сути готовое решение от самого Яндекса. Если кто-то выберет второй вариант, то можно тут посмотреть пример получения OAuth-токенa веб-сервисом на python.

Даем приложению доступ к сервисам API Яндекс ID и Яндекс.Почта:

Код подключения по imap
def get_email_address_list(access_token: str, sender_email: str) -> list:     try:         xoauth2_token = f"user={sender_email}\x01auth=Bearer {access_token}\x01\x01"         imap = imaplib.IMAP4_SSL(host='imap.yandex.com')         imap.authenticate("XOAUTH2", lambda x: xoauth2_token)         status, messages = imap.select("INBOX")         messages = int(messages[0])         messages_count = 0         if messages >= 30:             messages_count = 30         elif messages < 30:             messages_count = messages         while messages_count:             messages_count -= 1             res, msg = imap.fetch(str(messages - messages_count), "(RFC822)")             for response in msg:                 if isinstance(response, tuple):                     msg = email.message_from_bytes(response[1])                     sender_data = decode_header(msg.get("From"))                     if len(sender_data) == 2:                         sender_name, encoding = sender_data[0]                         sender_email, type_email = sender_data[1]                         if isinstance(sender_name, bytes) and isinstance(sender_email, bytes):                             sender_name = sender_name.decode(encoding)                             sender_name = ''.join(sender_name.split()).lower()                             sender_email = sender_email.decode(encoding).replace("<", "") \                                 .replace(">", "").replace(" ", "")                             sender_email = sender_email.lower()                             email_address_list.append(sender_name + ":" + sender_email)                                          imap.close()         imap.logout()              except Exception:         return []          return email_address_list

Приложение подключается к серверу по imap, получает список всех писем пользователя, перебирает последние 30, делает проверки на корректность данных и кладет строки в список в формате «имя отправителя: адрес почты». Я указываю лимит в 30 сообщений, так как вебхук должен дать ответ серверу Яндекса в течении 3 секунд, иначе Алиса вернет ответ, что вебхук не отвечает. Код получился недостаточно оптимизированный, функция не успевает перебрать более 30 сообщений и вернуть ответ за 3 сек.

Код подключения по smtp
def get_email_data_list(email_data_str: str) -> list:     email_data_list = email_data_str.split("@")     return email_data_list    def send_email(access_token: str, sender_email_name: str, sender_email: str, recipient_email_name: str,                recipient_email: str, subject: str, message: str) -> bool:     try:         just_a_str = f"user={sender_email}\x01auth=Bearer {access_token}\x01\x01"         xoauth2_token = base64.b64encode(bytes(just_a_str, 'utf-8')).decode('utf-8')          sender_email_login = get_email_data_list(sender_email)[0]         sender_email_domain = get_email_data_list(sender_email)[1]         recipient_email_login = get_email_data_list(recipient_email)[0]         recipient_email_domain = get_email_data_list(recipient_email)[1]          msg = EmailMessage()         msg['Subject'] = subject         msg['From'] = Address(sender_email_name, sender_email_login, sender_email_domain)         msg['To'] = Address(recipient_email_name, recipient_email_login, recipient_email_domain)         msg.set_content(message)          smtp = smtplib.SMTP_SSL(host='smtp.yandex.ru', port=465)         smtp.connect(host='smtp.yandex.ru', port=465)         smtp.docmd("auth", f"XOAUTH2 {xoauth2_token}")         smtp.sendmail(sender_email, recipient_email, msg.as_string())         smtp.quit()     except Exception:         return False          return True

Приложение подключается по smtp, формирует тему письма, email отправителя, email получателя и текст. Функция send_email отправляет письмо и возвращает True, если успешно вызвана, в противном случае она возвращает False.

Создаем навык в Яндекс.Диалогах.

Выбираем Создать диалог.

Выбираем Навык в Алисе

Заполняем поля. Указываем Имя навыка, Webhook URL, Тип доступа.

Webhook URL – это наш локальный сервер, проксирующий на ngrok. С него будут слаться запросы к Алисе. Тип доступа я оставляю приватным.

В связке аккаунтов надо указать идентификатор приложения и секрет приложения – это ClientID и Client secret в Яндекс.OAuth.

URL авторизации, URL для получения и обновления токена заполняем также, как на скрине. Эти данные нужны, чтобы пользователь смог авторизоваться через сервис Яндекса в навыке и наше приложение смогло получить access_token для выполнения своей логики.

Переходим к написанию сценариев для Алисы.

Код файла app.py
from bottle import request, post, default_app from mail_tools import get_sender_email_data, send_email, get_recipient_email_text, get_email_obj_text, \     get_email_address_list import json import re  response_texts  = [] response_ttss = []   @post('/') def work():     try:         response = {             "version": request.json["version"],             "session": request.json["session"],             "response": {                 "end_session": False             }         }         req = request.json          if req["session"]["new"] or req["request"]["original_utterance"].lower().strip() in ["привет", "хай", "дарова",                                                                                              "ку",                                                                                              "даров", "здарова",                                                                                              "hello", "hi"]:             response["response"]["text"] = "Привет, навык позволяет отправлять почту с помощью умного ассистента." \                                            "Чтобы начать ответьте 'старт'. Для выхода из навыка " \                                            "ответьте 'пока' или 'стоп'."             response["response"]["tts"] = "Прив+ет, н+авык позвол+яет отправл+ять п+очту с п+омощью умного ассист+ента. " \                                           "Чт+обы нач+ать отв+етьте старт. Для в+ыхода из н+авыка " \                                           "отв+етьте пок+а или стоп. "             response_texts.append(response["response"]["text"])             response_ttss.append(response["response"]["tts"])         else:             try:                 if req["request"]["original_utterance"].lower().strip() in ["стоп", "закончили",                                                                             "не надо", "все", "хватит",                                                                             "пока", "до свидания", "конец",                                                                             "нет", "отбой", "хватит"]:                      response["response"]["text"] = "Всего хорошего, до свидания!"                     response["response"]["tts"] = "Всег+о хор+ошего, до свид+ания!"                     response_texts.append(response["response"]["text"])                     response_ttss.append(response["response"]["tts"])                     response["response"]["end_session"] = True                 elif req["session"]["user"]["access_token"]:                     response["response"]["text"] = response_texts[len(response_texts) - 1]                     response["response"]["tts"] = response_ttss[len(response_ttss) - 1]                     if req["request"]["original_utterance"].lower().strip() == "помощь":                         response["response"]["text"] = "Привет, навык позволяет отправлять почту с помощью умного ассистента. " \                                                        "Чтобы начать ответьте 'старт'. Для выхода из навыка " \                                                        "ответьте 'пока' или 'стоп'."                         response["response"]["tts"] = "Прив+ет, н+авык позвол+яет отправл+ять п+очту " \                                                       "с п+омощью умного ассист+ента. Чт+обы нач+ать отв+етьте старт. " \                                                       "Для в+ыхода из н+авыка отв+етьте пок+а или стоп."                         response_texts.append(response["response"]["text"])                         response_ttss.append(response["response"]["tts"])                     else:                          if req["request"]["original_utterance"].lower().strip().strip() == "старт":                             global access_token                             access_token = req["session"]["user"]["access_token"]                             if get_sender_email_data(access_token):                                 global sender_email_name                                 sender_email_name = get_sender_email_data(access_token)[0]                                 global sender_email                                 sender_email = get_sender_email_data(access_token)[1]                                 response["response"]["text"] = f"{sender_email_name}, идентификация вашего email завершена. " \                                                               f"Чтобы продолжить, ответьте, 'список контактов'."                                  response["response"]["tts"] = f"{sender_email_name}, идентифик+ация в+ашего email завершен+а. " \                                                               f"Чт+обы прод+олжить отв+етьте сп+исок конт+актов."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                             else:                                 response["response"]["text"] = "Не удалось определить ваши почтовые данные. Есть проблема, " \                                                                "начните сначала. Ответьте 'старт' или 'помощь'."                                  response["response"]["tts"] = "Не удал+ось определ+ить в+аши почт+овые д+анные. " \                                                               "Есть пробл+ема, начн+ите снач+ала. " \                                                               "Отв+етьте старт или п+омощь."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                          elif req["request"]["original_utterance"].lower().strip() == "список контактов":                             global email_address_list                             email_address_list = get_email_address_list(access_token, sender_email)                             if email_address_list:                                 response["response"]["text"] = "Список контактов сформирован. " \                                                                "Я могу начать писать письмо. Кому будем отправлять? " \                                                                "Назовите получателя, ответив 'получатель', " \                                                                "а дальше назовите его имя и фамилию, " \                                                                "если это физ. лицо, " \                                                                "или же название компании, если это юр. лицо."                                  response["response"]["tts"] = "Сп+исок конт+актов сформир+ован. Я мог+у нач+ать пис+ать письм+о. " \                                                               "Ком+у б+удем отправл+ять? Назов+ите получ+ателя, " \                                                               "отв+етив получ+атель, а д+альше назов+ите ег+о имя " \                                                               "и фам+илию, если это физ лиц+о или же назв+ание" \                                                               "комп+ании, если это юр лиц+о."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                              else:                                 response["response"]["text"] = "Список контактов не удалось сформировать или же он пуст."                                  response["response"]["tts"] = " Сп+исок конт+актов не удал+ось сформиров+ать или же он пуст."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                          elif re.match(r"^получатель [\S\sa-zA-Zа-яА-Я0-9_.+-]+$",                                       req["request"]["original_utterance"].lower().strip()):                             if get_recipient_email_text(req["request"]["original_utterance"]):                                 global recipient_email_name                                 recipient_email_name = get_recipient_email_text(req["request"]["original_utterance"])[0]                                 global recipient_email                                 recipient_email = get_recipient_email_text(req["request"]["original_utterance"])[1]                                 response["response"]["text"] = f"Email получателя {recipient_email}. Чтобы продолжить, " \                                                                f"ответьте, 'тема письма'. " \                                                                f"Если хотите изменить email получателя, " \                                                                f"ответьте прошлую команду еще раз."                                 response["response"]["tts"] = f"Email получ+ателя {recipient_email}. " \                                                               f"Чт+обы прод+олжить, отв+етьте, т+ема письм+а. " \                                                               f"Если хот+ите измен+ить email получ+ателя, " \                                                               f"отв+етьте пр+ошлую ком+анду ещ+е раз."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                             else:                                 response["response"]["text"] = "Не удалось определить email получателя. " \                                                                "Возможно его нет в сформированном списке контактов. " \                                                                "Попробуйте снова, ответьте прошлую команду еще раз."                                 response["response"]["tts"] = "Не удал+ось определ+ить email получ+а " \                                                               "теля. Возм+ожно его нет в сформир+ованном " \                                                               "сп+иске конт+актов. Попр+обуйте сн+ова, " \                                                               "отв+етьте пр+ошлую ком+анду еще раз."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                          elif req["request"]["original_utterance"].lower().strip() == "тема письма":                             response["response"]["text"] = "Придумайте тему письма, ответив, 'тема'..., " \                                                            "а дальше только полёт вашей мысли."                             response["response"]["tts"] = "Прид+умайте т+ему письм+а, отв+етив, т+ема..., " \                                                           "а д+альше т+олько пол+ёт в+ашей м+ысли."                             response_texts.append(response["response"]["text"])                             response_ttss.append(response["response"]["tts"])                          elif re.match(r"^тема (?!.*\bписьма\b)[\S\sa-zA-Zа-яА-Я0-9_.+-]+$",                                       req["request"]["original_utterance"].lower().strip()):                             global subject                             subject = get_email_obj_text(req["request"]["original_utterance"].strip(), "тема")                             subject = subject if re.match(r'[+.!?]', subject[len(subject) - 1]) else subject + '.'                             response["response"]["text"] = f"Тема письма - {subject} Чтобы продолжить, " \                                                            f"ответьте, 'текст письма'. " \                                                            f"Если хотите изменить тему письма, " \                                                            f"ответьте прошлую команду еще раз."                             response["response"]["tts"] = f"Т+ема письм+а - {subject} Чт+обы прод+олжить, " \                                                           f"отв+етьте, текст письм+а. " \                                                           f"Если хот+ите измен+ить т+ему письм+а, " \                                                           f"отв+етьте пр+ошлую ком+анду еще раз."                             response_texts.append(response["response"]["text"])                             response_ttss.append(response["response"]["tts"])                          elif req["request"]["original_utterance"].lower().strip() == "текст письма":                             response["response"]["text"] = "Придумайте текст письма, ответив, 'текст'..., " \                                                            "а дальше только полёт вашей мысли."                             response["response"]["tts"] = "Прид+умайте текст письм+а, отв+етив, текст... " \                                                           "а д+альше т+олько пол+ёт в+ашей м+ысли. "                             response_texts.append(response["response"]["text"])                             response_ttss.append(response["response"]["tts"])                          elif re.match(r"^текст (?!.*\bписьма\b)[\S\sa-zA-Zа-яА-Я0-9_.+-]+$",                                       req["request"]["original_utterance"].lower().strip()):                             global message                             message = get_email_obj_text(req["request"]["original_utterance"].strip(), "текст")                             message = message if re.match(r'[+.!?]', message[len(message) - 1]) else message + '.'                             response["response"]["text"] = f"Текст письма - {message} Чтобы продолжить, " \                                                            f"ответьте, 'отправка письма'. " \                                                            f"Если хотите изменить текст письма, " \                                                            f"ответьте прошлую команду еще раз."                             response["response"]["tts"] = f"Текст письм+а - {message} Чт+обы прод+олжить, " \                                                           f"отв+етьте, отпр+авка письм+а. " \                                                           f"Если хот+ите измен+ить текст письм+а, " \                                                           f"отв+етьте пр+ошлую ком+анду еще раз."                             response_texts.append(response["response"]["text"])                             response_ttss.append(response["response"]["tts"])                          elif req["request"]["original_utterance"].lower().strip() == "отправка письма":                             response["response"]["text"] = "Отправляю письмо, подтвердите, " \                                                            "ответив, 'подтверждаю', либо вы можете " \                                                            "вернуться назад и изменить текст, " \                                                            "тему или получателя письма."                             response["response"]["tts"] = "Отправл+яю письм+о, подтверд+ите, " \                                                           "отв+етив, подтвержд+аю, л+ибо вы м+ожете " \                                                           "верн+уться наз+ад и измен+ить текст,т+ему или " \                                                           "получ+ателя письм+а."                             response_texts.append(response["response"]["text"])                             response_ttss.append(response["response"]["tts"])                          elif req["request"]["original_utterance"].lower().strip() == "подтверждаю":                             if send_email(access_token=access_token, sender_email_name=sender_email_name,                                           sender_email=sender_email, recipient_email_name=recipient_email_name,                                           recipient_email=recipient_email, subject=subject, message=message):                                 response["response"]["text"] = "Письмо отправлено."                                 response["response"]["tts"] = "Письм+о отпр+авлено."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                             else:                                 response["response"]["text"] = "Письмо уже отправлено, либо на " \                                                                "сервере какие то проблемы."                                 response["response"]["tts"] = "Письм+о уж+е отпр+авлено, л+ибо на " \                                                               "с+ервере как+ие то пробл+емы."                                 response_texts.append(response["response"]["text"])                                 response_ttss.append(response["response"]["tts"])                             sender_email_name = sender_email = recipient_email_name = \                                 recipient_email = subject = message = ""                         elif req["request"]["command"].lower().strip() in ["спасибо", "большое спасибо"]:                             response["response"]["text"] = "Пожалуйста, всегда к вашим услугам!"                             response["response"]["tts"] = "Пож+алуйста, всегд+а к в+ашим усл+угам!"              except KeyError:                 response["start_account_linking"] = {}                 response["response"][                     "text"] = "Привет, отправим почту кому-нибудь? Только для начала надо авторизоваться. " \                               "Чтобы начать ответьте 'старт'. " \                               "Для выхода из навыка ответьте 'пока' или 'стоп'."                  response["response"][                     "tts"] = "Прив+ет, отпр+авим п+очту ком+у ниб+удь? Только для начала надо авторизоваться. " \                              "Чт+обы нач+ать отв+етьте старт. Для в+ыхода из н+авыка отв+етьте пок+а " \                              "или стоп."     except Exception :         response["response"]["text"] = "Потеряна связь с космосом, повторите предыдущую команду или начните сначала."         response["response"]["tts"] = "Пот+еряна связь с космосом, повтор+ите предыд+ущую ком+анду или начн+ите снач+ала."     return json.dumps(response)  run(host='127.0.0.1', port=8080)

В ветвях сценариев я использовал оптимально нужные условия для работы навыка. Логика проста: пользователь начинает работу с навыком командой ‘старт’, если в запросе приложения отсутствует access_token, Алиса просит пользователя авторизоваться в навыке. Если пользователь авторизовался, приложение получает имя и email пользователя, формирует список контактов, помогает пользователю создать тему и текст письма, выполняет его отправку. В противном случае пользоваться функционалом нельзя.

Авторизация выглядит так:

Верхний уровень покрыт блоком try->except. При возникновении какой-либо ошибки в любом месте кода пользователь увидит сообщение: «Потеряна связь с космосом, повторите предыдущую команду или начните сначала». По-хорошему надо добавить логирование с записью в файл, но я обошелся без этого, it is bad practiсe.

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

 ... response_texts.append(response["response"]["text"]) response_ttss.append(response["response"]["tts"]) ...  
elif req["session"]["user"]["access_token"]:   response["response"]["text"] = response_texts[len(response_texts) - 1]   response["response"]["tts"] = response_ttss[len(response_ttss) - 1]

Добавляю код из файла mail_tools.py. В итоге у меня получилось bottle-приложение из двух файлов.

Код файла mail_tools.py
import base64 import imaplib import re import smtplib import email import email.header from email.header import decode_header from email.headerregistry import Address from email.message import EmailMessage  import requests  login_info_url = "https://login.yandex.ru/info?oauth_token={}" email_address_list = []   def get_sender_email_data(access_token: str) -> list:   try:     response = requests.get(login_info_url.format(access_token))     user_data = response.json()     sender_name = user_data["display_name"]     sender_email = user_data["default_email"]   except Exception:     return []   return [sender_name, sender_email]   def send_email(access_token: str, sender_email_name: str, sender_email: str, recipient_email_name: str,                recipient_email: str, subject: str, message: str) -> bool:     try:         just_a_str = f"user={sender_email}\x01auth=Bearer {access_token}\x01\x01"         xoauth2_token = base64.b64encode(bytes(just_a_str, 'utf-8')).decode('utf-8')          sender_email_login = get_email_data_list(sender_email)[0]         sender_email_domain = get_email_data_list(sender_email)[1]         recipient_email_login = get_email_data_list(recipient_email)[0]         recipient_email_domain = get_email_data_list(recipient_email)[1]          msg = EmailMessage()         msg['Subject'] = subject         msg['From'] = Address(sender_email_name, sender_email_login, sender_email_domain)         msg['To'] = Address(recipient_email_name, recipient_email_login, recipient_email_domain)         msg.set_content(message)          smtp = smtplib.SMTP_SSL(host='smtp.yandex.ru', port=465)         smtp.connect(host='smtp.yandex.ru', port=465)         smtp.docmd("auth", f"XOAUTH2 {xoauth2_token}")         smtp.sendmail(sender_email, recipient_email, msg.as_string())         smtp.quit()     except Exception:         return False          return True   def get_email_data_list(email_data_str: str) -> list:     email_data_list = email_data_str.split("@")     return email_data_list   def get_email_address_list(access_token: str, sender_email: str) -> list:     try:         xoauth2_token = f"user={sender_email}\x01auth=Bearer {access_token}\x01\x01"              imap = imaplib.IMAP4_SSL(host='imap.yandex.com')         imap.debug = 4         imap.authenticate("XOAUTH2", lambda x: xoauth2_token)         status, messages = imap.select("INBOX")         messages = int(messages[0])         messages_count = 0         if messages >= 30:             messages_count = 30         elif messages < 30:             messages_count = messages         while messages_count:             messages_count -= 1             res, msg = imap.fetch(str(messages - messages_count), "(RFC822)")             for response in msg:                 if isinstance(response, tuple):                     msg = email.message_from_bytes(response[1])                      sender_data = decode_header(msg.get("From"))                      if len(sender_data) == 2:                         sender_name, encoding = sender_data[0]                         sender_email, type_email = sender_data[1]                         if isinstance(sender_name, bytes) and isinstance(sender_email, bytes):                             sender_name = sender_name.decode(encoding)                             sender_name = ''.join(sender_name.split()).lower()                             sender_email = sender_email.decode(encoding).replace("<", "") \                                 .replace(">", "").replace(" ", "")                             sender_email = sender_email.lower()                             email_address_list.append(sender_name + ":" + sender_email)                      imap.close()         imap.logout()              except Exception:         return []          return email_address_list   def get_recipient_email_text(text: str) -> list:     try:         email_text = ''.join(text.split()).lower()         email_text = email_text.split("получатель")[1]         for email_recipient in email_address_list:             if email_text in email_recipient:                 recipient_name = email_recipient.split(":")[0]                 recipient_email = email_recipient.split(":")[1]                 return [recipient_name, recipient_email]      except Exception:         return []     return []   def get_email_obj_text(email_obj_text: str, email_obj_type: str) -> str:     email_obj_text_list = email_obj_text.split(email_obj_type + " ") \         if len(email_obj_text.split(email_obj_type + " ")) > 1 \         else email_obj_text.split(email_obj_type.title() + " ")     email_obj_text = email_obj_text_list[1]     email_obj_text = email_obj_text.replace(" точка с запятой", ";")     email_obj_text = email_obj_text.replace(" точка", ".")     email_obj_text = email_obj_text.replace(" знак восклицания", "!")     email_obj_text = email_obj_text.replace(" восклицательный знак", "!")     email_obj_text = email_obj_text.replace(" знак вопроса", "?")     email_obj_text = email_obj_text.replace(" вопросительный знак", "?")     email_obj_text = email_obj_text.replace("тире", "-")     email_obj_text = email_obj_text.replace(" двоеточие", ":")     email_obj_text = email_obj_text.replace(" запятая", ",")      email_obj_text_list = re.findall(r"[\sА-Яа-я-,;:_-]*!?.", email_obj_text)     text_list = []     for phrase in email_obj_text_list:         phrase = phrase.strip()         phrase = phrase.capitalize()         text_list.append(phrase)     email_obj_text = " ".join(text_list)     return email_obj_text

Актуальные имя и email пользователя приложение получает в функции get_sender_email_data отправляя GET запрос на https://login.yandex.ru/info с передачей access_token.

login_info_url = "https://login.yandex.ru/info?oauth_token={}" email_address_list = []   def get_sender_email_data(access_token: str) -> list:   try:     response = requests.get(login_info_url.format(access_token))     user_data = response.json()     sender_name = user_data["display_name"]     sender_email = user_data["default_email"]   except Exception:     return []   return [sender_name, sender_email]

Для определения темы и текста письма я использовал регулярное выражение. Эти почтовые объекты можно создавать с использованием знаков препинания и окончания предложения. Я подобрал дефолтный набор символов.

def get_email_obj_text(email_obj_text: str, email_obj_type: str) -> str:     email_obj_text_list = email_obj_text.split(email_obj_type + " ") \         if len(email_obj_text.split(email_obj_type + " ")) > 1 \         else email_obj_text.split(email_obj_type.title() + " ")     email_obj_text = email_obj_text_list[1]     email_obj_text = email_obj_text.replace(" точка с запятой", ";")     email_obj_text = email_obj_text.replace(" точка", ".")     email_obj_text = email_obj_text.replace(" знак восклицания", "!")     email_obj_text = email_obj_text.replace(" восклицательный знак", "!")     email_obj_text = email_obj_text.replace(" знак вопроса", "?")     email_obj_text = email_obj_text.replace(" вопросительный знак", "?")     email_obj_text = email_obj_text.replace("тире", "-")     email_obj_text = email_obj_text.replace(" двоеточие", ":")     email_obj_text = email_obj_text.replace(" запятая", ",")      email_obj_text_list = re.findall(r"[\sА-Яа-я-,;:_-]*!?.", email_obj_text)     text_list = []     for phrase in email_obj_text_list:         phrase = phrase.strip()         phrase = phrase.capitalize()         text_list.append(phrase)     email_obj_text = " ".join(text_list)     return email_obj_text

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

Можно протестировать сборку приложения в самом навыке, во вкладке Тестирование, либо в приложении Алиса на Windows/iOS/Android или на умной колонке. Для этого надо опубликовать приватный навык и сгенерировать одноразовую ссылку.

Как это работает у меня на колонке:

Под приложение я использовал сервер Beget. Виртуальный хостинг позволяет использовать код на Python последних версий. Мануал по деплою bottle приложения простой: создаете директории, закидываете файлы, немного магии и все работает. Я решил оставить навык приватным. Он не идеален и порой может отваливаться. Возможно, в дальнейшем я оптимизирую и доработаю код, прикручу БД, но это будет уже другая история.

Спасибо за уделенное время, если у вас есть вопросы – буду рад на них ответить в комментариях.


ссылка на оригинал статьи https://habr.com/ru/company/ru_mts/blog/710366/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *