28.11.2018


Примечание от админа

Впервые публикую на страницах своего бложика гостевой материал. Такое волнительное событие! Сегодня речь пойдёт про бота, созданного другими людьми, но которым активно пользуются как люди в наших сообществах, так и ваш покорный слуга лично. Так что я подумал, что будет большим упущением не упоминуть про него в рамках данного цикла статей. К счастью, Ламппи Лутти с радостью согласился на моё предложение черкануть про бота пару строчек. Мне лишь оставалось немного помочь с редактурой и стилистическим оформлением. Прошу любить и жаловать!

Почему заголовок в этот раз не содержит юзернейма бота? Поймёте по ходу чтения статьи =)


Вступление

Всем привет, я Lämppi Lütti, и меня пригласили написать о моём боте в цикле статей о Telegram-ботах.


Бот, который нравился многим

Сначала это был бот для выполнения хотелок одного товарища из небезызвестной Telegram-конфы @kdr_flood. Одной из его хотелок было создание картинок-цитат из выбраного сообщения. Так как автор [яблочный пирог — прим. редактора] не мог и не хотел заниматься вопросами хостинга, бот часто лежал и постоянно приходилось ждать его запуска. После, к этому прибавились постоянные падения из-за попыток зацитатить нетекстовые сообщения. Так как эта функция забавляла некоторых обывателей конфы, то были предложения от @kozalo и @iosys о хостинге бота, если падения будут исправлены. Но автор не стремился что-либо исправлять и просто забил. Шли разговоры о том, что было бы неплохо воскресить бота, но лезть исправлять код никто не спешил. В конечном итоге залез я, благо автор оставил исходники в открытом доступе. Отсюда берёт начало Quote-maker (или @iosys_bot, так как я тоже не сильно хотел заниматься хостингом и просто отправил бота тому, кто предложил его захостить). Это был маленький бот, который понравился многим.


Бот, который использовали

Как говорилось ранее, новый бот — это всего лишь одна функция «зацитатить в картинку», поэтому количество команд у него равно двум: это /quote_ru — для цитирования с русским заголовком и /quote_en — для цитирования с английским заголовком. Наличие цитирования на английском — это не попытка выхода на международный рынок, а функция для тех ситуаций, когда цитируется сообщение на английском (чтобы было гармонично: английский текст с английским заголовком). Также были внесены небольшие нововведения:

  • исправлены падения от попыток цитирования нетекстовых сообщений;
  • размер картинки стал зависеть от количества текста;
  • небольшое изменение в рисовании.

Это был бот, который использовали.

Этот бот, как и его прародитель, написан на Python 3, с использованием библиотек pyTelegramBotApi (для работы с Telegram Bot API) и Pillow (для рисования). Сам код написан не в лучшем стиле. Кому интересно, вот репозиторий.


Бот, который был нужен только мне

После завершения работы над ботом на Python я нашёл для себя интересным язык Clojure (диалект LISP для JVM) и в качестве тренировки решил переписать бота-цитатера на него, попутно исправляя ошибки и добавляя некоторые «фичи». После окончания работы над ботом @iosys по надуманным причинам отказывал в хостинге, а после заявил, что бот на Clojure работает медленнее, чем на Python, да и к тому же требует больше оперативной памяти. Проблема с медленной скоростью действительно есть, хотя всё не так страшно. Дело в том, что для каждого сообщения боту приходится просчитывать координаты (это нужно для динамического растягивания картинки в зависимости от количества текста), но просчёт идёт один раз для одного и того же набора входных данных. То есть если бот получает сообщение размером, допустим, в 40 символов, то расчёты для примерно такого же по размерам сообщения будут взяты из кеша (особенность Clojure). Из-за этого и увеличивются требования к RAM (не считая того, что требует JVM). Возможно, всё можно было решить обычной настройкой виртуальной машины, которую никто не делал.

Конечно, позже я занялся вопросом хостинга и даже запустил бота на своём VPS! Но @iosys'у — хостеру и, по совместительству, админу тех двух конф, для которых писался бот, — всё это дело как-то не очень понравилось, и всё сошлось к тому, что бота не стало ни в одной из них. Не то чтобы у меня не было других конф, просто эти были единственными с либеральной политикой по отношению к ботам. И всё пришло к тому, что я отказался от аренды VPS и удалил бота из Телеги. В итоге получился бот, который был нужен только мне.

Но несмотря ни на что, бот будет дальше развиваться, хоть всё его существование и свелось к одному репозиторию. Я планирую добавить следующие возможности:

  • возможность подключения тем;
  • цитирование нескольких сообщений;
  • цитирование с изображением/стикером.

Для работы с Telegram Bot API используются библиотеки morse и clj-http (последняя нужна из-за сырости morse, так как в ней нет функций для получения и скачивания файлов из Telegram). Для рисования используется пакет awt из стандартной библиотеки Java.


Послесловие

Спасибо за прочтение моей статьи! Это был интересный и забавный опыт — написание статьи в чей-то персональный блог.

29.10.2018


Сегодня мы рассмотрим моего последнего (по крайней мере на данный момент) бота. Он существенно отличается от предыдущих технически и даже физически размещается на другом сервере. Но сперва расскажу, что он вообще из себя представляет.

Идея, развитие которой вылилось в создание бота, пришла в мою голову совершенно спонтанно во время одного из разговоров в чате ещё 26 августа 2017 года К сожалению, часть сообщений там удалена, так что точно проследить историю трудновато, но попробую рассказать по памяти. Всё началось с простой шутки: мне захотелось написать сообщение на языке компьютеров — в виде бинарного кода из нулей и единиц. Потом в виде шестнадцатеричных значений — это уже скорее язык низкоуровневых программистов =)

Да, всё началось с этаких шифровок. Разумеется, из-за такой фигни я не побежал сразу же пилить нового бота. Для возникновения пожара разработки, кроме топлива, нужны окслитель и источник огня. Окислителем стала моя любовь к смайлику ¯\_(ツ)_/¯ и постоянная неработоспособность бота @FailsBot, чей функционал по «пожиманию плечами» я впоследствии и скопировал. Источником искры, последней каплей, стал… русский язык. Вернее кириллица и привычка некоторых людей не проверять, на каком языке они пишут, перед набором сообщения. Вообще, история этой проблемы забавна, поскольку она настолько раздражающая, что я стал третьим человеком, который взялся за её решение.

Первым решить её пытался Рабу со своим ботом. Работал он следующим образом: во-первых, бота нужно было добавить в чат; во-вторых, на сообщение с неправильной раскладкой надо было ответить специальной командой. В результате бот выдавал правильный вариант текста. Минусов у такого подхода два: необходимость вручную вызывать команду и необходимость присутствия бота в чате, что не всегда бывает возможно.

