В моём демо есть обычная кнопка отправки формы. За ней стоят два действия — записать заявку и сообщить о ней владельцу. В описании услуги их легко произнести на одном дыхании, хотя для программы это отдельные операции, и успех первой ещё ничего не говорит об успехе второй. Разбираю обработчик собственного демо по порядку строк. Сначала заявка попадает в базу, получает номер и становится частью таблицы. Только после этого бот пытается отправить сообщение в Telegram.
Разница занимает несколько строк кода. Для заявки — целую судьбу.
Сама заявка в этом сценарии начинается на странице сайта. Форма из раздела демо снаружи выглядит проще дверного звонка, но обработчику всё равно нужно понять, кому принадлежит входящая запись. Когда человек открывает раздел «Сайт и таблица», для него уже есть ссылка с токеном. Если токена ещё нет, бот создаёт его, связывает с пользователем и подставляет в адрес формы и браузерной таблицы. Токен здесь похож на номер ячейки в камере хранения — форма знает, в чью таблицу положить запись и кому потом послать уведомление, не спрашивая об этом посетителя.
Человек вводит имя и сообщение. Обработчик берёт из запроса токен, имя ограничивает сорока символами, текст — тремястами. Пустая или почти пустая форма не проходит: в имени и сообщении должно быть хотя бы по два символа. Просроченная ссылка получает ответ с кодом 404, незаполненная форма — 400. До Telegram дело в этих случаях вообще не доходит.
Есть ещё предохранители, потому что публичная форма без них быстро превращается в кормушку для роботов. В демо разрешено не больше десяти заявок в час для одного токена и не больше двадцати для одного IP-адреса; в браузерной таблице помещается тридцать строк, а после заполнения обработчик отвечает кодом 429. Это не производственная норма и не совет для любого сайта, а границы моего конкретного стенда — посетителю хватает, а бесконечный склад тестовых сообщений мне не нужен.
Если проверки пройдены, происходит главное. Обработчик добавляет в SQLite строку со временем, именем, источником «Сайт» и текстом заявки; вставка выполняется внутри транзакции базы, поэтому к моменту следующей команды запись уже сохранена и получила собственный номер. Именно этот номер Telegram-бот подставляет в уведомление владельцу.
Сообщение выглядит без церемоний — «Новая заявка с сайта», номер строки, имя, текст и кнопка открытия таблицы. Форма передала данные обработчику, обработчик записал их, а Telegram получил короткое извещение о том, где лежит запись; кнопка ведёт к той же таблице, для которой был создан токен. Если нужны уведомления с сайта в телеграм, сначала стоит договориться именно об этом порядке, а не начинать с цвета кнопки и торжественного текста «Спасибо, мы скоро свяжемся».
И вот здесь в коде обнаруживается развилка. Отправка сообщения может завершиться ошибкой. Например, бот не запущен, пользователь запретил ему писать или Telegram отклонил запрос как некорректный. Для трёх предусмотренных в обработчике типов ошибок демо пишет событие в журнал, но всё равно возвращает странице `ok: true`.
На первый взгляд нелогично — сообщения нет, а ответ успешный. Но успешной в этот момент считается не доставка уведомления, а приём заявки: строка записана раньше и остаётся в таблице, даже если телеграмная дверь захлопнулась. Для посетителя это тонкость невидимая, для владельца — разница между «данные сохранились, надо проверить канал оповещения» и «человек нажал кнопку, но от его сообщения не осталось ничего».
Я не выдаю это за универсально правильное поведение. Если Telegram — единственный рабочий экран владельца и таблицу никто не открывает, тихое исчезновение уведомления опасно; если главная задача — не потерять данные формы, а Telegram служит только звонком над дверью, порядок выглядит разумнее. Не знаю, какой вариант лучше без конкретного процесса — это решается не языком программирования, а ответом на вопрос, что для бизнеса считается принятой заявкой.
У моего демо есть ещё одно ограничение, которое легко не заметить по красивой кнопке «Открыть таблицу». Данные демонстрации удаляются через сутки — это стенд для знакомства, не архив заявок. Таблица здесь браузерная. Обработчик читает строки из своей SQLite и отдаёт их странице по порядку добавления. Это не Google Таблицы и не CRM. Связь с ними на сайте упомянута как отдельная интеграция через API, а не как скрытая способность этой демонстрационной страницы.
Для заказа такой связки я предлагаю сначала определить, где должна жить исходная запись, что показывать человеку при сохранённой заявке без сообщения, кто заметит молчание Telegram и нужен ли отдельный статус доставки. Ответы меняют архитектуру сильнее, чем выбор библиотеки. В текущем демо автоматического повтора нет, и придумывать его задним числом было бы нечестно.
Можно сделать иначе — сначала отправить сообщение, потом сохранить строку. Тогда сбой Telegram остановит весь запрос, зато вместе с уведомлением рискует пропасть и сама заявка. Можно сохранить запись и отдельно поставить уведомление в очередь, где оно дождётся следующей попытки; это уже другая схема, с состоянием доставки, расписанием повторов, пределом попыток и местом, где человек увидит окончательный сбой. Она может быть нужна реальному проекту, но в моём демо её сейчас нет.
Есть и менее заметная граница. Обработчик ловит не любую возможную ошибку, а только названные в коде TelegramForbiddenError, TelegramBadRequest и RuntimeError. Для них действует описанный сценарий: ошибка записывается в журнал, строка остаётся, форма получает успешный ответ. Обещать то же самое при любой аварии сервера нельзя. Код таких обещаний не даёт.
Поэтому я разделяю две фразы, которые в описании услуги звучат почти как одна. «Заявка принята» означает, что данные прошли проверки и записаны; «владелец уведомлён» — что Telegram отдельно принял сообщение. В спокойный день эти события идут рядом, и пользователь разницы не видит, но она появляется как раз в тот день, ради которого и пишут обработчики ошибок: сеть дёрнулась, бот лишился права писать, запрос оказался неприемлемым для Telegram.
При разобранной ошибке уведомления в браузерной таблице остаётся строка с именем, текстом и короткой пометкой источника — «Сайт».