- Форматирование текста в Telegram
- Форматирование текста через своего Telegram бота
- Форматирование текста через Telegram бота @bold
- Визуальная форматирование текста на iPhone / iPad
- Форматирование текста на остальных устройствах
- Простой Telegram-бот на Python за 30 минут
- Часть 1: Регистрация бота
- Часть 2: Подготовка к написанию кода
- Часть 3: Получаем сообщения и говорим «Привет»
- Часть 4: Кнопки и ветки сообщений
- TelegramBot. Базовый функционал. Мухи отдельно, котлеты отдельно. (Часть 2)
- Обработка команд
- Мухи отдельно, котлеты отдельно
- Пишем telegram бота на языке R (часть 1): Создаём бота, и отправляем с его помощью сообщения в telegram
- Все статьи из серии «Пишем telegram бота на языке R»
- Содержание
- Создание телеграм бота
- Установка пакета для работы с телеграм ботом на R
- Отправка сообщений из R в Telegram
- Как отправить в telegram таблицу
- Как добавить в сообщение Emoji
- Проверка планировщика задач Windows, и отправка уведомления о задачах, работа которых была завершена аварийно
- Настраиваем расписание запуска проверки задач
- Заключение
Форматирование текста в Telegram
А вы знаете, что в Telegram можно форматировать текст: жирный, курсив, код … и т.п.? Для этого можно использовать готовых ботов или Telegram Bot API, что не так и сложно; а можно стандартные средства месенджера … главное знать как!
Начнём с того, что до недавнего времени форматирование текста сообщений в Телеграм было доступно только ботам. Оно и сейчас доступно им в гораздо большем объёме и стилях.
Markdown стиль разметки текста сообщений в Telegram
HTML стиль разметки текста сообщений в Telegram
Конечно, у такого способа есть один ключевой недостаток — публикация сообщения от имени бота или с указанием «via» бота. Да и нужно же создать этого бота или использовать существующий, например @bold.
Есть и другие нюансы, которые связаны с экранированием спецсимволов … но этот вопрос я опущу, чтобы не грузить вас лишней информацией.
Форматирование текста через своего Telegram бота
Ну а тем, кто всё же хочет воспользоваться своим ботом, без программирования, подскажу один лайфхак. О том, как создать и получить токен бота я уже рассказывал и теперь мы можем воспользоваться Telegram Bot API для отправки запроса через адресную строку браузера … как говорится: почувствуй себя хацкером!
Такой URL имеет следующий формат:
Ща посмотрим, что из этого получится…
Опубликованное Telegram ботом сообщение с форматированием текста
Как вы видите, у нас всё получилось, и ничего сложного в этом нет … а ниже я выложу скриншот результата обращения по указанному URL в браузере … там будет ответ Telegram в формате JSON.
ответ Telegram Bot API в формате JSON
Форматирование текста через Telegram бота @bold
А теперь о @bold боте. Совсем чуть-чуть, но его стоит упомянуть.
Начну с того, что @bold является одним из официальных и старейших ботов Telegram и служит для создания небольших (до 260 знаков, без учёта знаков разметки, остальное урезается) сообщений с форматированием текста в inline-режиме (прописывается via бота).
Для использования @bold бота начните сообщение с его юзернейма, а потом укажите текст с разметкой, напримере:
По мере ввода текста бот выдаст всплывающее меню с вариантами разметки: Bold, Italic и Fixedsys (для форматирования строки текста в целом) и Custom (если вы использовали Markdown разметку).
Скриншот Telegram на Windows с использованием @bold бота для форматирования текста небольших сообщений
Визуальная форматирование текста на iPhone / iPad
Ну, а теперь у нас на очереди те возможности форматирования текста, которые доступны в приложении Телеграм. И начнём мы с Telegram для iPhone / iPad, т. к. только тут (насколько мне известно) доступно визуальное форматирование текста.
Примечание: на старых (уже?) iPhone / iPad это может и не работать.
Cкриншот Telegram на Iphone с использованием визуального форматирования текста сообщения.
Форматирование текста на остальных устройствах
Markdown разметка текста в приложении Telegram
Источник
Простой Telegram-бот на Python за 30 минут
На Хабре, да и не только, про ботов рассказано уже так много, что даже слишком. Но заинтересовавшись пару недель назад данной темой, найти нормальный материал у меня так и не вышло: все статьи были либо для совсем чайников и ограничивались отправкой сообщения в ответ на сообщение пользователя, либо были неактуальны. Это и подтолкнуло меня на написание статьи, которая бы объяснила такому же новичку, как я, как написать и запустить более-менее осмысленного бота (с возможностью расширения функциональности).
Часть 1: Регистрация бота
Самая простая и описанная часть. Очень коротко: нужно найти бота @BotFather, написать ему /start, или /newbot, заполнить поля, которые он спросит (название бота и его короткое имя), и получить сообщение с токеном бота и ссылкой на документацию. Токен нужно сохранить, желательно надёжно, так как это единственный ключ для авторизации бота и взаимодействия с ним.
Часть 2: Подготовка к написанию кода
Как уже было сказано в заголовке, писать бота мы будем на Python’е. В данной статье будет описана работа с библиотекой PyTelegramBotAPI (Telebot). Если у вас не установлен Python, то сперва нужно сделать это: в терминале Linux нужно ввести
После, в терминале Linux, или командной строке Windows вводим
Теперь все готово для написания кода.
Часть 3: Получаем сообщения и говорим «Привет»
Небольшое отступление. Телеграмм умеет сообщать боту о действиях пользователя двумя способами: через ответ на запрос сервера (Long Poll), и через Webhook, когда сервер Телеграмма сам присылает сообщение о том, что кто-то написал боту. Второй способ явно выглядит лучше, но требует выделенного IP-адреса, и установленного SSL на сервере. В этой статье я хочу рассказать о написании бота, а не настройке сервера, поэтому пользоваться мы будем Long Poll’ом.
Открывайте ваш любимый текстовый редактор, и давайте писать код бота!
Первое, что нужно сделать это импортировать нашу библиотеку и подключить токен бота:
Теперь объявим метод для получения текстовых сообщений:
В этом участке кода мы объявили слушателя для текстовых сообщений и метод их обработки. Поле content_types может принимать разные значения, и не только одно, например
Будет реагировать на текстовые сообщения, документы и аудио. Более подробно можно почитать в официальной документации
Теперь добавим в наш метод немного функционала: если пользователь напишет нам «Привет», то скажем ему «Привет, чем я могу помочь?», а если нам напишут команду «/help», то скажем пользователю написать «Привет»:
Данный участок кода не требует комментариев, как мне кажется. Теперь нужно добавить в наш код только одну строчку (вне всех методов).
Теперь наш бот будет постоянно спрашивать у сервера Телеграмма «Мне кто-нибудь написал?», и если мы напишем нашему боту, то Телеграмм передаст ему наше сообщение. Сохраняем весь файл, и пишем в консоли
Где bot.py – имя нашего файла.
Теперь можно написать боту и посмотреть на результат:
Часть 4: Кнопки и ветки сообщений
Отправлять сообщения это несомненно весело, но ещё веселее вести с пользователем диалог: задавать ему вопросы и получать на них ответы. Допустим, теперь наш бот будет спрашивать у пользователя по очереди его имя, фамилию и возраст. Для этого мы будем использовать метод register_next_step_handler бота:
И так, данные пользователя мы записали. В этом примере показан очень упрощённый пример, по хорошему, хранить промежуточные данные и состояния пользователя нужно в БД, но мы сегодня работаем с ботом, а не с базами данных. Последний штрих – запросим у пользователей подтверждение того, что все введено верно, да не просто так, а с кнопками! Для этого немного отредактируем код метода get_age
И теперь наш бот отправляет клавиатуру, но если на нее нажать, то ничего не произойдёт. Потому что мы не написали метод-обработчик. Давайте напишем:
Остаётся только дописать в начало файла одну строку:
Вот и всё, сохраняем и запускаем нашего бота:
Источник
TelegramBot. Базовый функционал. Мухи отдельно, котлеты отдельно. (Часть 2)
Продолжаем разрабатывать базовый функционал для бота в телеграм. В предыдущих частях обсуждался момент, что работу бота по получению сообщений, обработке и отправке нужно разделять. Давайте попробуем с помощью базовых инструментов Java Core сделать нашего бота многопоточным и асинхронным. Придумаем ему такое задание, которое занимает очень много времени на обработку. Рассмотрим как работают команды в телеграмме и как их нужно обрабатывать.
Это продолжение первой части статьи по программированию ботов для телеграмм на Java
TelegramBot инструкция по созданию базового функционала для бота. (Часть 1)
Кому интересно дальше, милости прошу под кат…
Сразу скажу, в этой части добавлено сразу много всего и по-тихоньку мы с вами разберем всю ту функциональность, что позволила боту суметь в многопоточность и зачем она ему вообще нужна.
Как обычно с главного:
Весь готовый код по этой статье вы можете найти в ветке Part2-Handlers в гит-репозитории.
Код полностью рабочий, достаточно склонироваться, изменить данные для авторизации бота(имя и токен) и запустить метод main в классе App.class.
Обратите внимание, данный класс при старте бота отправляет уведомление админу бота о том, что бот стартовал. ID админа бота также указывается в классе App.class и если вы его не измените — ваш бот будет пытаться слать сообщения мне 🙂
А дальше по пунктам разберем те изменения, которые появились после выхода первой части.
Обработка команд
Давайте для начала разберемся с таким понятием, что такое вообще команда в системе общения с телеграм ботом. В зависимости от настроек бота, он может либо видеть любые сообщения любого формата либо только специально оформленные команды. В чем отличие и
где можно встретить эти варианты сообщений.
Вызвав команду /myBots выберите вашего бота и дальше кнопка «Edit Bot»
Вы получите такое окно, где будут показаны все параметры бота и дальше можно настроить весь его интерфейс и указать с какими командами ваш бот умеет работать.
Задаются они вот в таком формате:
И после этого при начале ввода команды вашему боту — он будет показывать помощь со списком перечисленных команд:
И есть еще один нюанс. В группе может присутствовать несколько ботов и если у них есть общие команды(а общие команды обязательно будут, те же start и help реализованы у большей части ботов), то к самой команде обязательно будет добавлена часть, сообщающая к какому боту эта команда относится. И выглядеть команда полностью будет вот так:
/start@test_habr_bot
И вот зная все эти нюансы, давайте создадим с вами такой вариант обработки, который должен понимать команды, начинающиеся с наклонной черты и умеющий отличать адресована ли команда именно вашему боту или какому-то другому.
Создадим package, который у нас будет содержать классы, ответственные за обработку команд.
package com.example.telegrambot.command
В классе Command мы перечислим все команды, которые должен уметь понимать наш бот.
Как вы видели ранее, я у @BotFather указал, что бот у меня должен уметь понимать 4 команды. Это будут стандартные start и help. Одну добавим полезную — id. И еще одну, notify, про которую я расскажу чуть позже. И две команды NONE и NOTFORME, которые будут говорить нам, что текст сообщение либо вообще не является командой, либо это команда не для нашего бота.
Еще добавим один вспомогательный класс ParsedCommand
Основное его назначение — в объектах этого класса мы будет хранить результат парсинга текста. Содержаться в нем будет только сама команда и весь текст, который идет после команды.
И напишем отдельный класс, который будет нам парсить команды. Класс Parser
Вкратце. При инициализации парсера мы обязательно передаем в конструкторе имя нашего бота, чтобы парсер умел отличать свои команды от чужих.
Ну и дальше мы просто вызываем публичный метод
Которому в аргументах передаем текст сообщения, а он нам в ответ должен вернуть команду и текст сообщения идущий после команды.
Как работает парсер вы можете увидеть в тестовом классе.
Мухи отдельно, котлеты отдельно
Теперь нам нужно научить нашего бота раздельно получать сообщения, обрабатывать и отправлять ответы. После ряда проб и ошибок я пришел вот к такой логике работы приложения.
Основной класс Bot будет работать в основном потоке приложения и будет занят только тем, что все полученные сообщения будет складывать в специальную очередь и так же будет контейнером для сообщений, которые мы планируем отправить пользователю в ответ.
Изменения в этом классе очень незначительные. Мы добавили две очереди:
и немного переписали код функции public void onUpdateReceived(Update update)
Почему так? Опять таки я перепробовал разные варианты. И основная проблема многопоточности — это работа с общими данными. И мне больше всего понравилось как с этим справляется реализация многопоточных очередей ConcurrentLinkedQueue<>()
И как вы видите, в обоих очередях у нас будут хранится типы данных Object. Это еще одна закладка на будущее. Таким образом мы не привязываемся к типам полученных сообщений. Во входящую очередь сможем складывать не только объекты типа Update а и какие-то другие, нужные нам.
Тоже самое и с очередью на отправку. Так как мы можем отправлять самые разные типы сообщений и у них нет общего родителя — мы также используем общий тип данных — Object.
Если в таком виде запустить бота, то он будет работать, но не будет делать ничего. Все полученные сообщения он будет фиксировать в логе и складывать в очередь.
Следовательно нам нужен какой-то поток, который займется тем, что будет забирать из очереди принятые сообщения и совершать над ними какие-то действия и складывать в очередь sendQueue результаты своей работы.
Создадим отдельный package: service и в нем у нас будет всего 2 класса:
MessageReciever — обработчик полученных сообщений
MessageSender — обработчик очереди сообщений, которые нужно отправить пользователю.
Их работу мы рассмотрим чуть ниже, а пока пропишем их использование в нашем стартовом классе App
После того, как наш бот приконнектился — мы стартует наши обработчики в отдельных потоках:
Для обоих потоков мы указываем режим Daemon. Это нужно для того, чтобы потоки работали до тех пор, пока работает основной поток и сами завершались, как только он свою работу прекращает.
Как бы не хотелось разобраться в первую очередь с обработчиком входящих сообщений — давайте разберем работу класса MessageSender.
Разберем по пунктам, что он умеет и что делает:
Тут мы запускам бесконечный цикл, который занят только тем, что он проверяет очередь на отправку и вызывает команду send
Он, как и MessageSender, должен быть многопоточным, в конструкторе получать объект класса Bot, в котором он в бесконечном цикле будет брать принятые сообщения, обрабатывать их и складывать ему же в очередь для отправки результаты своей работы.
Тут мы используем созданный ранее парсер команд. И тут же мы добавим возможность использовать различные типы обработчиков для наших команд и некоторые из них сделаем многопоточными.
Цикл работы очень простой:
Проверяем очередь. Если что-то есть — запускаем анализатор:
Если ничего нет — ждем.
Анализатор проверяет тип объекта. Если он умеет с ним работать — запускает следующий анализатор. Если не умеет — ругается 🙂
Почему так? Опять таки это закладка на будущее и, надеюсь, я её раскрою в следующих частях этого цикла статей. Такая реализация позволит нам потом формировать свои какие-то задания для бота, делать списки рассылок, дневные задания. Для этого ресивер должен уметь обрабатывать не только объекты типа Update а и что-то наше. Но об этом потом 🙂
Рассмотрим подробнее анализатор для типа Update:
Он определяет ID чата. Получает текст сообщения. С помощью парсера определяет, является ли сообщение командой и определяет каким хендлером данную команду нужно обрабатывать. Запускает обработку команды и если обработка команды вернула какой-то непустой текст — формирует сообщение для отправки пользователю и складывает его в очередь.
И тут у вас должен возникнуть вопрос: «Что еще за хендлер?». Речи про него раньше не шло и в коде он не упоминался. Все верно. Сейчас мы этот функционал и разберем.
Для этого создадим отдельный package, который и будет хранить все наши хендлеры. Назовем его handler
Создадим абстрактный класс AbstractHandler
У него будет базовый конструктор, в котором мы передаем с каким именно объектом Bot ему нужно будет взаимодествовать. И объявлена абстрактная функция operate реализацию которой мы должны будем прописать в наследниках нашего класса.
И сразу же реализуем самый простой хендлер, который не будет делать ничего и использовать мы его будем тогда, когда не сможем понять что за тип команды нам передали и от бота никакой реакции не требуется.
Как мы его применим и где мы получим результаты его работы — разберем чуть позже.
Следующий на очереди — SystemHandler
Он у нас займется обработкой базовых команд, таких как start, help и мы поручим ему еще и выполнение команды id
Основа его выглядит так:
Как реализовано формирование ответа на команду start и help вы можете посмотреть в коде 🙂
Формируем текстовые сообщения и складываем их в очередь на отправку. На этом работа хендлера прекращается. Кто и как отправит эти сообщения — его совершенно не волнует.
И помните чуть выше я упоминал, что в результате работы хендлера он возвращает какие-то текстовые данные. И если эта строка не пустая — мы этот текст должны отправить пользователю. Вот именно этот функционал мы и использовали при отработке команды ID:
Хендлер вернет текст с ID пользователя тому, кто его вызвал и уже там сформируется сообщение на отправку, которое и уйдет потом в очередь.
И в начале статьи я упомянул, что мы реализуем такой вариант обработки сообщения пользователя, которому на работу нужно время. И чтобы он не мешал нашим обработчикам — мы его выделим в отдельный поток и пусть он занимается своим делами, не отвлекая остальных.
В качестве такого «тяжеловесного» потока я придумал команду notify. Принцип её работы такой.
Передав боту команду вида:
/notify 300
Бот должен вам сообщить, что команду понял и через 300 секунд он пришлет вам какое-то уведомление, что 300 секунд прошли. У этой команды может быть даже практическое применение 🙂
Например вы поставили пельмешки на огонь и вам их нужно снять через 5 минут. Бот с этим отлично справится и кинет вам в чат уведомление о том, что время вышло.
Или возьмем задачу более серьезную. Вы идете на важную встречу и вы знаете, что при общении с собеседником вам нужно будет прервать разговор. Для этого обычно просят друзей или позвонить или написать сообщение, что и будет являться мотивом не на долго отвлечься от беседы и сделать какие-то действия. Но зачем мучать друзей, когда у вас есть бот? Задав ему заранее задание и указав время — вы получите нужное уведомление в телеграм. Но это все лирика. Задание и эта команда придуманы лишь для того, чтобы показать вам как выделить в отдельный поток что-то, чья работа может занимать очень большой промежуток времени.
Проверяем, передали ли нам в тексте время задержки. Если нет — ругаемся. Если да — стартуем новый поток, куда мы передаем вводные по нашему заданию. Выполнением этого задания займется отдельный класс Notify.
Функционал предельно простой. Он спит указанное колличество секунд. Но в процессе его сна ваш бот умеет принимать любые другие сообщения, общаться с вами, запускать еще дополнительные уведомления. И все это работает отдельно друг от друга.
И чтобы логично завершить всю эту связку с вызовом хендлеров, вернемся в наш класс MessageReciever и посмотрим как мы понимаем какой хендлер нам нужен и как мы их запускаем.
Нужный хендлер нам возвращает команда
Теперь, если вы захотите добавить еще какие-то команды, нужно будет сделать следующее:
О чем мы поговорим в следующих частях?
Нам нужно понять как формировать разные типы сообщений. Как работать с клавиатурой и кнопками. Как редактировать свои старые сообщения. Как работать с колбеками. Как давать задания боту на выполнение каких-то действий. Как формировать интерактивное сообщение с ботом и многое другое. Все дальнейшие части зависят от вас и вашей активности.
Жду в комментариях ваши отзывы и направления, которые мы рассмотрим в приоритете.
Не стесняйтесь, задавайте вопросы. Если в статье что-то не указано или какой-то момент не ясен — напишите мне об этом. Я обязательно поправлю, отредактирую или уточню спорные моменты.
Программируйте в удовольствие и да прибудет с вами сила и красивый код 🙂
Бот, написанный в этой части статьи, работает. Помучать его можно тут: @test_habr_bot
Так же можно помучать моего планировщика: @EventCheckPlanner_Bot
И Дерзкого киномана: @FilmFanAmateurBot.
Источник
Пишем telegram бота на языке R (часть 1): Создаём бота, и отправляем с его помощью сообщения в telegram
Аудитория telegram ежедневно растёт с геометрической прогрессией, этому способствует удобство мессенджера, наличие каналов, чатов, и конечно возможность создавать ботов.
Боты могут использоваться в совершенно разных целях, от автоматизации коммуникации с вашими клиентами до управления вашими собственными задачами.
По сути через бота можно используя telegram выполнять любые операции: отправлять, либо запрашивать данные, запускать задачи на сервере, собирать информацию в базу данных, отправлять электронные письма и так далее.
Я планирую написать серию статей, о том, как на языке R работать с telegram bot API, и писать ботов под свои нужды.
В этой, первой статье мы разберёмся как создать телеграм бота, и отправлять с его помощью уведомления в telegram.
В результате у нас получится бот, который будет проверять статус последнего выполнения всех задач в планировщике заданий Windows, и отправлять вам уведомления, если какие-то завершились ошибкой.
Все статьи из серии «Пишем telegram бота на языке R»
Содержание
Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Создание телеграм бота
После чего вы получите сообщение со списком команд:
BotFather попросит вас ввести имя и логин бота.
Если вы всё сделали правильно, то получите следующее сообщение:
Более подробно о возможностях BotFather можно узнать из этой публикации. На этом шаге подготовительные работы по созданию бота завершены.
Установка пакета для работы с телеграм ботом на R
Я предполагаю, что у вас уже установлен язык R, и среда разработки RStudio. Если это не так, то вы можете посмотреть данный видео урок о том, как их установить.
Для работы с Telegram Bot API мы будем использовать R пакет telegram.bot.
Более подробно узнать об установке различных пакетов можно из этого видео.
После установки пакета его необходимо подключить:
Отправка сообщений из R в Telegram
Отправьте боту любое сообщение, например «Привет бот». На данный момент это нам надо для того, что бы получить id вашего с ботом чата.
Теперь в R пишем следующий код.
Создать переменную среды можно несколькими способами, я расскажу о наиболее универсальном и кроссплатформенном. Создайте в вашей домашней директории (узнать её можно с помощью команды path.expand(«
«) ) текстовый файл с названием .Renviron. Сделать это также можно с помощью команды file.edit(path.expand(file.path(«
И добавьте в него следующую строку.
Помимо id чата из объекта полученного методом getUpdates() вы получаете и некоторую другую полезную информацию. Например, информацию о пользователе, отправившем сообщение.
Основы форматирования Markdown разметки:
Теги HTML разметки
Помимо текста вы можете отправлять и другой контент используя специальные методы:
Как отправить в telegram таблицу
К сожалению на момент написания статьи telegram не поддерживает полноценные таблицы в HTML или Markdown, но вы можете иметировать подобие таблицы. Для этого воспользуйтесь кодом представленной ниже функции to_tg_table() :
С помощью этой функци вы можете преобразовать любой data.frame и отправить в telegram:
В telegram это буедет выглядеть так:
У функции to_tg_table() есть несколько дополнительных аргументов:
Пример с выравниванием столбцов:
Как добавить в сообщение Emoji
Требования к телеграм ботам могут быть разные, в том числе заказчик может попросить вас добавить в сообщения бота какие то Emoji.
Получить полный список доступных смайлов можно по этой ссылке.
Результат:
Проверка планировщика задач Windows, и отправка уведомления о задачах, работа которых была завершена аварийно
Далее с помощью функции taskscheduler_ls() мы запрашиваем информацию о задачах из нашего планировщика. С помощью функции filter() из пакета dplyr мы убираем из списка задач те, которые были успешно выполненны и имеют статус последнего результата 0, и те, которые ещё ни разу не запускались и имеют статус 267011, выключенные задачи, и задачи которые выполняются в данный момент.
В объекте task у нас теперь список задач, работа которых завершилась ошибкой, этот список нам надо отправить в Telegram.
Если рассмотреть каждую команду подробнее, то:
Всё что нам остаётся — отправить этот результат в телеграм.
Итак, на данный момент код бота выглядит вот так:
При использовании приведённого выше примера подставьте в код токен вашего бота и ваш идентификатор чата.
Вы можете добавлять условия фильтрации задач, например проверяя только те задачи, которые были созданны вами, исключая системные.
Настраиваем расписание запуска проверки задач
Наиболее подробно процесс настройки запуска скриптов по расписанию описан в этой статье. Тут я лишь опишу шаги, которые для этого необходимо выполнить. Если какой-то из шагов вам не понятен, то обратитесь к статье на которую я указал ссылку.
Заключение
В этой статье мы разобрались с тем, как создать бота, и отправлять с его помощью различные уведомления в telegram.
Я описал задачу контроля планировщика заданий Windows, но вы можете использовать материал этой статьи для отправки любых уведомлений, от прогноза погоды до котировок акций на фондовой бирже, т.к. R позволяет вам подключиться к огромному количеству источников данных.
В следующей статье мы с вами разберёмся с тем, как добавить боту команды и фильтры сообщений, для того, что он мог не только отправлять уведомления, но и выполнять более сложные действия.
Источник