Бот «Умный планировщик» — понимает с полуслова

Нужно быстро куда-то записывать напоминания, которые будут потом вам сами присылать уведомления? Да так, чтобы можно было писать обычным человеческим языком, а вас при этом прекрасно понимали? Мне тоже, поэтому я написал бота под это дело.

Демонстрация работы "Умного планировщика"

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

Забегая вперёд, скажу, что этого удалось добиться, как и также парочки других приятных особенностей:

  1. Бот понимает слова с ошибками.
  2. Бот понимает голосовые сообщения.
  3. Бот умеет прикреплять файлы к напоминаниям.
  4. Бот может быть интегрирован в вашу Trello доску.

Если вам не терпится опробовать бота прямо сейчас, то вот ссылка на него.

Для всех остальных, кому интересно как 16-ти летний школьник смог написать его, я расскажу небольшую историю разработки и о том, как устроена система парсинга напоминаний внутри «Умного планировщика».

Краткая история создания

Бот был написан на Node.js. Почему? Просто потому что javascript в данный момент является одним из самых популярных языков программирования. Я также думал попробовать написать бота на Python или php, но я остановился на js, так как он обладает схожим синтаксисом с знакомым мне C++.

Первая версия «Умного планировщика» работала иначе, но служила для одной и той же цели — создания напоминаний из естественной речи. Я хотел проверить имеет ли право на жизнь эта идея, и для этого я опубликовал статью про бота на Хабрахабре. Получив положительные отзывы, я решил продолжить разрабатывать бота.

Я не буду рассказывать вам как работала эта «кривая» и топорная версия, которая была создана почти год назад. Лучше перейдём сразу к текущей стабильной версии.

Архитектура бота

Алгоритм работы бота следующий:

Проверка напоминаний сделана при помощи минутного таймера. На диаграмме эта часть обозначена «Планировщик задач». Он загружает все напоминания из базы данных, проверяет их время и если оно вышло, то уведомляет о них. Истёкшие напоминания удаляются

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

Для повышения безопасности я добавил шифровку текста напоминаний при помощи встроенной в Node.js библиотеки crypto.

База данных PostgreSQL

Выбор СУБД PostgreSQL обусловлен тем, что она бесплатна и изначально интегрирована в платформу Heroku, на которой хостится бот. В базе данных всего 3 таблицы: Пользователи, Чаты и Напоминания.

Также мною была с нуля написан ORM интерфейс для базы данных. Я знаю, что наверняка кто-то это уже сделал, но я хотел попробовать сделать это сам и, таким образом, уменьшить количество зависимостей.

Извлечение даты

Процесс обработки сообщений пользователя разделён на два этапа: работу с числами и распознавание введённого напоминания.

Парсинг чисел

Прежде чем приступать к распознаванию времени, нам сначала нужно заменить все слова в строке, записанные в виде слов, реальными числами. Для этого я написал npm библиотеку parse-word-to-number. Она берёт строку и возвращает строку, в которой все возможные слова были заменены числами.
Для соотнесения фильтруемых слов из исходной строки с подходящими словами из словаря я использовал расстояние Дамерау-Левенштайна. Это позволяет библиотеке понимать слова, написанные с ошибками.

Особенности parse-word-to-number

Библиотека работает по такому алгоритму:

  1. Выбрать следующее слово в строке и найти его в словаре.
  2. Если оно найдено, то:
    ⠀⠀Если предыдущее слово это число:
    ⠀⠀⠀⠀Если разряд предыдущего числа больше текущего, то сложить текущее с предыдущим и соединить их.
    ⠀⠀⠀⠀Иначе если текущее число может быть умножено на предыдущее, то умножить текущее на предыдущее и соединить их.
  3. Во всех остальных случаях записать это слово в конец новой строки.

Она также поддерживает английский язык.

Распознавание текста и времени

Теперь, когда все числа в сообщении были заменены реальными числами, мы можем наконец извлечь описание времени из него. Для этого я написал ещё одну npm библиотеку date-parser. Она принимает строку и возвращает массив объектов класса ParsedDate (подробная информация в readme библиотеки). Каждый из этих объектов представляет собой одно событие и содержит в себе его дату и описание. Таким образом библиотека способна извлекать сразу несколько напоминаний из строки за раз.

Использование date-parser

Библиотека также поддерживает повторяющиеся напоминания и ограничения времени для них, как например: «Разминка каждые 30 минут до 8 вечера».

Процесс парсинга разделён на три стадии: упрощение, поиск и формирование.

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

(Слову "завтра" соответствует символ A, предлогу "в" – p, "6" – n, "вечера" – O. "Сходить" и "магазин" не были найдены в словаре – они помечены точками)

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

Поиск ключевых слов с помощью регулярных выражений

В конце, после извлечения времени, мы формируем описание из незадействованных слов:

Описание формируется из оставшихся слов

Эта самая важная и основная часть парсинга. Настоящая библиотека date-parser устроена в разы сложней. Я не стал углубляться в технические нюансы, потому что этого алгоритма будет уже достаточно для мощного, но в то же время простого парсера даты.

Настройка часового пояса

Последняя, но не менее важная часть создания напоминаний это использование часового пояса. Для настройки часового пояса я сделал две опции.

Отправить геолокацию

Если вы пишите боту с смартфона, тогда вы можете настроить ваш часовой пояс в 1 клик: просто отправьте свою геолокацию и бот автоматически определит ваш часовой пояс.

Для определения часового пояса по долготе и широте бот использует сайт GeoNames.

Написать вручную

Или же, если вы используете Telegram с ПК, вы можете сами написать часовое смещение в формате ±ЧЧ:ММ.
Эта опция также доступна на мобильных устройствах.

Изменения в будущем

Даже имея столько полезных свойств, «Умному планировщику» ещё есть куда расти. В планах есть ещё множество улучшений, вот основные из них:

  1. Интеграция бота с Google календарём пользователя.
  2. Настройка индивидуального времени для неявных ключевых слов, как «утро», «день», «вечер» и «ночь».
  3. Улучшение интерфейса бот-человек при помощи замены большинства текстовых команд удобными встроенными клавиатурами.

Прочие мелкие доработки описаны в списке на Github’е проекта.

"Умный планировщик" — это открытый проект с открытым исходным кодом, так что не стесняйтесь сообщать о любых ошибках или предлагать улучшения, я буду премного благодарен.

Спасибо за внимание, надеюсь у вас появится больше свободного времени с моим ботом!

З.Ы. Это перевод и адаптация моей статьи с английского языка. Оригинал вот.

0
67 комментариев
Написать комментарий...
Валентин Хирш

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

Ответить
Развернуть ветку
Maksim Isaev

Не совсем так на самом деле, если дата и время разделены в тексте - Todoist берет последнее для преобразования

Ответить
Развернуть ветку
Семен Смирнов

Ну пишите вместе
Это реально не проблема, учитывая удобство во всем остальном

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

Представляю как перехожу с него на костыльного бота с одними напоминаниями и трепло доской :)

Ответить
Развернуть ветку
Maksim Isaev

Да, с этим я абсолютно согласен - в этом плане Todoist крутая штука, сам пользуюсь им и явно переходить на что-то другое пока не планирую.  Я просто отметил пример постановки задачи (напоминания) у бота и приложения, то что сам пакет date-parser не плохо реализован - добавил себе его в закладки. 

Думаю попробовать его применить к Алисе от Яндекс, чтобы там с помощью пакета реализовать распознавание даты и текста, и добавлять задачу в Todoist через их API :)

Ответить
Развернуть ветку
64 комментария
Раскрывать всегда