Telegram Bot

Telegram Bot


Постановка задачи и первичные варианты решения

В связи с ежедневными вечерними (да ещё и постоянно в разное время) обновлениями расписания в ОГАПОУ «Белгородский индустриальный колледж» необходимо программное обеспечение (ПО), которое будет следить за расписанием и уведомлять при его изменении.

Да, в Интернете по созданию ботов в Telegram есть куча статей, но в данной статье будет описан весь мой путь до стабильной и бесплатной работы бота в мессенджере Telegram.


Процесс разработки бота

Создание нового бота в BotFather

Для начала необходимо создать нового бота и автоматически получить токен этого бота у создателя ботов в Telegram @BotFatherСправка по командам была представлена в пункте «Сервисы для разработки – Telegram» этой статьи. Также сразу можно изменить название бота, описание бота, информацию о боте, фото профиля бота.

Запишем полученный токен в файл конфигов configs.py:

# токен бота из @BotFather
TOKEN = '2118918752:token'

Сам токен скрыт в этой статье, чтобы предотвратить несанкционированное использование моего бота.

Создание нового приложения на Heroku

После регистрации на Herokuсоздадим новое приложение в регионе Европа. На выбор предлагался ещё регион Соединённые Штаты. Подробнее про регионы для приложений Heroku – развертка в различных географических регионах.


Окно создание нового приложения на Heroku

Классическая структура бота Telegram

Присвоим токен, инициализируем обработчик входящих сообщений. Напишем обработчик для команды /start – глобальной команды, с которой начинается общение любого пользователя с любым ботом Telegram. В конце запустим режим длительного опроса. Таким образом создадим классическую структуру бота в основном файле bot.py:


Код Python


Блок if (__name__ == '__main__'): позволяет выполнять вложенные инструкции только при запуске самого файла, а не кода из импортированного модуля. Подробнее можно прочитать в статье «Зачем нужен if __name__ == '__main__' ?».

Создание модуля парсера данных с сайта «Расписание занятий»

Сайт – «Расписание занятий»

Создадим конструктор __init__, в который будет передаваться URL расписания. Конструктор класса – метод, который автоматически вызывается при создании объекта этого класса. Хотя в Python правильнее называть конструктором метод __new__. Но принимать параметры нужно именно в методе __init__, ведь задача этого метода как раз изменить новое состояние вновь созданного экземпляра класса. Метод __init__ не должен ничего возвращать, чтобы не вызвать ошибку. Параметр self это ссылка на конкретный экземпляр класса. Для обращения к переменным экземпляра всегда нужно дописывать selfself.url. Начало создания модуля в файле BIKParser.py:

Код Python


Для удобного использования классов и методов необходимо грамотно заполнять docstring – строки документации. Заполняется в комментарии вида '''docstring''' или """docstring""" сразу после объявления класса или метода. Обратиться к такой строке документации можно через атрибут __doc__. Для заполнения таких строк применяется язык разметки – reStructuredText. Результат документации с синтаксисом выше при наведении на метод:

Документация при наведении

Вызов методов внутри класса тоже производится через self.

Реализуем метод заполнения расписания FillFileSchedule:

def FillFileSchedule(self):
  '''Заполнение файла расписания'''

  # заполнить файл старого расписания
  f = open('old_schedule.txt', 'w')
  f.write(str(self.new_schedule))
  f.close()

Реализуем метод проверки расписания CheckChange. Получаем страницу расписания, выделяем все строки таблиц. Выделяем только нужные данные из всех данных парсинга, сразу же дополняя в начале <table><tbody> и в конце </tbody></table> для формирования полноценной HTML таблицы. Далее необходимо выполнить проверку на наличие изменений в расписании по отношению к прошлому сохранённому расписанию. В случае изменения расписания необходимо перезаписать старый файл расписания и изменить переменную результата на True.

Код Python


Для отправки картинки расписания нужно создать соответствующий метод ChangeImage.

Код Python


Так как бот планируется быть размещённым на Heroku, а там файлы приложения хранятся в директории app, то эту директорию необходимо указать в качестве выходного пути для модуля HTML2Image, импортированного нам в код, как HTI.

Также стоит обратить внимание на строчку из официальной документации модуля HTML2Image, которая была представлена в пункте «Описание используемых пакетов Python – HTML2Image» этой статьи:

However default flags are not used if you decide to specify custom_flags or change the value of browser.flags:

В которой говориться об отмене флагов по умолчанию при использовании пользовательских флагов или изменении значений флагов браузера. В таких случаях нужно будет не забыть вернуть флаги по умолчанию: --default-background-color=0 (объяснение флага) и --hide-scrollbars (объяснение флага).

Создание модуля по работе с БД PostgreSQL