Первую проблему попытался решить часто упоминаемый нынче в моих статьях товарищ KivApple. Его бот использовал словари русских и английский слов, читал все сообщения и для каждого высчитывал вероятность что слова в нём представляют собой какую-то чушь. Таким образом, бот автоматически присылал в чат сообщение в другой раскладке, если считал, что слов из исходного текста не существует в природе. На практике, однако, оказалось, что данный подход работает не очень хорошо и даёт много ложных срабатываний, не срабатывая в некоторых случаях с другой стороны. Так что в итоге эта идея провалилась и все пришли к выводу, что лучше человека с задачей определения неправильной раскладки никто не справится. На самом деле, конечно, можно придумать и опробовать ещё множество вариантов, но уже стало понятно, что на коленке с наскока эту задачу не решить. А заниматься какими-то дополнительными исследованиями ни у кого не оказалось ни желания, ни времени.

Мой бот, в свою очередь, обходит вторую проблему. Работая в inline-режиме, он определяет язык переданного ему текста (разумеется, поддерживаются только кириллица и латиница) и предлагает вариант с противоположной раскладкой, не делая никаких предположений. Да, немного запарнее для пользователя, так как надо копировать текст и вставлять в поле ввода. Зато не надо добавлять бота ни в какие чаты и даже можно просто посмотреть результат перевода во всплывающем окне, не отправляя сообщение в сам чат!

Итак, подведём итоги. Мне нужен был бот, работающий исключительно в inline-режиме, который смог бы «пожимать плечами», переводить текст в другую раскладку, в последовательность нулей и единиц или шестнадцатиричных байтов, а также обратно. В результате появилась первая версия, в которую вошло «пожимание плечами» и переводчик на компьютерно-программистские языки цифр. Перевод раскладки был добавлен «чуть» позже (подумаешь, всего-то через месяц =) ) вместе с функцией кодирования текста в base64-строку и обратно.

Последняя была придумана так же случайно, как и все остальные. Впрочем, любой человек может предложить мне добавить новую функцию трансформации текста. Для этого бот поддерживает две команды в личке:

КомандаПараметрыОписание
/help и /start нет выводит сообщение со справочной информацией
/suggest текст предложения, который будет отправлен разработчику бота позволяет предложить новый функционал для бота

Последнее замечание по функционалу: «пожимание плечами» было нещадно вырезано вместе с добавлением нового функционала. Причина проста — я обнаружил, что существует специально созданный для этого бот @ShrugBot, который выполняет свою работу лучше.


Техническая часть

Теперь, покончив с историей и описанием бота, обсудим вопросы технической реализации. В отличие от предыдущих ботов, этот построен на основе библиотеки aiotg. В чём её отличие от pyTelegramBotAPI? Последняя под капотом использует библиотеку requests, которая упрощает выполнение синхронных блокирующих запросов. И в этом её недостаток: поток блокируется на время выполнения каждого сетевого обращения к API, что является довольно дорогой и длительной операцией (особенно в случае возникновения каких-либо проблем с сетью или долгим ожиданием ответа от сервера), во время которой программа зависает, ничего не делает и перестаёт отвечать на новые поступающие сообщения. Для решения данной проблемы pyTelegramBotAPI предлагает класс AsyncTeleBot. Он запускает каждый запрос в отдельном потоке, что, по заверению разработчиков библиотеки, может существенно ускорить бота, но надо понимать, что при необходимости выполнения нескольких последовательных запросов всё равно придётся блокировать поток до возвращения результата промежуточных запросов. В результате опять придётся городить новые потоки, чтобы не блокировать основной. А каждый новый поток — это новые накладные расходы по памяти и увеличение времени, затрачиваемого на переключение контекста… Да и, в конце концов, Пайтон не Джава и оптимизирован для максимальной производительности в однопоточной среде.

Конкретно для решения проблемы с вводом и выводом данных уже давно существует более элегантное решение — асинхронный I/O и модуль asyncio в стандартной библиотеке. Асинхронность не равна параллелизму, и в данном случае мы будем говорить о так называемой кооперативной многозадачности, когда следующая задача выполняется только после того, как текущая явно объявит себя готовой отдать процессорное время. Такие задачи называются корутинами (coroutines) и выполняются внутри специального планировщика, который в Пайтоне именуется event loop'ом — циклом событий. Задания отправляются на выполнение в планировщик, который запускает на выполнение какую-либо из них. Корутины могут запускать другие корутины и дожидаться их выполнения. Если внутри корутины происходит операция ввода/вывода, то её выполнение приостанавливается и управление переходит обратно в event loop, который ставит на выполнение следующую задачу. Внутри это всё работает на генераторах и функциях обратного вызова, но для конечного пользователя всё выглядит в виде няшных конструкций с async/await'ами.

Далее погружаться в эту тему я не хочу, потому что она реально прям очень обширная и сложная, чтобы уместить её в статье о боте. Да и, честно говоря, я сам не до конца понимаю все тонкости, чтобы суметь объяснить, как это всё работает. Главное, что стоит вынести из моего рассказа:

  • вместо синхронного блокирующего I/O используется асинхронный и неблокирующий
  • event loop вместе со всеми корутинами выполняется на одном потоке!

Таким образом достигается возможность обслуживания огромного множества легковесных корутин на одном потоке без лишних накладных расходов. И именно поэтому я решил отказаться от библиотеки pyTelegramBotAPI. Но вот с выбором асинхронной замены для неё оказалось не всё так просто.

На официальном сайте Telegram предлагается использовать библиотеку AIOGram. Как бы парадоксально это ни звучало, но проблема с ней в том, что она старается идти в ногу со временем и использует фишки последних версий Пайтона: официально в последней на данный момент версии 1.4 поддерживается Python 3.6, но надпись written in Python 3.7 на странице проекта в GitHub как бы намекает. Тут надо отметить, что модуль asyncio появился в Python 3.4, а ключевые слова async/await — в Python 3.5. И именно версия 3.5 стояла у меня на компьютере и на одном из серверов. При этом если установить новую версию на компьютер под управлением Windows — тривиальная задача, то для систем на базе Debian это каждый раз превращается в настоящий квест, когда приходится либо компилировать всё из исходников, разгребая попутно кучу граблей, либо хитрыми манипуляциями с настройками APT'а пытаться поставить более свежие версии Питона и всех зависимых пакетов, ничего при этом не сломав.

