История об изучении использовании python для написания бота библиотеки для ботов в telegram. Код, описанием и историей которого является эта статья доступен по ссылке. Как-то в разговоре со знакомым, на тему проф ориентации и общей перспективности в смысле бабла, прозвучал вопрос: «Пишешь ли ты на питоне?» Я ответил в том смысле, что на любых животных писать неудобно. Они либо кусаются, либо убегают, а часто одновременно и от этого адски страдает каллиграфия. А на круглых, к которым относится питон, как самоходный шланг, и пытаться-то глупо. Оказалось, что речь шла не о хладнокровной, а об электронной сущности, в смысле языка программирования: «Знаешь о таком?» Разумеется, я знал и, за следующие 15 секунд, я вывалил на собеседника все мои познания о том, что это очередной Н-плюс-первый птичий язык, на котором пишет довольно много народу, правда не особо понятно что именно и зачем, однако в моей жизни его необходимость в нем равна приблизительно нулю. «Хот нет, вру. Пацан для колледжа делал какую-то домашку именно на питоне и просил помочь там с чем-то. Писал какого-то «бота» для телеграмма.» В ответ я узнал, что именно питон и именно телеграмм - это крайне модно, молодёжно, современно и вообще: бабы кончают, дети смеются. - Ты хотя бы пробовал? - Ну, это же телеграмм ставить надо… Это же какой-то чат в телефоне, правильно? - Ясно с тобой все. Рюмка чая, вместе с нашим с ним разговором, подошли к концу, но идея запала. Тем более, что у меня как раз была неделя, в которую я был практически абсолютно свободен аж до следующего понедельника. «Аж кончают… да ну нафик, брехня. Но глянуть можно… Вай бы и не нот, так сказать?». Чайник вскипел, чай налит, комп гудит: «Ну-с, и чего там оно этот питон?… Эй Влад, та на чем писал на этой фигне? Бесплатная? Ага качается. А писал чего? Бота? Ясно». О самом питоне мне сказать особо нечего. В процессе поиска я наткнулся на фразу: «На python написано самое большое количество кода в мире», с которой теперь, спустя пару недель я бы поспорил. Если выкинуть из текста программ на питоне начальные пробелы и клепанный "self.", то рейтинг количества кода можно смело делить вдвое.  Ну да пусть она остается на совести создателя статьи, к делу это не относится. В этой статье я не буду описывать ни как скачать PyCharm или десктопную версию телеграмма (зарегистрироваться в которой оказалось той еще проблемой, кстати), ни как создать своего бота и получить API Key. Этой 30-секундной информацией интернет просто завален. Будем подразумевать, что потенциальному читателю это либо вообще до фени, либо он способен эти операции произвести самостоятельно. Переходим к сути А суть в том, что после кое-какой настройки среда для написания кода готова, найдена и скачана библиотека для написания ботов в телеграмм, бегло просмотрены ее примеры, пастнуты в редактор кода и вот первый бот уже умеет говорить: «Я увидел: ‘ваш текст’». В качестве библиотеки я выбрал «aiogram». Просто потому, что первые две попавшиеся были какие-то вообще крайне печально документированные. Ну да «опенсорс–же—блин», так что удивлен я не был, просто искал что-то следующее, где хотя бы примеры были на часть функционала. О своем выборе библиотеки не жалею. Она успешно работает, все свои функции выполняет без нареканий. Другое дело, что 60-80% ее функционала мне не пригодилось, но об этом будет чуть позже. В принципе, можно использовать вообще любую библиотеку, реализующую АПИ телеграмма. Все они идентичны до синтаксиса, отличаясь нюансами наполнения. Кратко о функционировании ботов для телеграмм Бот – это http клиент, который посылает серверу телеграмм команды, получает в ответ результат их выполнения и, параллельно, события, которые произошли в области интереса бота с момента последнего запроса. Сам бот может быть запущен где угодно, лишь бы с того утюга, на котором он функционирует, был доступ к сайту телеграмма. Больше требований к месту его расположения никаких нет. Это удобство. Неудобство заключается в клиент-серверной идеологии самого АПИ. Реализация АПИ нас не волнует вообще, т.к. мы общаемся уже через библиотеку прокладку, но нас волнует событийность модели. Все библиотеки для работы с телеграмом которые я видел реализованы абсолютно одинаково: есть бесконечный цикл запросов на получение новых событий от сервера при получении событий дергаются куски пользовательского кода Пользовательский код можно описывать с разной степенью «задекорированности», но так или иначе это всегда должен быть какой-то конечный кусок, реализующий действия по каждому элементарному событию. Учитывая то, что основных события всего два: текст сообщения и нажатие на кнопку в сообщении, то в пользовательской части весь код, по сути, состоит из вызова всего двух функций. В этом заключается основная проблема т.к. реализовать сколько-нибудь сложную логику на двух асинхронно вызываемых функциях, дергаемых по каждому событию крайне сложно. Разумеется, я ознакомился с «типовыми» подходами по написанию кода в этих условиях. Я знаю о наличии декораторов с кучей условий, а так же хорошо знаком с концепцией конечного автомата. Возможно из-за того, что я некоторые решения представляю себе довольно хорошо, я еще на берегу понял, что это тупиковый путь. Реализовать бота на событийной модели можно, но для этого нужно четкое описание логики «высеченное в граните». У меня же нет четко поставленной задачи по написанию чего-то конкретного, а титанизм усилий при попытке заменить на половине дороги логику обработки или используемый инструментарий я представлял замечательно, поэтому я даже пробовать не стал делать что-то сложнее примеров для ознакомления. Второй «печальный» момент, с которым придется столкнуться при написании ботов – это, хм… невменяемость протокола. Прямо по нему видно, что его писали кусками в разное время и при добавлении чего-то нового ничего из старого никогда не менялось. В результате в протоколе нет практически ни одной сходной вещи, которые бы функционировали одинаково. Пара  примеров. Есть два типа клавиатур, которые можно привязать к сообщению: INLINE и KEYBOARD. Кроме слова «клавиатура» них нет ничего общего. Кнопки у них описываются по разному, имеют разный набор полей и даже сообщения с этими клавиатурами ведут себя различно и несовместимо. Сообщения с картинкой и типом «photo» – это отдельная сущность, которая с другими сообщениями имеет мало общего. У него даже текст подписи называется «caption» вместо «text». Сообщения с другими «вложениями» - это опять отдельные сущности. В общем, попытка унифицировать этот винегрет обещает длительную и плодотворную работу методом тыка. Не очень удивительно то, что основные библиотеки для ботов даже не пытаются эту работу делать. Впрочем, к текущей статье это имеет теоретическое отношение. После того, как мой этап знакомства с АПИ телеграмма и используемой библиотеки привел меня к знаниям, описанным в паре прошлых абзацев, я отложил попытки написать что-то цельное и начал искать готовые решения по «очеловечиванию» реализации «бизнес логики» бота. К огромному моему удивлению я не нашел вообще ничего. Я сейчас имею в виду не вебовские «конструкторы» ботов, коих миллион (ну или приблизительно столько), у меня все же стояла задача по изучению питона, а не по тренировке мелкой моторики правой руки компьютерной мышью. Я имею в виду какие-то библиотеки, которые бы позволяли реализовывать логику работы конечного продукта (бота) в привычной линейной форме, полностью убирая все особенности модели общения с сервером с глаз пользователя. Не нашел. В результате, после довольно краткого знакомства с «полем боя» задача плавно трансформировалась из «пишем бота» в «пишем средство для написания бота». Мне, в сущности, это было даже гораздо интереснее и ближе т.к., повторюсь, основной задачей было именно освоение питона, а не реализация конкретной задачи, но все же было бы интересно узнать, если такие решения существуют. Итого: необходимо реализовать средство, которое бы позволяло писать ботов для телеграмма в привычной линейной схеме реализации алгоритма. Т.е. чтобы можно было писать что-то типа такого (питоно–подобный псевдокод):  Т.е. решать задачу, а не воевать с ограничениями, накладываемыми на способ ее реализации. Для решения этой задачи нам нужно сначала понять пару простых вещей: Логика: Какие интерфейсы нам нужны для реализации интерактивных задач.    Функции бота: Какие способы взаимодействия с пользователем существуют, как и какие нужно использовать, чтобы реализовать нужное для нашей логики.    Работа бота: Как и где эксплуатировать нашего бота. Как он будет получать управление.    Запуск: Как запустить нашу логику параллельно «бесконечному циклу опроса сервера».    Я не буду вдаваться в различия способов эксплуатации бота и возможных мест откуда он может быть вызван, что и где можно или нельзя использовать. В конце концов у нас не бесконечность, а всего лишь одна неделя на то, чтобы ознакомиться с питоном и создать какой-то конечный продукт, поэтому сосредоточимся только на персональных чатах (тех, когда человек пишет чату явно, запуская его кнопкой «запуск»). По сходной же логике работают и боты, которые были добавлены в групповые каналы, но там есть свои особенности. Будем писать код, не обращая внимания на эти различия. По крайней мере пока. Итого: пишем библиотеку для ботов, ориентированных на работу в персональном канале. При работе телеграмма существуют следующие способы взаимодействия бот-человек: Написание текста как с одной, так и с другой стороны – это сообщение. В этом важно то, что бот ничего не делает до тех пор, пока ему не придет первое сообщение. Именно при получении сообщения определяется, откуда его написали (из какого канала), и кто это сделал. Т.к. события могут происходить абсолютно не связанно друг с другом, то сообщения будут приходить для разных каналов, от разных людей, которые находятся на разном этапе общения с ботом. Учитывая это нужно понимать что одна линейная бизнес-логика должна работать только для одного канала. Или так: для каждого канала должна быть запущена своя уникальная копия бизнес-логики. Сохранение как промежуточных, так и финальных данных происходит так же на «по–канальной» основе. В сообщении формируемом ботом, помимо «контента» можно использовать кнопки. Кнопки бывают двух видов: привязанные к сообщению и к чату (хотя тоже к сообщению, но зрительно… блин, тьфу на тебя АПИ телеграмма!). В детальное описание кнопок и их различий я вдаваться не буду по описанному уже выше принципу: «кому пофиг, а кто и так в курсе». В контексте статьи важен сам факт и различия в использовании этих кнопок. Кнопки у чата просто спамят текст в канал, а те, которые у сообщений, вызывают отправку специального обновления.    Подводим итог.    Бот может получить управление в любой момент, когда пользователь напишет какое-то сообщение или нажмет на кнопку. Это может случиться через секунду или через неделю. Не то, чтобы это было как-то важно для нашего недельного марафона, но учитывать все же стоит. Нарисуем что-то типа: и и забудем об этом вопросе.    Общение с ботом происходит в каждом канале независимо и уникально. Учитывая еще и тот факт, что боту может поступать сообщение с произвольными интервалами, то логика обработки сообщений должны быть такой: при получении сообщения находим канал, который его обслуживает.  Если канала нет, то создаем новый. Передаем каналу сообщение. Если в канале уже работает логика, то распределяем сообщение в соответствии с ее потребностями, если ее нет, то запускаем ее, передав сообщение как стартовое. Код приводить не буду т.к. в реализации это все крайне просто, но очень много пробелов , «self.» и прочего не важного текста. Желающие могут посмотреть реализацию самостоятельно. Накидаем пример того, как нам хотелось бы, чтобы наша программа выглядела. Например, так:     На примере этого кода видно, что нам нужно реализовать:    Передачу сообщений (в идеале с картинками).    Удаление/Изменение сообщений    Все вышеперечисленное, с учетом того, что нужны кнопки управления, которых бывает 2 вида.    Реализовать «модальность» для сообщений, т.е. когда все события обрабатываются только одним элементом монопольно не давая выполняться обработчикам из предыдущих элементов до тех пор, пока модальный элемент не будет закрыт. Это нужно для реализации ввода, ответов на вопросы, меню и прочего.    Реализовать возможность обрабатывать нажатия на кнопки в немодальных сообщениях.    Любые библиотеки для работы ботов представляют из себя, в конце, концов такое:    Т.е. запуск некоего бесконечного цикла опроса и реакций на события, в то время как пользовательский код вызывается разными сортами такого:    Т.е. наш код будет вызван при получении различных событий. Их есть некоторое разнообразие, но мы сосредоточимся на простейших: получение нового сообщения в канале и получения уведомления при нажатии кнопки. Внедриться внутрь цикла с линейной логикой не представляется возможным, поэтому нужно запустить нашу программу параллельно ей и обеспечить взаимодействие из нее с данными, получаемыми «мелкими порциями» асинхронно из основного цикла. Мне, как человеку далекому от птичьих языков более привычным был бы способ создания отдельного процессорного потока для выполнения логики, но раз подвернулась такая оказия, то заодно разберемся с co-routines (я без понятия как это называется по русски), в windows эти сущности называются Fibers, где-то еще могут быть другие названия. Суть подхода в том, что множество логически не связанного кода выполняется в одном потоке, передавая друг другу управления в тот момент, когда этому коду нечего делать. Мы пишем http клиента и «нечего делать» - это его основное занятие, поэтому в топку «взрослые» потоки, пишем на «резиновых женщинах». Добавить свой код в очередь выполнения очень просто: где logicTask хранит объект добавленного кода, а self.logic.main запускает на выполнение процедуру с реализацией линейной логики. С учетом разных нужд пусть интерфейс для нашей логики будет выглядеть так:     Пользователь нашей библиотеки будет наследоваться от этого интерфейса и реализовывать то, что ему нужно. По крайней мере main. Разбираемся с АПИ телеграмма (в синтаксисе выбранной библиотеки) и выясняем что все кроме модальности реализуемо довольно просто: Создаем новые сообщения (тип зависит от того с картинкой оно нужно или нет) со всеми возможными наворотами: Редактирование существующего сообщения будет чуть сложнее (спасибо такому однозначному и «единообразному» протоколу телеграмма), но тоже ничего выдающегося:    Это все, разумеется, надо обернуть кучей логики и проверок на то, в каких условиях и с какими «бубнами» вызывать то или другое. Желающие могут посмотреть реализацию самостоятельно. Удаление сообщений существует и, слава богу, простейшее без каких-либо ветвлений в истории развития протокола: Вроде все. Кнопки в протоколе описываются по разному, но пофик,  унифицируем все до: для любой клавиатуры. Это лишняя страница кода, зато не нужно париться с типами при использовании. Стоит того. Модальность к самому телеграмму отношения уже не имеет - это чисто логический наворот, который реализуем простой очередью сущностей, которые ждут получения событий. Т.к. событий всего два, то и интерфейс для такой сущности будет иметь всего две полезные функции. Так же эти «ждуны» должны уметь быть посредниками между кодом логики, который будет ожидать каких-то событий и получаемыми мелкими событиями. Принцип простой: с одной стороны, если логике что-то надо, то она заводит «ждуна» и ждет, пока поставленное условие не выполнится. С другой стороны, при получении мелких событий от телеграмма, проверяется очередь «ждунов» и, начиная с последнего, полученные данные применяются до тех пор, пока не дойдем до модального «ждуна» или до такого, который ровно этого события и ждал. Реализуется почти так же просто как и звучит. Интерфейс ждуна, грубо говоря, такой:  Ну и в классе нужно не забыть создать для него небольшую инфраструктуру:    В этом месте есть небольшой момент, на который стоит обратить внимание. Локер, на котором синхронизируется список «ждунов» должен быть: а) реентерабельным и б) не отпускающим очередь «детских потоков». Первое понятно для чего, а вот второе важно в единственном моменте. В телеграмме, при получении события от нажатой кнопки на это событие можно ответить и при ответе у пользователя всплывет либо модальное окошко с текстом, либо пропадающий хинт. Так вот, если использовать блокировки из набора асинхронной библиотеки, то в момент блокировки будет передано управление первому попавшему ждущему своей очереди коду. Если окажется так, что этот код отправит серверу телеграмм, какие-то данные , то ответ на событие уже можно и не отправлять – оно не сработает, пользователь не увидит реакции. Если не хочется лишать себя такой функции как эти «быстрые ответы прям сразу при нажатии», то асинхронные локеры использовать нельзя. В примере выше используется многопоточный реентерабельный локер, хотя учитывая структуру самого приложения его функция чисто декоративная и не более интеллектуальная чем:    только что использовать удобнее и писать меньше. По функционалу вроде бы все. Если не лукавить, то времени ушло чуть более недели. Впрочем, меня извиняет поездка на шашлыки и пара катаний на дачу. Зачем я все это тут описал? Целей у меня, на самом деле, несколько. Во-первых, хотелось бы узнать мнение тех, кто пишет на питоне давно и серьезно. Все же этот код я писал как знакомство я языком и буду рад любым отзывам о его качестве. Однако просьба не забывать, что 2 недели назад я путал питона с ужом :) Во-вторых, мне интересно было бы узнать о подобных альтернативах. Все же я не верю, что такое никто не реализовывал ранее. Ну и, в третьих, на тот случай, если мой код или его  описание окажется для кого-то в чем-то полезным. Буду рад. Код, описанием и историей которого является эта статья доступен по ссылке.