В ресурсы приложения на Heroku необходимо добавить дополнение Heroku Postgres для добавления БД к приложению. При добавлении выбираем бесплатный план Hobby Dev.


Окно добавления БД PostgreSQL к приложению

Добавим в файл конфигов configs.py идентификатор упрощённого подключения к БД PostgreSQL:

# идентификатор упрощённого подключения к БД PostgreSQL
DB_URI = 'postgres://user:password@host:port/database'

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

Стоит обратить внимание на фразу из окна настроек БД на Heroku:

Please note that these credentials are not permanent. Heroku rotates credentials periodically and updates applications where this database is attached.

Из которой понятно, что учётные данные для подключения не постоянные и в ходе обслуживания БД Heroku меняет эти данные. Об обслуживании на электронную почту аккаунта Heroku приходит письмо такого содержания:

Your database DATABASE_URL on bikbeepbot requires maintenance. During this period, your database will become read-only. … We expect maintenance to last just a few moments depending on the size of your database. We will notify you when maintenance begins, and again once it's complete.

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

Для создания таблицы в БД необходимо в pgAdmin 4 подключиться к выделенной нам БД по предоставленным учётным данным.

Главное окно создание сервера
Окно настройки соединения сервера

Далее необходимо в обозревателе найти и развернуть свою БД создать схему и уже в ней создать таблицу. Далее пользуясь кнопкой «+» добавляем два столбца. Поле «По умолчанию» заполнится автоматически после сохранения при выборе серийного типа данных. Также после сохранения серийный тип данных преобразовывается в соответствующий ему тип данных целых чисел.

Окно создания и настройки столбцов таблицы

Повторно открыть это окно можно кликнув правой кнопкой мыши (ПКМ) по таблице в обозревателе.

Так как моим ботом не будет пользоваться большое число пользователей, то я выбрал тип данных с диапазоном от 1 до 32767. Как сказано в официальной документации API ботов Telegram, представленной в пункте «Сервисы для разработки – Telegram» этой статьи:

Unique identifier for this user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a 64-bit integer or double-precision float type are safe for storing this identifier.

Для уникальных идентификаторов пользователей безопасно использование 64-битных целых чисел, которым и является bigint в БД PostgreSQL.

Создадим конструктор __init__модуля, в который будет передаваться идентификатор упрощённого подключения к БД PostgreSQL. Начало создания модуля в файле SQLRequests.py:

Код Python


Далее необходимо реализовать метод проверки наличия пользователя в БД user_exists и метод добавления пользователя в БД user_add:

Код Python


Для корректного выполнения запроса название таблицы БД обязательно должно быть в двойных кавычках "TableName". В запросах применяются f-строки появившиеся в Python 3.6. Статья на русском по форматированию строк и с примерами f-строк: f-строки в Python 3.

Для рассылки боту необходимо получить идентификаторы пользователей, для этого реализуем ещё один метод get_users:

Код Python


Хоть разница в методах получения результатов fetchone( ) и fetchall( ) очевидна из названия, но подробнее можно почитать в официальной документации модуля psycopg2, представленной в пункте «Описание используемых пакетов Python – psycopg2» этой статьи.

Подключение и использование созданных модулей

Изначально надо инициализировать созданные модули. Модернизируем файл bot.py:


Код Python


Запоминать идентификаторы пользователей будем при старте общения пользователя с ботов в обработчике команды /start. Для этого в метод welcome добавим проверку на наличие пользователя в БД:

# проверка на наличие пользователя в БД
if (not db.user_exists(message.from_user.id)):
  # добавление пользователя в БД
  db.user_add(message.from_user.id)

Проверять изменение расписания, формировать изображение с расписанием, а затем получать идентификаторы пользователей и отправлять им изображение с расписанием под циклом while True: будем в методе scheduled:

Код Python


Так как метод get_users() возвращает лист кортежей, то в цикле for необходимо выбирать идентификаторы пользователей, которые в кортеже (0, 1) идут под номером 1. Под номером 0 будет первичный ключ – столбец id, являющийся автоинкрементом.

После запуска бота и начала общения с ботом проверить содержимое столбцов таблицы БД можно в pgAdmin 4, кликнув ПКМ по таблице и выбрав «Просмотр/редактирование данных – Все строки» или выбрав «Запросник» и написав запрос:

SELECT * FROM bikbeepbot."UsersBD"

Нажав кнопку выполнения запроса «▶» (треугольник вправо) внизу отобразится результат запроса.

Результат выполнения запроса

Развёртка бота на Heroku