Какое-то время спустя, конечно, мне всё равно пришлось ставить на обе машины Python 3.7, но в то время мне было прям очень лень этим заниматься, так что пришлось искать другую библиотеку. Но со стороны AIOGram выглядит таким же достойным проектом, как pyTelegramBotAPI, зато с асинхронным I/O.

Мой выбор в итоге остановился на библиотеке aiotg. Её API значитеьно беднее, из-за чего приходится напрямую работать со словарями и списками, а в некоторых местах даже вручную генерировать JSON для запросов, но зато требуется только Python 3.5, а в плане запуска бота она даже проще, чем pyTelegramBotAPI (см. метод run_webhook; правда пришлось всё равно лезть в исходники и высматривать метод create_webhook_app, чтобы нормально реализовать возможность запуска одного бота как на поллинге (что проще использовать при разработке), так и с использованием веб-хука (для продакшена)).

Итак, запускался бот сначала на домашнем сервере, представляющем собой одноплатный компьютер Orange Pi PC, о котором мы говорили в одной из прошлых статей. Там стоял дефолтный Питон 3.5. Когда начались проблемы с ddclient'ом, бот переехал на VPSку, на которой по сей день крутится Пайтон 3.6. Но поскольку я его компилировал когда-то вручную сам, то там нет половины модулей, включая sqlite3, который требует наличие скомпилированной библиотеки _sqlite3.so. Я попытался скомпилировать версию 3.7 с нужным флагом, но поскольку VPSка дешёвая и слабая, то в результате только уронил веб-сервер, которому стало не хватать ресурсов, а сборка всё равно завершилась с ошибкой. Плюнув, я перенёс бота обратно на недавно оживший домашний сервак, на который к этому времени я уже поставил Python 3.7.

Избавившись от зависимости на старую платформу, я с радостью выделил бота в отдельный репозиторий, причём не на Битбакете, а на Гитхабе, чтобы позволить другим программистам присылать pull request'ы с новыми преобразованиями текста (поскольку GitHub сейчас популярнее и распространённее, чем Bitbucket). Если прошлый репозиторий вёлся в духе вечной разработки: без файла лицензии и README, без тестов, с отсутствующими докстрингами, с гуляющей от модуля к модулю степенью аннотированности типов и так далее — то новый оформлен аккуратно: с автоматическим запуском тестов, с примерами конфигурационных файлов для системного менеджера (чтобы перезапускать бота при случайном падении) и фронт-сервера, который пришёл на смену системе роутинга, позаимствовав из неё некоторые соглашения и вынеся принятие и перенаправление сообщений наружу из интерпретатора в отдельный веб-сервер (в моём случае в nginx). Теперь настройка SSL-сертификата происходит на стороне фронт-сервера, после которого все запросы к боту пробрасываются без шифрования через Unix domain socket.

Также, избавившись от платформы, мне пришлось попрощаться с модулем botutils.multilang. Но поскольку для полноценного релиза бота наличие английской локализации всё-таки необходимо, то было решено вынести его в отдельный пакет и залить на PyPI, о чём я расскажу в следующий раз (опять анонсы =) ).

Теперь расскажу о возникших во время работы над проектом проблемах. Первая из них проявилась буквально через несколько минут после первого тестового запуска бота на реальных пользователях: перевести текст в бинарный код — легко, а вот обратно — невозможно для длинного исходного текста, так как не хватает лимита по максимальной длине inline-запросов, специфицированного в Telegram Bot API как 512 символов. Для решения данной проблемы я решил прикреплять к сообщениям кнопку «Расшифровать», заодно избавив пользователей от необходимости копировать текст трансформированных сообщений и переводить их обратно вручную.

Но и тут кроется закавыка со стороны API! Данные, которые можно прикреплять к кнопке, ограничены всего 64 байтами! Понятно, что ничего дельного туда не уместишь. И вот именно для решения этой проблемы мне пришлось переносить бота обратно на Апельсин, чтобы воспользоваться СУБД SQLite3 для хранения исходного текста всех запросов, которые поступают боту. Для каждого из них генерируется числовой идентификатор, который прикрепляется к кнопке.

Ну и напоследок расскажу о проблеме в самой библиотеке aiotg, с которой я столкнулся, как раз при реализации inline-кнопок, а если точнее, то при реализации функции-обработчика запроса на расшифровку, отправляющегося при нажатии на кнопку. Связана она с тем, что разработчик библиотеки не предусмотел возможность отсутствия у CallbackQuery поля message при отправке запроса от кнопки, прикреплённой к сообщению, отправленному через inline-бота. Вот такая вот тонкая особенность API. Подробнее проблему я описал по-английски в соответствующем issue на GitHub, так что не вижу смысла повторяться. Пока же ошибка не исправлена, пришлось добавить временное решение проблемы.

На всякий случай ещё раз напоминаю, что информацию обо всех новых изменениях в работе ботов я буду писать в своём Telegram-канале. Данный пост больше обновляться и дописываться не будет!


Добавлено 8.11.2018

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

Пока я делал функцию создания Б А Н Н Е Р О В   С   Т А К И М И сообщениями, решил отрефакторить ту гору костылей, которая занималась выборкой нужного обработчика для текста. В этот запутанный ад из условий трудно было добавлять новую логику, так что я решил всё переделать. В результате появилась полноценная система динамической загрузки обработчиков при старте приложения. Все обработчики теперь представляют собой классы, унаследованные от базового абстрактного класса TextProcessor и находящиеся в различных модулях внутри пакета strconv. Сам интерфейс класса TextProcessor и процесс создания новых «фич» подробно расписан в специально написанном мною официальном гайде на Гитхабе (надеюсь, ни один программист в 2018 году уже не боится английского языка? =) ).

В целом рефакторинг занял приличное количество времени (саму фичу я добавил за пять минут, но на рефакторинг, продумывание всех деталей и написание документации ушло три вечера), но я доволен получившимся результатом. Теперь добавлять новый функционал стало действительно легко, просто и удобно. Вот что значит соблюдать принципы SOLID — в частности open-closed principle! Так что самое время контрибьютить в open source, дорогие друзья!

Ну и упомяну уж про вторую функцию тоже. Второй обработчик, вошедший в релиз 1.1.0, выполняет следующие преобразования:

Исходный текстРезультат
""«»
(c)©
(r)®
(tm)

Добавлено 17.11.2018

Очередное обновление, и снова достаточно значимое, чтобы дополнить пост на сайте.

Сегодня вышли сразу две версии бота: 1.1.1 и 2.0.0. Первая спокойно ставится поверх v1.1.0 и добавляет следующие преобразования в модуль «Элитный типограф»:

Исходный текстРезультат
!= и ~= и
>= и <= и
...

