С какими проблемами вы можете столкнуться и как их обойти
Тема ботов Telegram только развивается. Если вы разрабатываете дополнения к Google Sheets, вы пишете код Google App Script (GAS). Поэтому, скорее всего, мозги для этих ботов написаны на GAS.
Была идея написать бота, который будет искать ключевые слова в сообщениях чата и что-то делать с этими сообщениями. Например, присылать мне уведомление о том, что кто-то написал что-то на интересующую меня тему. Или, наоборот, удалять нецензурную лексику и спам, а авторов таких сообщений банить в чате.
Как всегда, в процессе практической реализации задуманного возникают подводные камни. О некоторых из них и способах их обхода я расскажу ниже. Но сначала немного теории.
getUpdates VS setWebhook?
Неважно, на каком языке вы пишете мозги для своего бота — GAS, PHP, Pyton или что-то еще. В любом случае, вы используете API Telegram.
В настоящее время API поддерживает два способа обработки обновлений бота: getUpdates и setWebhook.
getUpdates — это механизм pull, setWebhook — механизм push.
Например, вы запускаете getUpdates и получаете все сообщения, написанные в чате за этот час. В GAS вы можете настроить временной триггер с таким интервалом. Но это означает, что для нашего примера с антиспам-ботом спам будет безнаказанно висеть в чате целый час. Это не очень хорошо, желательно отстреливать спам сразу при приближении.
Поэтому необходимо использовать setWebhook. Как сказано в «Замечательном руководстве Марвина по всем вещам Webhook»:
setWebhook, по сравнению с getUpdates,
- избавляет вашего бота от необходимости часто запрашивать обновления.
- Это избавляет бота от необходимости использовать механизм опроса в вашем коде.
setWebhook() + fetch(url)
Теперь несколько слов о механизме получения обновлений или, проще говоря, сообщений из чата Telegram.
- Напишем простейший скрипт в редакторе GAS:
function doPost(e) {
var contents = JSON.parse(e.postData.contents);
var chat_id = contents.message.from.id;
// code for extracting data and writing to the sheet
}
Вот и все, у нас есть обновление в переменной contents. С помощью дальнейшей обработки из нее можно извлечь все, что нужно. Например, chat_id — идентификатор отправителя сообщения, text — текст сообщения.
Затем эти данные можно записать на лист, отправить кому-то и т.д.
-
Развернем скрипт как веб-приложение.
При этом указываем параметры «Run as: От моего имени, Кто имеет доступ: Everyone» и копируем ссылку на веб-приложение (webAppUrl). -
Формируем ссылку.
Для этого необходимо соединить токен бота и URL веб-приложения следующим образом: -
Получить url и установить webhook:
Вы можете перейти по этому url в браузере без какого-либо скрипта и увидеть
Это объект JSON, разобрав который, вы можете извлечь все 3 поля по отдельности — ok, result и description. Затем их можно использовать в условной логике, показывать пользователю и все такое.
Подводные камни
Итак, у нас есть скрипт, который мгновенно записывает все сообщения из чата Telegram на лист. Затем вы можете искать в них ключевые слова и что-то делать с этими сообщениями.
Но представьте, что вы, восхищенные, выложили ссылку на этого своего бота в открытый доступ и ожидаете, что довольные пользователи поблагодарят вас за полезный инструмент.
Но не тут-то было!
Всех устраивают сервисы Google. И листы они вам предоставляют, и среду разработки скриптов, и мощные серверы по всему миру, на которых эти скрипты работают, и много других вкусняшек. Однако есть и подводные камни, и один из них — квоты и лимиты Google. «Apps Script Services» имеет ежедневные квоты и лимиты на некоторые функции. Если вы превысите квоту или лимит, ваш скрипт выдаст исключение и выполнение прекратится». Например, вызовы URL Fetch ограничены 20 000 в день, а лимит на одновременное выполнение сценариев составляет 30 пользователей. Это означает, что если количество пользователей бота станет достаточно большим, он перестанет работать.
И что же делать?
Например, можно купить собственный сервер или арендовать сервер и обрабатывать всю логику на нем. Это стоит дополнительных денег и времени, поэтому логично подумать о чем-то другом.
Выход
Разработчик может придумать следующее.
Поскольку веб-приложение работает от моего имени, мои квоты расходуются. Но что если развернуть его не от моего имени, а от имени пользователя?
Заинтересованный пользователь может сделать это в 3 клика. Вам просто нужно дать ему шаблон листа с внедренным в него скриптом. Он сделает копию листа для себя, развернет скрипт как веб-приложение и вставит webAppUrl в нужную ячейку.
Теперь квоты и ограничения будет потреблять не разработчик, а пользователь.
И очень маловероятно, что у этого пользователя в группе Telegram будет более 20 000 сообщений в день.
Вроде бы все работает.
Но не тут-то было!
Снова подводные камни
Каждый разработчик хочет, чтобы плоды его творчества были полезны. Как для него, так и для пользователей.
Как распространить наш скрипт, чтобы о нем узнали заинтересованные пользователи? Можно писать статьи на тематических сайтах, размещать рекламу в Google и т.д.
Все это замечательно, но грех не воспользоваться бесплатными инструментами.
Говоря об инструментах Google, нельзя не упомянуть Google Workspace Marketplace. Там разработчики Google публикуют дополнения для продуктов Google — для листов, документов, почты и т.д.
Таким образом, идея реализации нашего Telegram-бота выглядит следующим образом:
-
Мы создаем шаблон листа Google.
В нем рисуется интерфейс с необходимыми заголовками, к таблице прикрепляется скрипт веб-приложения. -
Всю логику обработки сообщений из веб-приложения мы переносим в дополнение и публикуем его на Google Workspace Marketplace.
-
Пользователь устанавливает дополнение, делает для себя копию шаблона, выполняет начальные настройки (вводит в нужные ячейки токен бота, URL веб-приложения, свои ключевые слова) и пользуется в свое удовольствие.
Да, здесь мы переложили часть работы на пользователя, но именно для этого я и пишу эту статью. Возможно, кто-то из читателей предложит обходные пути.
Тем более, что не тут-то было, здесь еще есть подводные камни.
Снова
Проблема в том, что вы НЕ можете передать данные напрямую в дополнение НИКАКИМ способом. То есть, вы получили сообщение через webhook, но вы не можете отправить это сообщение в обработчик, который, напомню, находится в add-on.
Вы не можете запустить функцию, которая находится внутри дополнения.
Вы даже не можете передать какой-то тег или сигнал типа «Hey add-on! Есть сообщение! Запустите fetch(url) и прочитайте!».
Никогда. Я не знаю почему, то ли из соображений безопасности, то ли по какой-то другой причине. Вы можете прочитать об этом здесь: Как передать данные из webapp в аддон.
В голову может прийти мысль: «Мы пишем сообщения на листе. Поставим триггер onEdit() или onChange(), и как только сообщение будет записано на лист, триггер сработает, прочитает данные и обработает все как надо».
Здесь есть две загвоздки.
Одна из них заключается в том, что все операции ввода-вывода типа spreadsheet относительно медленные — это доли секунды. При интенсивном общении в чате Telegram многочисленные вызовы к листу и из листа могут вызвать значительные задержки.
Иначе обстоит дело, если данные передаются в обход листов, напрямую из fetch() в код дополнения. Серверы Google работают быстро, и такие операции занимают миллисекунды.
Вторая загвоздка заключается в том, что триггеры onEdit() или onChange() НЕ срабатывают при программном изменении данных на листе.
Эти триггеры срабатывают, если данные на рабочем листе были изменены пользователем. Например, введены с клавиатуры или выбраны из меню.
Подробнее об этом здесь: Объекты событий.
Есть способ!
Ответ на вопрос, заданный в посте на Stack, все-таки был найден. Автор даже назначил баунти за правильный ответ, но лучшее, что ему тогда предложили, — воспользоваться облачным сервисом Google. Да, среди вкусных плюшек есть и такая. И это, безусловно, мощная штука.
Вот только оплачивается он при превышении определенных показателей.
А хороших руководств по его использованию я не встречал.
По GAS таких ресурсов предостаточно. Это и Google Apps Script, и mdn web docs, и еще куча других.
В Google Cloud таких ресурсов гораздо меньше.
Поэтому, когда через полгода на пост пришел Алан Уэллс и написал про Sheets API и USER_ENTERED, все сразу стало понятно.
Схема следующая:
-
Включите Sheets API в службах веб-приложений. Проверить, что все правильно, можно, если посмотреть на код в файле appsscript.json. Если кто забыл, то для этого в настройках скрипта установите флажок «Показывать файл манифеста appsscript.json в редакторе».
Он должен содержать следующий код:В дальнейшем, когда пользователь будет делать копию вашего шаблона, этот сервис будет включен в его копию скрипта.
-
Как мы обычно записываем данные на лист?
Мы используем setValue(value).
Например:
В огонь!
Теперь мы используем API Google Sheets.
Код должен выглядеть примерно так:
Здесь есть два ключевых момента:
A. Мы записываем данные в лист с помощью функции batchUpdate().
B. ‘USER_ENTERED’ как бы говорит триггеру, что данные на листе были изменены не программой, а пользователем.
Все, теперь триггер сработает. Данные, пусть и опосредованно — через лист, передаются в надстройку, и вся схема функционирует.
Заключение
Подробнее обо всем этом вы можете прочитать здесь: Telegram Assistant: Описание и руководство. Там же есть ссылка на дополнение и на электронную таблицу со скриптом. Вы можете использовать все это свободно.
А я буду благодарен за конструктивную критику и советы!