Если вам нужен простенький Telegram bot, способный выполнять скрипты (написанные на любом языке) и отвечать текстом и картинками, то вам под кат. Там вы найдёте рассказ о бот-движке, который делает то, что вам надо.
Краткий список возможностей движка:
- Движок может обслуживать сразу несколько ботов
- Бот выполняет скрипты (написанные на любом языке)
- Сообщение попадает на вход скрипту в виде аргументов и переменных окружения
- Вывод скрипта может быть текстом, форматированным текстом или изображением (распознаётся автоматически)
- Движок гарантирует поочерёдный запуск скриптов (при написании скриптов можно не думать про локи и конкурентный доступ к ресурсам)
- Бота можно дёргать по HTTP, чтобы отправлять сообщен не в ответ на запрос, а "асинхронно" (например по cron-у)
Движок максимально неприхотлив: ему не нужны базы данных, публичные IP-шники, SSL-сертификаты… Можно просто запустить на лаптопе, сидя за НАТом с наглухо закрытыми портами. В общем, начать экспериментировать вы можете прямо не сейчас, не отрываясь от чтения.
Сейчас я покажу, как это всё запустить и оживить.
Сборка
Вам понадобится язык Go. Чтобы его поставить, не нужны даже root-права. Но, для простоты, далее, я буду предполагать, что он у вас стоит в системе.
Скачиваем и собираем проект:
cd tmp
git clone https://github.com/michurin/cnbot.git
cd cnbot
go build ./cmd/...
./cnbot
При запуске без параметров (последняя команда) вы получите ошибку, что не указан конфигурационный файл. Это значит, что всё собралось правильно.
Начинаем разговор
Первым делом, вам надо зарегистрировать бота и получить для него токен. Это совсем не сложно: инструкция на сайте Telegram.
Создаём минимальный кофигурационный файл (config.yaml
):
bots:
firstBot:
token: "22222222:AAAAAAAAAAAAAA"
script: "/usr/bin/true"
Тут должен быть правильный токен и любой исполняемый файл в качестве скрипта (рекомендую выбить что-нибудь побезопасней, чем /bin/rm
). Проверяем настройки (-i
):
./cnbot -i -c config.yaml
Если токен правильный, вы получите отчёт о состоянии бота.
Запускаем бота (без -i
)
./cnbot -c config.yaml
Пытаемся добавить его в Telegram-клиенте. В логах бота видим ошибку
user 500050880 is not allowed
Это ваш user_id
(у вас он будет другой), добавляем его в конфиг
bots:
firstBot:
token: "22222222:AAAAAAAAAAAAAA"
script: "/bin/echo"
allowed_users: [500050880]
Обратите внимание, я прописал echo
в параметр script
. Это быстрый (хоть и кривоватый) способ сделать echo-бота. Вы уже можете поговорить с ним. Попробуйте сказать hi
, Hi!
, -n hi
.
Из подобного разговора сразу видно как легко получить уязвимость (-n
было интерпретировано как параметр echo
). Так же видно как формируются аргументы скрипта: сообщение приводится к нижнему регистру; допустимыми символами считаются буквы, цифры, минус, точка и подчёркивание; все недопустимые символы считаются разделителями.
Полное сообщение тоже доступно. Давайте заменим /bin/echo
на простой скрипт и посмотрим переменные окружения:
#!/bin/sh
env
Если сказать этому боту Hello!
Он покажет переменные окружения
BOT_TEXT=Hello!
BOT_FROM_FIRSTNAME=Alexey
BOT_NAME=firstBot
BOT_CHAT=500050880
BOT_FROM=500050880
Видно, что доступно оригинальное сообщение, имя и ID пользователя, который отправил сообщение, название бота (согласно конфигу; напомню, что движок может обслуживать сразу несколько ботов).
Если бот получает контакт или пересылку сообщения, то к переменным окружения добавляется информация о контакте или авторе оригинального сообщения. Это удобно, когда вы хотите добавить в white list нового пользователя. Чтобы узнать его ID — просто перешлите его контакт или любое его сообщение боту. См. пример в demo.sh.
Обратите внимание, что переменной PATH
не видно. Если вам нужны экзотические пути, пропишите их явно в начале скрипта или используйте полные пути.
Скрипт можно чуть усовершенствовать
#!/bin/sh
echo '%!PRE'
env | sort
Теперь вывод будет преформатированный. Можно использовать и markdown, см. всё тот же demo.sh.
Чтобы ответить картинкой, её достаточно просто вывалить на stdout
:
#!/bin/sh
curl -qfs https://golang.org/lib/godoc/images/footer-gopher.jpg
Если скрипт не выдаст ничего, то бот отправит сообщение "empty", чтобы бот действительно ничего не ответил, скрипт должен ответить одним единственным символом "точка".
Все примеры можно найти в demo-скрипте, а я бы чуть подробнее остановился бы на асинхронной нотификации.
Бот говорит сам
В боте можно включить HTTP сервер добавлением одной строки bind_address
в конфиг:
bots:
firstBot:
token: "22222222:AAAAAAAAAAAAAA"
script: "/bin/echo"
allowed_users: [500050880]
bind_address: ":9091"
Теперь вы можете отправить асинхронное сообщение:
echo "ok" | curl -qfsX POST --data-binary @- "http://:9091/500050880"
То есть, пользователь получит его не в ответ на своё сообщение, а просто как нотификацию. Тело сообщения обрабатывается по тем же правилам, что и аутпут скрипта. То есть, вы можете отправлять форматированный текст и картинки.
Можно использовать и multipart/form-data
:
curl -qfsX POST -F to=500050880 -F msg=ok "http://:9091"
Думаю, нет смысла обсасывать каждую деталь работы бота. У вас уже есть достаточно информации, чтобы понять, нужно оно вам или нет. Полный спектр возможностей можно посмотреть, поговорив с demo-сриптом. В конфиге можно указать таймауты для http-клиента и выполнения скрипта. Все опции есть в readme проекта, хотя, думаю, большинству будет комфортно жить и с дефолтами.
Любые вопросы, пожелания, предложения — приветствуются.
Приятного ботостроительства!
Если вы всё ещё читаете...
..., то могу рассказать, как я дошел до жизни такой.
Мы как-то делали очень глубокий редевелопмент системы. Фактически, мы запускали абсолютно новый проект сразу под огромной нагрузкой. Конечно, где могли, мы подстелили соломки. Но у нас не было ресурсов, что бы поддерживать на лету сразу два проекта такого масштаба. То есть мы не имели возможности быстрого переключения на старую версию.
В день старта я уходил домой очень поздно, но мне хотелось держать руку на пульсе постоянно. Мне не подходил сценарий: найти местечко в метро, достать ноут, поднять VPN, посмотреть логи… Хотелось иметь возможность глянуть основные вещи с телефона и, возможно, что-то подтюнить, сбегая по эскалатору.
Я гуглонул, что на это тему знает Интернет, и оказалось, что Telegram предоставляет бесплатное и великолепное API для ботов. Я написал бота-уродца в несколько строчек на bash+curl+jq, который умел выполнять буквально три команды, и поехал домой.
Бот оправдал себя полностью и идея мне очень понравилась. Только реализация полностью на шеле была уж очень кривая и неуклюжая. На досуге, я переписывал эту штуку несколько раз на ноде и питоне, но всё это были какие-то сырые поделки.
Тем временем, я начал использовать таких ботов во многих бытовых делах. У меня есть бот для учёта тренировок и отслеживания прогресса (графики), есть бот для управления домашним микротиком… В конце концов, у меня накопился очень чёткий список фичей, которыми должен обладать бот. А так же, список тупиковых идей, которые выглядят хорошо, но на деле мало полезны.
В 2018 я начал учить Go и, просто для эксперимента, заимплементил движок ботов на Go. Это был мой первый код на новом языке и он был ужасен :-) Но, глобально, Go оказался очень удобной штукой для подобных вещей. И вот, поднабравшись опыта в Go, я решал вернуться к этому проекту и переписать его на Go, но уже "правильно" (ну или близко к тому).
Так и появилось это поделье. Почему репа называется cnbot, я так и не смог вспомнить по прошествии лет.
Куда я планирую всё это развивать?
Я подумываю о расширении функциональности, но очень осторожно. Очень не хотелось бы переусложнять. Если вам нужно какое-то специфичное решение, — просто напишите своего бота. А этот движок я хотел бы оставить максимально простым.
Но я бы хотел развивать движок в сторону встраиваемости: выделить из него какую-то простую часть, которую можно было бы подключить библиотекой к любой Go-программе. Условно, если у вас уже есть микросервис для… для чего годно… хоть для рендеринга 3D-сцен, — вы можете в одну-две строчки встроить в него чат бота для оперативной диагностики/мониторинга/управления… Вот это, мне кажется, было бы полезно. На самом деле, вы уже сейчас можете так сделать. Просто это не очень удобно.