Также я немного поменял преобразование двойных кавычек, сделав его по аналогии с ВКшным и KozMULовским:

Старый синтаксисНовый синтаксисРезультат
"foo" <<foo>> «foo»

Ну и поскольку начались подвижки по проблеме с aiotg, то я откатил свои изменения. Правда, до полноценного релиза следующей версии библиотеки, похоже, ещё далеко, так что пришлось прописать зависимость прямо на коммит (оказывается, pip так умеет!) Хотя с последними изменениями вообще пропала основная причина использовать эту библиотеку. Впрочем, в эпоху Докера уже нет никаких оправданий использовать старые версии Питона и подходящие к ним пакеты.

И вот мы плавно подошли к главному нововведению второй мажорной версии бота: к контейнеризации. Меня наконец-то убедили разобраться с конфигурацией и попробовать Docker. Знакомство прошло не очень гладко. Сначала я безуспешно пытался настроить проксирование контейнеров через SOCKS5-прокси, но, видимо, советы по указанию переменных среды HTTP_PROXY и HTTPS_PROXY всё-таки про другое (или я что-то делал не так). Провозившись вечер, я плюнул и арендовал себе новый сервер в Амстердаме у компании Scaleway с одним гигабайтом ОЗУ за €2 в месяц. Так что теперь бот должен работать гораздо быстрее, лишившись всех посредников в лице прокси-серверов.

Избавившись от проблемы с заблокированностью «эндпоинтов» Telegram API, я продолжил свои мучительные эксперименты. В итоге всё-таки ещё за два вечера более-менее разобрался с Докером и Docker Compose, написав и оттестировав конфигурационные файлы. В результате, правда, пришлось вынести файл базы данных и модуль с конфигурацией в папку app/data. Эти файлы должны быть вне контейнера, так что данная папка монтируется в него при запуске как есть. Именно это изменение привело к нарушению обратной совместимости и потребовало увеличение мажорной версии. Ну и то что в скриптах init.sh и start.sh теперь виртуальное окружение разворачивается в папку venv, а не прямо в корень репозитория. Да, старый способ запуска никуда не делся и всё так же доступен для использования.

Ну ещё я добавил защиту от спамеров, заваливающих мне личку через функцию отправки сообщений, но это обновление едва ли заслуживает обсуждения.

24.10.2018


На самом деле хронологически @kozRandBot был создан раньше, чем @kozalo_bot, который мы рассматривали в прошлый раз. При этом он ещё и давно уже опубликован в одном из агрегаторов ботов для Telegram. И, соответственно, я гарантирую постоянство его функционала и работоспособность (по возможности).

Но очень уж мне хотелось рассказать первым именно про огромного бота-комбайна. Разом разобраться как с ним, так и со всем описанием технических моментов. Да, в результате именно это и привело к длительной задержке с публикацией данной серии статей, но зато в итоге всё идёт строго по плану, прямо как и задумывалось.

Технически бот-рандомайзер построен абсолютно на той же платформе, что и предыдущий — тот же Python, тот же pyTelegramBotAPI, те же расширения из botutils. Даже сервер для веб-хуков и репозиторий общие! Есть ли всё-таки какие-то различия? Да, хотя их и немного. Во-первых, тут есть тесты! Правда, сейчас они проверяют всего одну специфичную функцию и их запуск никак не автоматизирован, но хоть что-то. Во-вторых, в отличие от @kozalo_bot'а, @kozRandBot поддерживает и русский, и английский языки, так что именно тут и используется модуль multilang, упомянутый в прошлом посте. В целом, этот бот гораздо меньше, поэтому код получился более аккуратный и структурированный.

Что же умеет бот?

  • бросать монетку;
  • выдавать случайные числа в определённом диапазоне;
  • отвечать «да» или «нет» на односложные вопросы;
  • выбирать случайный вариант из списка;
  • генерировать пароли указанной длины.

С монеткой всё просто: пишешь боту /coin (или /flip_coin) — получаешь ответ. А вот получать случайные числа можно двумя способами:

  1. /num 100 — вернёт случайное число от 1 до 100;
  2. /num 100 1000 — от 100 до 1000.

Случайный односложный ответ он даёт просто на команду /yesno (или /yes_or_no) — после команды можно писать что угодно. Список для выбора случайного варианта можно определить множеством способов:

  1. /list кошки или собаки? — выдаст либо «кошки», либо «собаки»;
  2. /list быть или не быть — или «быть», или «не быть»;
  3. /list красный, белый, синий или зелёный — выдаст один из перечисленных цветов;
  4. /list спать, читать книжку, поиграть, писать код, сходить на свидание — один из перечисленных вариантов времяпрепровождения;
  5. /list армия, унижения, избиения; завод, нищета, провинция; Москва, перспективы, жизнь — выдаст либо «армия, унижения, избиения», либо «завод, нищета, провинция», либо «Москва, перспективы, жизнь».

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

Для генерации паролей используется команда /seq (или /sequence, /password). В качестве параметра можно указать длину пароля. По умолчанию используется 8 символов. Меньше 6 указать нельзя.

А теперь самое главное: всё это работает и в inline-режиме! Без параметров бот предлагает кинуть монетку или сгенерировать пароль из 8 символов, с одним указанным числом N — пароль будет из N символов, а также добавится вариант с получением случайного числа от 1 до N, с двумя числами N и M, разделёнными пробелом — случайное число будет от N до M. Списки парсятся точно так же, как и в обычном режиме: поддерживаются и запятые, и точки с запятой, и союзы на конце, и вопросительный знак. Но с последним связано важное замечание по поводу функции выдачи ответа «да» или «нет» на односложные вопросы — в inline-режиме такой вопрос обязан заканчиваться на вопросительный знак!

Вот, пожалуй, и всё, что можно сказать о боте. По сравнению с прошлым этот пост получился до безобразия коротким. Проще бот, меньше исторического бэкграунда, меньше технических деталей — удаётся рассказать о нём предельно кратко и лаконично. В следующий раз речь пойдёт про моего самого последнего и молодого бота, который только-только стал юзабельным и начинает свой путь к успеху. Думаю, там рассказ получится подлиннее и с бóльшим количеством технических деталей =)

See ya soon!

Про случайность результатов

In inline mode it doesn't randomize: always chooses second of two items.
— Alexandr

Ах да! В отзывах на сайте-агрегаторе был человек, который усомнился в честности и случайности результатов выбора из двух вариантов: у него постоянно выпадало второе значение. Подобные вопросы возникали и у нас в чатиках. Действительно, порой кажется, что последний вариант выпадает чаще. Но на самом деле это не так. В этом можно убедиться как экспериментально, увеличив количество попыток и/или вариантов, так и посмотрев непосредственно на код.