Стек – образ ОС поддерживаемых Heroku. Стеки основаны на дистрибутиве Linux – Ubuntu. Heroku на текущий момент предоставляет два стека, на котором может работает приложение: Heroku-18 и Heroku-20. Цифра в названии зависит от первых цифр версий Ubuntu: Heroku-18 основан на Ubuntu 18.04 и закончит поддержку в апреле 2023 года, а также Heroku-20, поддерживающий Python 3, основан на Ubuntu 20.04 и закончит поддержку в апреле 2025 года. Приложения по умолчанию размещаются на Heroku-20.

Перед развёрткой бота необходимо создать 3 служебных файла в корневой директории, а также выбрать необходимые сборочные пакеты.

Служебные файлы

  1. Файл runtime.txt содержит среду выполнения Python и точное название версии.
python-3.10.0

2. Файл requirements.txt содержит зависимостей для приложения – сторонние пакеты, используемые в исходном коде нашего приложения.

aiogram
requests
bs4
html2image
pillow
psycopg2

3. Файл Procfile содержит команды, выполняемые приложением при запуске. Строки файла должны иметь следующий формат: <process type>: <command>.

worker: python bot.py

Сборочные пакеты

Добавление сборочных пакетов для приложений происходит в разделе «Настройки».


Окно добавления сборочных пакетов
  1. Сборочный пакет для приложений Python heroku/python.
  2. Сборочный пакет для драйвера браузера Chrome https://github.com/heroku/heroku-buildpack-chromedriver.git.
  3. Сборочный пакет для браузера Google Chrome https://github.com/heroku/heroku-buildpack-google-chrome.git. Данный сборочный пакет включает в себя в строке 183 файла /bin/compile флаг --remote-debugging-port=9222 включающий удалённую отладку и запрещающий делать снимок экрана. Для исправления этой ошибки воспользуемся ответвлением этого сборочного пакета от aurelmegn https://github.com/aurelmegn/heroku-buildpack-google-chrome.git.

Выполнение развёртки приложения

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

Справка по команде:

Для входа в Heroku CLI выполним команду heroku login, которая откроет окно браузера по умолчанию для выполнения авторизации на Heroku.


Окно авторизации в браузере, в котором уже выполнен вход в Heroku

Справка по командам Git представлена в пункте «Сервисы для разработки – Git» этой статьи.

Создадим Git-репозиторий командой git init. Выполняется один раз при первой развёртки приложения.

Для отслеживания новых, изменённых и удалённых файлов в текущей директории используется команда git add ..

Для фиксации изменений используется команда git commit -m "First release". Для повторных фиксаций изменений правильнее менять сообщение в кавычках.

Просмотр адресов удалённых репозиториев может осуществиться с помощью команды git remote -v. Команда не является обязательной.

Для отправки изменений в удалённый репозиторий выполним команду git push heroku master.



Результат работы

Для просмотра состояния приложений Heroku можно воспользоваться командой heroku ps.

Запустим приложение, выполнив команду heroku ps:scale worker=1.

Разработанный бот в Telegram – @BIKbeep_bot.

Пример работы бота
Пример работы бота

Для остановки приложения можно выполнить команду heroku ps:scale worker=0 -a bikbeepbot.

Чтобы получить исходное содержимое репозитория приложения можно осуществить клонирование репозитория по пути из которого выполнится команда heroku git:clone -a bikbeepbot.



Ошибки

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

Журнал приложения

app[worker.1]: [1218/191809.376330:ERROR:bus.cc(393)] Failed to connect to the bus: Failed to connect to socket /var/run/dbus/system_bus_socket: No such file or directory

app[worker.1]: [1218/191809.480389:ERROR:sandbox_linux.cc(376)] InitializeSandbox() called with multiple threads in process gpu-process.

app[worker.1]: /app/bot.py:67: DeprecationWarning: There is no current event loop
app[worker.1]:   get_event_loop().create_task(scheduled(15))

Последующие ошибки (от 11 марта 2022)

Вследствие последних политических событий ОГАПОУ «Белгородский индустриальный колледж» ограничил доступ к своему сайту странам Европы, где и размещается бот. Для решения данной проблемы было принято решение воспользоваться сервисом онлайн-просмотра кода файлов по URL от Дмитрия Елисеева.

После небольших правок в методе проверки расписания CheckChange бот продолжил свою работу. Во всей статье код метода CheckChange представлен без этих правок.



Исходный код

Файл configs.py:


# токен бота из @BotFather
TOKEN = '2118918752:token'

# идентификатор упрощённого подключения к БД PostgreSQL
DB_URI = 'postgres://user:password@host:port/database'

Файл bot.py:

Код Python


Файл BIKParser.py:

Код Python


Файл SQLRequests.py:

Код Python


Файл runtime.txt:

python-3.10.0

Файл requirements.txt:

aiogram
requests
bs4
html2image
pillow
psycopg2

Файл Procfile:

worker: python bot.py




Report Page