Telegram бот для периодической отправки контента файлов сообщениями
В комментариях к одной из статей про Telegram CLI товарищ под ником Aluminium задался вопросом отправки контента небольшого лог-файла в чат телеграма. Как мне показалось, использовать Telegram CLI и демона для этой задачи чересчур. Для таких целей идеально подходит бот, которого написать достаточно просто. Из этого всего получился такой простенький бот. Если вам интересно, как это всё рождалось, прошу в пост.
Вместо вступления
Как раз сегодня я решил начать подбирать себе какую-нибудь систему управления временем: таймменеджмент, если по-русски. Первое, что пришло мне в голову - заезженная Pomodoro: вы определяете время для работы, в течении которого вы исключительно работаете, и такое же время для отдыха.
Я выбрал на сегодня 5 задач и выделил на каждую из неё по часу. Одна из этих задач - написание такого бота.
Quidquid latine dictum sit, altum sonatur.*
*Все, что сказано на латыни, звучит как мудрость
Главный вопрос при начале проекта - какой язык использовать? Изучить что-то новое, или писать на старом? Я решил попробовать попробовать мейнстримовый NodeJS. Убил пол часа на попытки запустить какой-нибудь hello world на этом добре. Но не судьба - времени в обрез.
Я вспомнил, что когда-то баловался с ботами для Telegram на Python. Что ж, эксгумировал старые сорцы и освежил память. В том скрипте использовалась обертка над API Telegram ботов python-telegram-bot. На нём я и остановился.
Собрал для начала из кусков разных примеров из официальной репы некую основу и понеслась.
Чё почём
Разберёмся для начала с конфигом. Сначала он был написан в json формате, но в нём нельзя писать комментарии, поэтому отказался от него в пользу YAML.
Комменты там на английском, потому что я выпендриваюсь это универсальный язык и я стараюсь писать все доки и ридмики для своих наработок на нём, чтобы охватить бОльшую аудиторию. Но коли этот блог ведётся на Великом и Могучем, считаю своим долгом перевести.
# Здесь прописывается токен Telegram бота, который вы
# должны получить при создании бота у @BotFather'a
# Выглядеть о будет как-то так
token: 000000000:AeAaAfAeOmgAAAdfAaAAFmEexzYNMBrHUAw
# Здесь задаётся интервал отправки контента из файлов в секундах
# По умолчанию - 4 часа
interval: 14400
# Список отслеживаемых файлов.
# Содержимое этих, и только этих, файлов будет отправляться вам в чат
files:
- .gitignore
- config.yml
# Список имен пользователей, которые могут общаться с ботом и получать контент файлов
users:
- telegram_user_name
Первым делом
Первым делом я написал вывод файлов по команде /cat
. Бот, получив такую команду прочитает все файлы из списка и поочереди выдаст их в чат.
Тут был небольшой подводный камень - Телега не может перевозить сообщения тяжелее 4096 байт, поэтому при попытке отправить прочитать большой файл всё накрывалось медным тазом.
Странно, что python-telegram-bot не решает это из коробки, но написать своё решение не составило труда: бьём файл на куски по 4КиБ и отсылать отдельными сообщениями.
Вот кусок кода, который это делает.
with open(filename, 'rb') as file:
data = []
while True:
data_chunk = file.read(4096)
if not data_chunk:
break
data.append(data_chunk)
Можно, конечно, не запихивать сначала весь файл в массив, а сразу слать кусками. Но файл может быть очень большим и передача может прерваться где-нибудь на середине. Уж лучше ничего. Но всё в ваших руках - можете переписать под свои нужды :)
Ещё один момент - чтобы Telegram никак не козявил содержимое наших файлов, его нужно перед отправкой обернуть в три Markdown’овские тильды, тобиш ```контент```. Но при тестах обнаружился один косячок: у многострочных файлов Телега обрубала первую строку. Причем по какому-то непонятному признаку.
Чтобы этого избежать, до и после контента нужно поставить символ новой строки. И у нас получается ```\nконтент\n```. И, да, нужно ещё прописать, что сообщение уходит в формате Markdown.
А вот строчка кода, которая отправляет мессагу таким образом:
bot.sendMessage(chat_id, parse_mode="Markdown", text="```\n%s\n```" % data_chunk)
Вторым делом
Далее я реализовал отправку содержимого файла с заданным интервалом.
Библиотека-обертка работает асинхронно, поэтому ни о каких while True
с счетчиками и речи быть не может. (Такое вообще нельзя писать никогда).
К счастью, умный люди уже за нас подумали и написать очередь задач JobQueue. И он нам идеально подходит.
Создаём объект Job с нужными параметрами и запихиваем его в очередь:
notification_job = Job(callback_cat, config['interval'], context=chat_id)
job_queue.put(notification_job, next_t=0.0)
2 важных момента:
context=chat_id
- вызов функцииcallback_cat
, как понятно из названия, будет производиться асинхронно, поэтому я нужно сохранить информацию о чате, в котрый нужно отправить сообщение.next_t=0.0
- указывает, что первый разnotification_job
нужно выполнить сразу.
Чтобы это добро начало нам слаться каждые interval
секунд, нужно написать в чат команду /start
.
Кстати, в доке секунды прописывались типом float, но int тоже сканал.
Напоследок
Допилил ещё парсинг аргументов к команде /cat
, так что ей можно передавать названия файлов через пробел и она выведет только их. Допустим запрос только файлов, указанных в конфиге(да, я параноик).
Чтобы остановить отправку файлов достаточно написать в чат /stop
. Но даже после этого можно вручную запрашивать содержимое файлов с помощью /cat
Ещё момент: в терминал при запуске ничего выводиться не будет, все логи будут писаться в файл bot.log
, который при каждом запуске будет перетираться. Так что не откладывайте напильник далеко - он вам пригодится :)
TODOшечка
Обернуть это дело в демона и прописать его в автозапуск я оставлю вам на домашнее задание. Я пока для себя не придумал способов применения этого бота, поэтому писать демона просто не вижу необходимым. А как это сделать под Fedora мы уже разбирались здесь.
Кстати, о помидорах: в конце первого часа мне казалось, что вот-вот ещё пять минут и тут всё будет готово. Короче, потратил я на это всё, вместе с написанием этой статьи, 4 часа. Систему нужно дорабатывать…
А в процессе написания приходили на ум всякие идеи, которые вы, мои дорогие читатели, при желании можете с легкостью дописать. А я просто оставлю эти идеи здесь, пусть они ждут своих героев :)
Добавить возможность редактирования конфига на лету с помощью чата. Я сразу этого не хотел делать, потому что ещё тогда ещё не придумал, как буду ограничивать доступ к доту. Хотя метод с именем пользователя выглядит достаточно надежно, но какой-нибудь злой дядька, получивший доступ к компу с открытым Telegram’ом может натворить дел, так что нужно секурить каким-нибудь паролем.
/unlock_config <password>
- включение возможности редактирования конфига из чата. Пароль, естественно, хранится в конфиге в хэшированном виде, а после ввода пароля сообщение нужно удалять :)/set_interval <interval>
- изменение интервала отправки сообщений/add_file <path>
и/remove_file <path>
- добавление\удаление файлов в\из списка допустимых./head
и/tail
- аналоги соответствующих комманд в *nix подобных системах - выводить кусок с начала или с конца файла соответсвтенно./watch <path>
- наблюдать за файлом и отсылать изменения в реальном времени. Соответственно, нужен/unwatch <path>
/lock_config
- после всех махинаций нужно залочить конфиг обратно.
Много чего ещё можно придумать - всё в ваших руках. Пользуйтесь на здоровье и обязательно пишите комментарии - меня они очень вдохновляют :)
До скорых встреч!