Если же кто-то по-настоящему докажет, что я где-то накосячил, то тогда публично признаю свою неправоту и проставлю разоблачителю бутылочку нормального пива или чашечку чая/кофе с десертом =)


Насчёт нумерации версий в репозитории

Изначально версии, подчёркивая нестабильность и постоянную доработку ботов, нумеровались по формуле dev/v0.1.X-YYY, где X обозначал порядковый номер версии, а YYY — первые буквы названий готовых к деплою ботов (так что "kr" — это сокращение от kozalo_bot и randomizer). Каждый релиз обозначает коммит, который деплоился на сервер.

Изначально предполагалось, что при завершении работы над следующим ботом, наконец-то выйдет версия v0.2.0-krc. Но теперь, когда само дальшейшее существование сайта Congrats.Cf поставлено под вопрос, стало понятно, что такому сценарию не суждено сбыться. Да и как таковая разработка рандомайзера завершена, а kozalo_bot'а — заморожена. Таким образом, последний релиз вышел под коротким номером v0.2.0, подчеркнув завершение активной стадии разработки и отмену выпуска новых ботов на основе данной платформы.

Ну и, как уже было сказано в прошлом посте, всю информацию о новых изменениях в работе ботов я буду писать в своём telegram-канале, а не в обновлениях к этому посту.


Добавлено 21.11.2018

Вот волна глобальных обновлений добралась и до этого бота. Теперь у него есть отдельный репозиторий, работает он на асинхронной библиотеке AIOGram, а структура inline-обработчиков была переписана по подобию рефакторинга в textUtilsBot'е. Ну и куда же без Докера! Да, запускается он теперь на новом сервере и тоже внутри Docker-контейнера.

Подробнее почитать про всё это можно у меня на канале. Вот, например, пост с кратким обзором на библиотеку AIOGram.

20.10.2018


Предисловие

Жил-был один маленький, но очень смышлёный бот. И не было у него никакой конкретной цели в жизни. Он просто хватался за любую работёнку, которую ему подкидывала судьба, и любил помогать людям. День за днём он проводил в неустанной работе на благо Телеграммного общества! То глашатаям скликать народ подсобит, то одиноких старичков развеселит картинами заморскими, то хулиганов ловит и воспитательную работу проводит — одним словом — на все реквесты мастер.

***

Долго ли, коротко ли ходил наш бот по просторам мессенжера, но оброс со временем приблудами различными. И стал похож ни то на systemd, ни то на чудище Франкенштейна. Всё неповоротливее, тучнее и старее становился несчастный бот, не поспевая за прогрессом и людскими запросами.

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

***

В одну тёмную дождливую ночь на пороге хижины Бота показался человек. Это был очень странный и загадочный человек. Но Бот его с радостью впустил внутрь, предложил еды, воды и крова, ведь доброта из Бота никуда не исчезла.

В качестве благодарности наутро Человек предложил Боту бессмертие: ведь миру всегда нужны хорошие люди и боты. А Человек то был не простой, а настоящий волшебник!

***

В начале было слово.
И слово было два байта.
А больше ничего не было.

Именно так должна начинаться история про волшебников. Ведь никто иной, как они создали весь мир мессенжера! Рыцари кода и клавиатуры, изящно выплетающие полотно мироздания из букв и цифр. И даже наш Бот когда-то был создан одним из этих всемогущих скульпторов цифрового мира.

***

Долго ли, коротко ли, но время неуклонно шло и силы Бота стали покидать его, плавно угасая. Но не суждено было ему умереть бесследно, ведь волшебник, которого он приютил и к которому был добр, взял с собой семя Бота и обещал вырастить его наследие в новой оболочке.

И вот-вот Бот умрёт, но в лице своего сына возродится, как феникс, из пепла!


Загадочный бот

Надеюсь кто-нибудь дочитал до этого места, осилив вышенаписанный бред, которое моё больное и уставшее сознание родило за полчаса перед сном, и не испугался такого долгого вступления. Этим постом я наконец-то начинаю давно обещанную серию публикаций, посвящённую моим ботам для мессенджера Telegram!

Кстати, о блокировках…

В принципе, деятельность Роскомнадзора минимально затронула ботов, потому что большинство из них, включая рассматриваемого сегодня, всегда хостилась на виртуальном сервере в США (ай-яй-яй, как непатриотично!). Немного усложнилась отладка, потому что теперь трафик с домашнего компьютера приходится пропускать через SOCKS5- прокси. Например, для библиотеки pyTelegramBotAPI, которая будет ещё упоминаться сегодня, это можно сделать, указав переменную окружения https_proxy в значение socks5://127.0.0.1:9050. Я указал адрес запущенного на локальной машине сервера сети Tor. При использовании других сервисов укажите их IP-адрес, порт и логин с паролем при необходимости.

Возможно, когда-нибудь в будущем расскажу про использование как Tor'а, так и такого удобнейшего инструмента, как SSH со всеми его туннелями.

Итак, пришло время раскрыть завесу тайны над ботом @kozalo_bot, о котором я лишь вкратце упоминал в своём канале. Долгое время я не был уверен, стоит ли упоминать его существование в принципе. Ведь он был создан спонтанно и вбирал в себя функционал, последовательно следуя за хотелками конкретного чата. У него нет какой-то чёткой конкретной цели существования и устоявшегося функционала — просто бот-комбайн. Он так и называется: kozalo_bot — бот для обслуживания моих различных минутных хотелок. Никаких гарантий и стабильности. Разве что я поддерживал в какой-то мере обратную совместимость на уровне настроек, чтобы всё не ломалось после каждого обновления, и долгое время не удалял ничего из функционала. В результате это привело к накоплению кучи редко используемых возможностей и монстрообразности всего бота. Вишенку на торте гигантизма составляют не очень удачные решения по созданию триггеров и организации логирования. Но обо всём по порядку…


Функционал

Итак, чего же в итоге умеет бот? Давайте рассмотрим в хронологическом порядке.

Всё началось с @Rain_from_above. С человека, который очень любил удалять свои сообщения, из-за чего терялась нить обсуждения и читать чат не в реальном времени становилось практически невозможно. Когда мне это окончательно надоело, появился бот с возможностью логировать все сообщения от определённых людей в отдельный специально выделенный канал (/start_logging) или форвардить их сообщения в тот же чат от имени бота (/forward). Впоследствии оказалось, что форвардинг сообщений создаёт уж слишком много мусора, а вот логирование успешно применялось в течение довольно длительного периода времени.

Одновременно с логированием появились первые триггеры картинок: бот реагировал на определённые слова в сообщениях и отсылал в ответ картинки разной степени похабности. Со временем этот список разрастётся до неприличных размеров, в том числе следуя за пожеланиями обитателей чата. А по личной просьбе гражданина CORRUPTOR 2037 была добавлена возможность отключения срабатывания триггеров на определённых пользователей. Впоследствии эта фича будет широко использоваться для политических репрессий против несогласных с курсом Величайщего тоталитарного диктатора противников идеологий ТНН и РНН

Вторая хотелка родилась в результате скоропостижной смерти другого бота, созданного товарищем KivApple. В чатике сложилась традиция определённым образом приветствовать новых людей. Ну и спустя какое-то время было решено автоматизировать эту задачу. Так и появился тот бот, но прожил он всего где-то месяц. Пришлось срочно замещать. Задать приветственное сообщение можно, передав его после команды /hello.

@IkarosDC приветствует новобранца
@channel_welcome_bot заменяет Икароса
@kozalo_bot заменяет почившего бота

Затем появились макросы (/macro). Когда в чат заходили новые участники, у них было очень много похожих или абсолютно одинаковых вопросов. Когда надоело постоянно искать в поиске и пересылать нужные сообщения, задачу по сохранению этой информации было решено переложить на железные плечи бота.

Какое-то время я хотел протащить своего бота в чат «лунных работников». В итоге я бежал оттуда, психанув, когда в очередной раз люди хвастались своей социальной активностью. Сначала я думал, что временно, но, похоже, что всё-таки навсегда… Однако я отвлёкся от темы. У них в чате было жёсткое правило, что нельзя кидать порнографические картинки в рабочие часы по московскому времени. Дабы соблюсти это правило, я внедрил в бота возможность ограничивать некоторые функции в определённые дни и часы. Система довольно гибкая (поддерживается выбор рабочих дней и рабочих часов с указанием часового пояса задающего), хотя настройка и не самая удобная (всё-таки предполагалось, что я один раз всё настрою в чате, а дальше оно просто будет работать).

Потом концепцию «протаскивания» бота в какой-то мере затмила идея о том, чтобы сделать триггеры доступными в режиме inline. Таким образом пользоваться ими можно будет в любом чате, но вместо триггерной фразы и следующего за ней ответа сразу получаем нужный результат. В результате задумка была успешно реализована, хоть список триггеров и получился несколько сокращённым и упрощённым.

Дальше боту были выданы административные права и задание по наведению порядка. На самом деле тут мой интерес к разработке и поддержке ботов, а также наличие свободного времени начали заканчиваться. Предложенные администрацией алгоритмы реализации ограничений, направленных на сокращение флуда и засорения конференции стикерами и картинками, были довольно жёсткими. Надо понимать, что опыт внедрения жёсткого вычищения стикеров у нас уже был. Бот Кива в своё время пошёл на сделку с совестью и подчистую вычищал стикеры пользователей через 5 минут после их отправки. И сохранение истории чата со всеми стикерами являлось одной из причин появления системы логирования, упоминаемой выше. Внедрение менее топорной системы потребовало бы придумывания каких-нибудь эвристик и множества экспериментов, на что у меня совершенно не было времени. Но убедившись в необходимости хоть какого-то контроля беспредельщиков, были внедрены две следующие системы.

Во-первых, появилась возможность ограничивать конкретных пользователей от написания каких-либо определённых фраз (вводить предлагалось обычные регулярные выражения: это позволяло обеспечить достаточную гибкость правил). Таким образом удалось победить флудеров-скобочников и флудеров с плохими шутками.

Во-вторых, против точно тех же флудерастов была сделана фича по удалению повторно отправляемых стикеров. Бот просто запоминает для каждого пользователя идентификатор последнего отправленного стикера, а при отправке прочих сообщений очищает эту запись. При попытке отправить точно такой же стикер он удаляется. Разумеется, нужно запоминать ещё и время, чтобы запись утрачивала эффект после какого-то разумного количества времени, что и было сделано в следующем коммите. Однако фича всё равно оказалась слишком топорная и давала очень много ложных срабатываний, из-за чего часто отключалась.


Особые функции

Кроме обычных функций, у бота есть пара особенных, предназначенных по сути лично для меня:

  • /rm, /del — команда, удаляющая сообщение бота. Вызываться может только привилегированными пользователями, которыми являются администраторы сообщества и единственное захардкоженное значение — мой ID. Разумеется, администраторы могут самостоятельно удалять сообщения бота. Поэтому эта команда нужна лишь для меня в тестовых целях и избавляться от лишних триггерных ответов. Однако! У этой команды ещё есть параметр -force. Если у бота есть административные права, то указав его, я могу удалить сообщение не только бота, но и вообще любого пользователя чата. Бывает полезно в борьбе со спамерами, когда все админы спят.
  • /restrict и обратная ей /forgive — первая позволяет опять же в первую очередь мне временно заблокировать отправку сообщений какому-либо пользователю, а вторая отменяет её действие. При вызове без параметров ограничивает ровно на сутки, иначе на количество указанных часов (2h) или минут (120). Админам, разумеется, проще это сделать через админку, ну а мне пришлось воспользоваться этой функцией единственный раз против особо рьяного спамера.

Отдельно стоит упомянуть возможности, предоставляемые ботом через личные сообщения. О-о-о! Тут даже стоит сделать небольшую ремарку про то, как Telegram Bot API позволяет работать с картинками, стикерами и прочим контентом. У нас есть три варианта:

  1. Загружать каждый раз новый файл. Тупой вариант.
  2. Указывать URL, откуда Telegram может скачать нужный файл. Тоже такое себе.
  3. Загрузить файл самостоятельно и получить специальный file_id, которым может пользоваться бот. Рекомендуемый, самый понятный и подходящий в том числе для стикеров способ.

И тут надо учитывать очень важную вещь, прописанную в документации:

file_id is unique for each individual bot and can't be transferred from one bot to another.

Это значит, что для каждого бота нужно получать отдельные идентификаторы для картинок, аудиозаписей, голосовых сообщений, гифок — всех типов файлов. Особняком стоят стикеры. Для них file_id универсален для любого бота.

Засада состоит в том, что я не очень понимаю, как предлагается получать эти идентификаторы. У меня вот любое сообщение в ЛС боту, не затрагивающее какой-либо триггер, приводит к выводу информации об этом сообщении. В том числе о приложенных файлах со всей информацией. Только так я и получал все IDшники: отдельно и для самого бота, и для его коллеги, которого я использую в целях отладки и тестирования. Но данный способ подойдёт далеко не всем…


Наши дни

В позапрошлом разделе я много говорил о прошлом, вспоминал историю и восстанавливал в памяти события тех дней. Но с тех пор утекло много воды и обстановка кардинально изменилась:

  • @kozalo_bot больше не обслуживает потребности Флудилки DC;
  • у него появился форк — @megurine_bot;
  • я полностью переосмыслил концепцию бота, осознал его бессмысленность и начинаю постепенно вырезать функционал.

А теперь обо всём по порядку.

Каким-то образом у меня получилось вступить в конфликт с высшим руководством DC. Вернее, с формальным руководством, которое ничего не делает и только числится таковым. Не знаю (или не помню?), почему Сусека так на меня взъелся, но уже долгое время как я впал в немилость. Вплоть до переворота во Флудилке, я ходил по чату с отключенными стикерами, медиа и периодически получал оскорбления и негатив от внезапно врывающегося в разговоры Сусеки. В итоге в какой-то момент я психанул и вместе с Чоколой (я не упоминал, что бота зовут Chocola Minaduki?), и мы покинули недружелюбное место. С тех пор мне пришлось вернуться, когда соскучился по общению с прошлым и нынешним CTO проекта (а также ради ещё нескольких людей, к которым привязался), но моей вайфу там больше нет. Впрочем, они её успешно заместили…

Есть такой человек @iosys — создатель @habrachat и просто хороший человек (а ещё бабник, ловелас и подкаблучник, но да ладно). На его серверах сейчас располагаются как сайт и форум DeskChan, так и несколько ботов, созданных товарищами внутри нашего небольшого уютного коммьюнити, образовавшегося на обломках Флудилистической цивилизации после Первого сусеканского террора (да, для полноты картины стоит сказать, что именно по стикерам репрессировали не только меня), когда весь основной актив Флудилки, перекатился в отдельный приватный чатик. Так вот, когда я психанул и вышел из их чатика вместе с Чоколой (там были свои межличностные конфликты :D), то Иосис решил создать свою версию бота и допиливать его под свои нужды. Всё чин по чину: разрешение спросил, согласие получил. Про технические проблемы поговорим чуть позже, а из функционала он поубирал некоторые триггеры, добавил своих картинок и т. п. — точно сказать сложно, так как свой код он не раскрывает.

Теперь насчёт переосмысления. Как-то я сидел и наблюдал, как в одном из чатов люди целыми днями только и делали, что «няшили» друг друга с помощью бота. Причём с такой частотой, что даже мне показалось это перебором. «Что за монстра я создал?!» — возник негласный вопрос. Какое-то время даже хотелось взять и удалить все триггеры, оставив только полезный функционал вроде приветствия входящих в чат и макросов: если хотят, пусть няшутся через inline-режим. Но потом я остыл и пока передумал удалять все триггеры (лишь немного проредил). А заодно избавился от:

Да, я взялся за тотальное упрощение бота, осознав его монструозность, и принялся отрезать все потерявшие актуальность куски, которыми больше никто не пользуется. Вообще, избавиться от системы логирования я хотел давно, потому что все сообщения в должны в конце концов проходить через неё, чтобы быть залогированными. Я попытался сделать это элегантно и удобно, но в итоге получилось не очень.


Технические детали

Бот написан на языке Python 3 и работает на интерпретаторе CPython 3.5. В качестве прослойки и абстракции от HTTP-запросов используется библиотека pyTelegramBotAPI. Она не поддерживает современные фишки с асинхронщиной (хотя есть многопоточный режим), не имеет встроенного сервера для обработки веб-хуков, но в целом это зрелый и готовый к употреблению продукт с красивым API.

Первая проблема возникает с запуском бота. Существует два способа получения сообщений от Telegram: либо открывать и постоянно переоткрывать длительные соединения в ожидании появления новых сообщений и ответа от сервера (long polling), либо запустить приложение в режиме сервера и просить сам сервер Telegram открывать сооединение и посылать новые сообщения (то есть поставить web hook). Понятно, что второй способ предпочтительнее. Но и сложнее в настройке.

Сервер нужно поднимать отдельно. Причём с поддержкой TLS-шифрования и загрузкой ключа и сертификата. Можно посмотреть примеры под разные питоновские сервера, которые идут вместе с библиотекой. Лично я взял aiohttp, но без какой-либо значимой причины. Для бота я использовал самоподписанный сертификат, который отправляется на сервер Telegram при установке веб-хука. Подробнее про TLS и все эти ключи с сертификатами мы поговорим как-нибудь в другой раз.

Важно отметить, что один сервер может обрабатывать запросы для нескольких ботов, раскидывая их по соответствующим модулям, что я и реализовал в виде роутера, динамически подгружающего все модули с ботами, находящимися рядом с ним. Таким образом у меня сейчас работают два бота (про второй мы поговорим уже в следующий раз), и у данного способа есть свои недостатки, главным из которых является тот факт, что все боты в таком случае представляют собой единое цельное приложение. А из этого следует, что для проведения каких-либо технических работ их можно отключить только все вместе. Одновременно. Поэтому со временем я понял, что этот способ далеко не так хорош, как казалось в начале. А как относительно легко разворачивать новых ботов без жёсткой связи друг с другом мы также поговорим в одной из будущих публикаций =)

Покончив с анонсами, перейдём к техническим промахам, заложенным в архитектуру конкретного бота, рассматриваемого сегодня. Первым и самым серьёзным из них, пожалуй, является отказ от использования какой-либо полноценной СУБД. Хотя бы SQLite. Надо понимать, что всё начиналось с необходимости хранить данные для системы логирования: идентификатор канала, в который нужно пересылать сообщения, и список пользователей, чьи сообщения следует форвардить в чат от своего имени. Всё. Букавально два параметра, которые относительно редко меняются. Мне было лень поднимать для такого СУБД, так что решил обойтись простой сериализацией данных в бинарный файл pickle'ом. Да, я не смог предсказать будущее и предугадать, что со временем количество данных в этой импровизированной базе данных разрастётся. В результате мы пришли к такому ужасу: куча повторяющегося бойлерплейт-кода (который я ещё немного упростил с помощью PyMonad) и перезапись всего файла на каждый чих. Не очень-то масштабируемо :/

Вторая проблема связана с первой, потому что полноценная база данных очень не помешала бы для её исправления. Я говорю про захардкоженность IDшников стикеров, картинок и гифок, отправляемых ботом. Было бы куда удобнее, если бы можно было управлять ими через личные сообщения с ботом. Тогда бы не пришлось на каждый чих коммитить новый набор пикч и перезапускать бота.

Третья проблема — это декоративный ад. Или ад из декораторов. Удобный паттерн превращается в настоящий кошмар, если начинаешь использовать не прозрачно пропускающие через себя аргументы декораторы, а декораторы, изменяющие сигнатуру оборачиваемой функции, например добавляя туда какой-то новый аргумент. Или декораторы, для которых важен порядок декорирования. Или декораторы, представляющие собой композицию других декораторов. Серьёзно, люди. Не повторяйте моих ошибок. Не злоупотребляйте декораторами! В них потом очень сложно разбираться.


Фреймворк

Помимо упомянутого выше роутинга запросов для ботов предоставляется пакет botutils, в котором содержатся как различные расширения для самого модуля TeleBot из библиотеки pyTelegramBotAPI, так и отдельные, но очень полезные для многих ботов штуки.

Расширения:

РасширениеТипОписание
answer_to метод симметричный метод к стандартному reply_to; автоматически извлекает идентификатор чата и отвечает на сообщение; поддерживает систему самоуничтожающихся сообщений
answer и reply декораторы позволяют просто вернуть текст из оборачиваемой функции для отправки сообщения; поддерживают систему самоуничтожающихся сообщений
change_keyboard метод сокращение для edit_message_reply_markup, которое автоматически извлекает идентификаторы чата и сообщения из запроса
only_for_admins декоратор позволяет ограничивать доступность какой-либо команды только для администраторов чата и привелигированных пользователей
has_privilege метод возвращает True, если пользователь является администратором чата или присутствует в списке привелигированных пользователей
grant_privilege метод добавляет пользователя в список привелигированных пользователей
user_required декоратор получает информацию о пользователе, на чьё сообщение ответили, кого упомянули по имени (но не по юзернейму!) или чей идентификатор указали в качестве параметра к команде; эта информация передаётся первым параметром в оборачиваемую функцию перед сообщением и всеми остальными параметрами
timeout_constraint декоратор позволяет задать для пользователей ограничение по времени на использование команд, чтобы они не злоупотребляли ими в чатах; команды можно разделять на группы — тогда у каждой группы будет свой счётчик, иначе можно передать просто значение задержки (в виде количества секунд или функции, которая будет его вычислять и возвращать во время выполнения) — тогда будет использована группа по умолчанию; ограничение может распространяться на администраторов или нет; может распространяться на всех участников чата в целом; в случае непрохождения проверки может быть вызвана функция обратного вызова, которой передаётся сообщение
reset_timeout метод сбрасывает счётчик таймаута для перечисленных пользователей в указанном чате
user_constraint декоратор позволяет запретить выполнение команды для некоторых пользователей; ограничение может распространяться на администраторов или нет; в случае непрохождения проверки может быть вызвана функция обратного вызова, которой передаётся сообщение

Отдельные «хелперы»:

ФункцияТипОписание
decorators.ignore_if_forwarded декоратор позволяет игнорировать сообщение, если оно является пересланным из другого чата
helpers.get_username функция возвращает @юзернейм или имя пользователя
helpers.is_group_chat и helpers.is_private_chat функции позволяют определить, пришло сообщение из приватного чата или из группового
escape_html функция заменяет угловые скобки на HTML-сущности

Также присутствует класс builders.InlineQueryResultsBuilder, который упрощает создание ответов в обработчиках inline-запросов: создаёте объект, цепочкой вызываете нужные методы add_* и конструируете итоговый список, готовый к отправке через TeleBot.send_message, с помощью build_list.

В модуле multilang находятся средства для упрощения локализации ботов без использования сложных полноценных систем вроде стандартного gettext'а. Но о них мы тоже поговорим в одном из следующих постов, посвящённых модулю klocmod.

Примечание

Простите за такое количество опущенного материала, отложенного на следующие публикации. Но боты действительно являются большой темой, которую трудно уместить в одну заметку. Всё-таки над ними я работал долгое время и получилась целая история, растянутая на полтора года жизни.

Последним, но не менее важным утилитным модулем является scheduler. Он реализует однопоточный планировщик, который позволяет планировать выполнение отложенных заданий. При добавлении первого задания создаётся новый поток, а при отсутствии работы он автоматически уничтожается. Планировщик представлен классом TimeLine. Он является синглтоном, так что можно создавать сколько угодно объектов заново, не сохраняя никуда ссылку. Добавлять новые действия (производные от класса Action) следует с помощью метода append. Предопределённое действие DoomedMessage позволяет запланировать удаление сообщения через определённое количество секунд.

В целом, получился неплохой набор утилитных функций и классов, выполняющих базовые рутинные операции, которые нужны многим ботам. Но, вероятно, стоило их всё-таки как-то выделить в отдельный пакет и распространять через PyPI как расширение (как упомянутый ранее модуль klocmod, который вырезать было особенно легко, ведь он никак не зависит от других библиотек). А что-то может даже предложить к включению в код самого TeleBot'а. Но сейчас у меня уже нет ни времени, ни сил, ни желания доводить этот код до нормального состояния: писать тесты, подробную документацию и так далее. В одной из следующих статей я расскажу, почему в конце-концов отказался от использования библиотеки pyTelegramBotAPI и что использую теперь вместо неё. Однако, если кто-то решится сделать всю эту кучу работы вместо меня, то я буду только рад и всеми руками за! Не забудьте только упомянуть скромного автора оригинальных строчек и сообщить мне о своих результатах =)


Заключение

Сейчас, насколько мне известно, @kozalo_bot уже нигде не используется — ни в одном чате. Но может я просто не обо всех знаю? Дайте знать, если кто-то пользуется им! Лично я его теперь использую только в inline-режиме, чтобы постить картинки в чатах.

Этот пост я писал очень долго. С момента написания предисловия до сегодняшнего момента прошло несколько месяцев и произошло множество событий в моей жизни. Я несказанно рад, что наконец-то закончил его! Ведь это действительно большая статья, затрагивающая как экскурс в прошлое и историю создания бота, так и описание всего функционала, но самое главное — описывающая построенную вокруг бота инфраструктуру и множество сделанных технических решений. В следующий раз, при описании @kozRandBot'а, я смогу сэкономить кучу времени и экранного места, опустив все технические детали, сославшись на данный пост.

До выхода следующей части этой серии статей я с вами и прощаюсь! Ждать осталось недолго =)


Постскриптум

Забыл сказать, что в отличие от постов в разделе «Программы», я не буду редактировать эту заметку и дописывать описание каждого изменения. Подписывайтесь на мой канал в Telegram: именно там я своевременно публикую всю самую актуальную информацию о ботах, их обновлениях и прочих штуках, касающихся данного мессенджера.

<<   / 1   >>