Как сделать навигацию по каналу в Telegram. Обучение.
![]()
Как сделать навигацию в телеграм В этом видео я покажу вам, как можно упорядочить информацию на канале в Telegram или чате. Делается всё очень быстро, а пользы вагон Мои контакты: Телеграм: https://t.me/meleshko_0605 ВКонтакте: https://vk.com/id54414952 WhatsApp: https://wa.me/message/4CQDRJRFNVGYE1 #навигация #телеграм #навигациятелеграм
Показать больше
Войдите , чтобы оставлять комментарии
Создание telegram-ботов с интерактивным меню
Однажды меня попросили провести ревью и рефакторинг одного telegram-бота. Увидев файл размером 2000 строк, рассчитанный только на обработку разных меню я понял, что это требует унификации и общих подходов. Так родилась библиотека aiogram-dialog .
В этой статье я бы хотел обратить внимание на некоторые проблемы, которые мы встречаем при создании таких меню, предложить варианты их решения. А во второй половине статьи показать как это решается с помощью aiogram-dialog .
Мы не будем рассматривать архитектуру всего приложения, об этом вы можете прочитать у Фаулера или Мартина. Мы поговорим только про определенную часть UI ботов. Так же это не будет введением в разработку telegram-ботов с нуля. Я предполагаю, что читатель знаком с питоном, ООП и слышал о такой вещи как DRY. В коде примеров я использую aiogram v3.0 и надеюсь, что читатель уже использовал встроенную в библиотеку машину состояний.
Примеры выбраны так, чтобы проще было показать определенные проблемы, но это не единственные сценарии приводящие к ним.
Постановка проблемы
Шаг первый. Меню
Давайте рассмотрим небольшого бота, взаимодействующего с пользователем через сообщение с inline-клавиатурой.
Пусть сообщение содержит имя пользователя, а клавиатура содержит кнопку, включающую расширенный режим. При нажатии на кнопку с галочкой мы будем обновлять сообщение, а не посылать новое, скрывая или показывая в нем дополнительный текст. Так же заложим на будущее кнопку вызова настроек.
Для реализации такого бота нам пока потребуется 3 обработчика событий телеграм:
- событие для команды /start, отправляющее сообщение
- обработчик устанавливающий галочку
- обработчик снимающий галочку
import asyncio import os from aiogram import Router, F, Bot, Dispatcher from aiogram.filters import CommandStart from aiogram.types import ( CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, Message, ) router = Router() STEP1_EXTEND_CB = "extend" STEP1_COLLAPSE_CB = "collapse" STEP1_SETTINGS_CB = "settings" ADDITIONAL_TEXT = "Here is some additional text, which is visible only in extended mode" @router.message(CommandStart()) async def step1(message: Message): keyboard = InlineKeyboardMarkup(inline_keyboard=[[ InlineKeyboardButton(text="[ ] Extended mode", callback_data=STEP1_EXTEND_CB), InlineKeyboardButton(text="Settings", callback_data=STEP1_SETTINGS_CB), ]]) await message.answer( f"Hello, . \n\n" "Extended mode is off.", reply_markup=keyboard, ) @router.callback_query(F.data == STEP1_EXTEND_CB) async def step1_check(callback: CallbackQuery): keyboard = InlineKeyboardMarkup(inline_keyboard=[[ InlineKeyboardButton(text="[x] Extended mode", callback_data=STEP1_COLLAPSE_CB), InlineKeyboardButton(text="Settings", callback_data=STEP1_SETTINGS_CB), ]]) await callback.message.edit_text( f"Hello, . \n\n" "Extended mode is on.\n\n" + ADDITIONAL_TEXT, reply_markup=keyboard, ) @router.callback_query(F.data == STEP1_COLLAPSE_CB) async def step1_uncheck(callback: CallbackQuery): keyboard = InlineKeyboardMarkup(inline_keyboard=[[ InlineKeyboardButton(text="[ ] Extended mode", callback_data=STEP1_EXTEND_CB), InlineKeyboardButton(text="Settings", callback_data=STEP1_SETTINGS_CB), ]]) await callback.message.edit_text( f"Hello, . \n\n" "Extended mode is off.", reply_markup=keyboard, ) async def main(): bot = Bot(token=os.getenv("BOT_TOKEN")) dp = Dispatcher() dp.include_router(router) await dp.start_polling(bot) asyncio.run(main())
Проблема 1:
В обработчике разных событий есть одинаковый код генерации текста и клавиатуры. При добавлении новых кнопок или переходов между меню он снова будет дублироваться
Решение проблемы 1:
Необходимо отделить код генерации представления и код обработки действий. То есть вынести формирование текста и клавиатуры в отдельные функции, которые мы будем везде вызывать.
import asyncio import os from aiogram import Bot, Router, F, Dispatcher from aiogram.filters import CommandStart from aiogram.types import ( Message, InlineKeyboardMarkup, InlineKeyboardButton, User, Chat, CallbackQuery, ) router = Router() STEP1_EXTEND_CB = "extend" STEP1_COLLAPSE_CB = "collapse" STEP1_SETTINGS_CB = "settings" ADDITIONAL_TEXT = "Here is some additional text, which is visible only in extended mode" def step1_text(user: User, is_extended: bool) -> str: if is_extended: status = "on" suffix = "\n\n" + ADDITIONAL_TEXT else: status = "off" suffix = "" return ( f"Hello, . \n\n" f"Extended mode is ." f"" ) def step1_keyboard(is_checked: bool) -> InlineKeyboardMarkup: if is_checked: checkbox = InlineKeyboardButton( text="[x] Extended mode", callback_data=STEP1_COLLAPSE_CB, ) else: checkbox = InlineKeyboardButton( text="[ ] Extended mode", callback_data=STEP1_EXTEND_CB, ) return InlineKeyboardMarkup(inline_keyboard=[[ checkbox, InlineKeyboardButton(text="Settings", callback_data=STEP1_SETTINGS_CB), ]]) @router.message(CommandStart()) async def step1(message: Message): await message.answer( text=step1_text(user=message.from_user, is_extended=False), reply_markup=step1_keyboard(is_checked=False) ) @router.callback_query(F.data == STEP1_EXTEND_CB) async def step1_check(callback: CallbackQuery): await callback.message.edit_text( text=step1_text(user=callback.from_user, is_extended=True), reply_markup=step1_keyboard(is_checked=True) ) @router.callback_query(F.data == STEP1_COLLAPSE_CB) async def step1_uncheck(callback: CallbackQuery): await callback.message.edit_text( text=step1_text(user=callback.from_user, is_extended=False), reply_markup=step1_keyboard(is_checked=False) ) async def main(): bot = Bot(token=os.getenv("BOT_TOKEN")) dp = Dispatcher() dp.include_router(router) await dp.start_polling(bot) asyncio.run(main())
Шаг второй. Ввод текста
Добавим к обработке нажатий возможность обработки входящих сообщений. В ответ мы хотим посылать новое сообщение, содержащие актуальную информацию, не теряя при этом состояния чекбокса. В старом сообщении же мы будем скрывать клавиатуру, чтобы не иметь много похожих кнопок, на все из которых юзер может попытаться нажать.
Проблема 2:
- При входящем сообщении мы не знаем какое старое сообщение редактировать, как это было в CallbackQuery
- При входящем сообщении мы не знаем состояние чекбокса в старом сообщении
Решение проблемы 2:
Необходимо запоминать где-то состояние чата: нажата ли галочка и id последнего сообщения.
import asyncio import os from aiogram import Bot, Router, F, Dispatcher from aiogram.filters import CommandStart from aiogram.fsm.context import FSMContext from aiogram.types import ( Message, InlineKeyboardMarkup, InlineKeyboardButton, User, Chat, CallbackQuery, ) router = Router() STEP1_EXTEND_CB = "extend" STEP1_COLLAPSE_CB = "collapse" STEP1_SETTINGS_CB = "settings" IS_EXTENDED_KEY = "extended" LAST_MSG_ID_KEY = "last_message_id" ADDITIONAL_TEXT = "Here is some additional text, which is visible only in extended mode" def step1_text(user: User, is_extended: bool) -> str: if is_extended: status = "on" suffix = "\n\n" + ADDITIONAL_TEXT else: status = "off" suffix = "" return ( f"Hello, . \n\n" f"Extended mode is ." f"" ) def step1_keyboard(is_checked: bool) -> InlineKeyboardMarkup: if is_checked: checkbox = InlineKeyboardButton( text="[x] Extended mode", callback_data=STEP1_COLLAPSE_CB, ) else: checkbox = InlineKeyboardButton( text="[ ] Extended mode", callback_data=STEP1_EXTEND_CB, ) return InlineKeyboardMarkup(inline_keyboard=[[ checkbox, InlineKeyboardButton(text="Settings", callback_data=STEP1_SETTINGS_CB), ]]) @router.message(CommandStart()) async def step1(message: Message, state: FSMContext): message = await message.answer( text=step1_text(user=message.from_user, is_extended=False), reply_markup=step1_keyboard(is_checked=False) ) await state.set_data(< IS_EXTENDED_KEY: False, LAST_MSG_ID_KEY: message.message_id, >) @router.callback_query(F.data == STEP1_EXTEND_CB) async def step1_check(callback: CallbackQuery, state: FSMContext): await state.update_data() await callback.message.edit_text( text=step1_text(user=callback.from_user, is_extended=True), reply_markup=step1_keyboard(is_checked=True) ) @router.callback_query(F.data == STEP1_COLLAPSE_CB) async def step1_uncheck(callback: CallbackQuery, state: FSMContext): await state.update_data() await callback.message.edit_text( text=step1_text(user=callback.from_user, is_extended=False), reply_markup=step1_keyboard(is_checked=False) ) @router.message() async def step1_nothing(message: Message, bot: Bot, state: FSMContext): data = await state.get_data() await bot.edit_message_reply_markup( chat_id=message.chat.id, message_id=data[LAST_MSG_ID_KEY], ) message = await message.answer( text=step1_text( user=message.from_user, is_extended=data[IS_EXTENDED_KEY], ), reply_markup=step1_keyboard(is_checked=data[IS_EXTENDED_KEY]) ) data[LAST_MSG_ID_KEY] = message.message_id await state.set_data(data) async def main(): bot = Bot(token=os.getenv("BOT_TOKEN")) dp = Dispatcher() dp.include_router(router) await dp.start_polling(bot) asyncio.run(main())
Шаг третий и далее
Добавим к боту вторую клавиатуру, которая появляется по нажатию кнопки «Settings». Пусть это будет экран настроек, содержащий ещё пару чекбоксов и кнопки «сохранить» и «отменить». По Нажатию «сохранить» мы сохраняем настройки в БД и возвращаемся в прошлое меню. А по нажатию «отменить» тоже возвращаемся, но без сохранения.

Проблема 3:
Обработчик сообщения не знает что мы перешли в настройки и отправляет нам меню 1.
Решение проблемы 3:
Необходимо запоминать в каком меню мы находимся по аналогии с другими данными чата. Можно использоваться для этого State из aiogram
Проблема 4:
- В разных меню могут быть похожие по смыслу данные, необходимо следить, чтобы они не перетирали друг друга.
- Необходимо удалять временные данные настроек при выходе, так как потом они будут сброшены. При этом другие данные не должны быть затронуты. Проблема кажется несущественной пока это касается только одного меню с небольшим количеством данных — мы всегда можем перечислить их ключ. Но подход будет повторяться
Решение проблемы 4:
Заведем для каждого меню (то есть состояния или группы состояний) зафиксируем ключ, под которым его данные будут храниться в общем словаре data. При выходе из меню мы можем удалять целиком ключ
< "last_message_id": 1, "step1": < "extended": true >, "settings": < "option1": false, "option2": true >>
Проблема 5:
Повторяющиеся паттерны обработки. В разных частях программы могут повторяться чекбоксы, кнопки выбора из нескольких вариантов, календарь, переходы вперед/назад. Приходится дублировать хэндлеры, делающие одну и ту же работу:
- генерация кнопок
- сохранение своего состояния
- вызов показа нового текста и клавиатуры после нажатия
Решение проблемы 5:
- выносим каждый паттерн в отдельный класс
- добавляем ему id для генерации callback_data и хранения данных
- параметризуем экземпляр колбэк-функциями для вызова прикладной логики, не относящейся к обновлению меню
Например, класс Checkbox может выглядеть так:
class Checkbox(): def __init__( self, checked_text: str, unchecked_text: str, id: str, on_click: Optional[OnStateChanged] = None, ): . def is_checked(self, state: FMSContext) -> bool: . async def render_keyboard( self, state: FMSContext, ) -> List[List[InlineKeyboardButton]]: . async def process_callback( self, callback: CallbackQuery, state: FMSContext, ) -> bool: .
Имея набор таких виджетов (примитивов над клавиатурой, обработкой ввода или генерацией текста), логичным становится объединение их в один объект, описывающий состояние сообщения, который надо показать (далее Окно ).
Проблема 6:
В коде имеющем несколько меню возможны переходы по нескольким направлениям. Часто необходимо реализовать переход назад или в главное меню.
- В некоторые меню можно попасть разными способами и переход назад должен возвращать пользователя правильно
- Для отрисовки главного меню требуется его импортировать в другие меню, и наоборот из него мы косвенно импортируем их. Возможны циклические импорты
Решение проблемы 6:
- Заводим стек состояний. При переходе в новое меню мы не просто сохраняем его стейт, а добавляем его в стек.
- Заводим отдельный класс менеджер, следящий за состоянием стека и вызывающий отрисовку исходя из текущего состояния, очистку при возврате в главное меню
- Все переходы делается по State, а конкретные объекты окон привязываются к стейтам и регистрируются в менеджере.
Примерный вид класса менеджера стека:
class Manager: def __init__(self, windows: Dict[State, Window]): . def refresh(self, context: FMSContext): . def switch_into(self, state: State, fsm_context: FSMContext): . def switch_up(self, fsm_context: FSMContext): . def reset_stack(self, state: State, fsm_context: FSMContext): .
Концепции
В примерах выше мы пришли к следующим концепциям, помогающим структурировать переходы между меню бота:
- Разделение реакции на события и генерации сообщения
- Хранение состояний в виде стека
- Изолированные контексты для разных частей UI
- Центральный менеджер управляющий переходами состояния
- Переиспользуемые компоненты («виджеты»), группирующиеся в «окна» описывающие внешний вид сообщения
Все эти концепции уже реализованы в aiogram_dialog
Создание бота на aiogram-dialog
Установка
Устанавливаем стандартным для Python способом, например с помощью pip.
pip install aiogram-dialog==2.*
Окна и виджеты
Выше мы предполагали, что для описания внешнего вида сообщения будет использоваться набор виджетов, скомпонованный в один объект. Первое сообщение может состоять из таких частей:
-
Динамический текст, куда будут подставлены данные из текущего контекста или события.
from aiogram_dialog.widgets.text import Format text = Format( "Hello, . \n\n" "Extended mode is .\n" )
from aiogram_dialog.widgets.text import Const additional = Const( "Here is some additional text, which is visible only in extended mode", when="extended", )
from aiogram_dialog.widgets.text import Const from aiogram_dialog.widgets.kbd import Checkbox EXTEND_BTN_ID = "extend" checkbox = Checkbox( checked_text=Const("[x] Extended mode"), unchecked_text=Const("[ ] Extended mode"), )
from aiogram_dialog.widgets.text import Const from aiogram_dialog.widgets.kbd import Button button_next = Button(Const("Settings"), )
from aiogram_dialog.widgets.kbd import Row row = Row(checkbox, button_next)
Теперь объединим это всё одно окно. Так же нам потребуется создать State , для того чтобы мы могли переключиться на это окно позднее. Библиотека требует чтобы все стейты были созданы в StatesGroup , таким образом мы достигаем большей гибкости в изоляции контекстов.
from aiogram.fsm.state import State, StatesGroup from aiogram_dialog import Window from aiogram_dialog.widgets.text import Format, Const from aiogram_dialog.widgets.kbd import Checkbox, Button, Row class MainMenu(StatesGroup): START = State() EXTEND_BTN_ID = "extend" window = Window( Format( "Hello, . \n\n" "Extended mode is .\n" ), Const( "Here is some additional text, which is visible only in extended mode", when="extended", ), Row( Checkbox( checked_text=Const("[x] Extended mode"), unchecked_text=Const("[ ] Extended mode"), ), Button(Const("Settings"), ), ), state=MainMenu.START )
В процессе рендеринга данного окна туда в дальнейшем будет передан текущий контекст, откуда Checkbox прочитает своё состояние и сможет выбрать какой из двух вариантов текста использовать. Так же будет использовано текущее обрабатываемое событие (Message или CallbackQuery) чтобы подставить имя пользователя. Однако мы не указали пока что подставить в качестве в текст.
Для внедрения дополнительных данных в процессе рендеринга сообщения в окно добавляется функция-getter. Она возвращает словарь, к которому в дальнейшем можно обращаться из Format , использовать внутри виджетов, влиять на их видимость и т.п. В частности, через него делается создание кнопок для выбора из списка.
В данном случае функция-геттер должна вернуть по ключу extended_str строку «on» если галочка снята и «off» в противном случае. А по ключу extended — само булево значение опции.
В качестве параметров она получает всё, что прилетает из middleware. Пока проигнорируем это, поставив **kwargs .
async def getter(**kwargs) -> Dict[str, Any]: if True: # here will be some condition return < "extended_str": "on", "extended": True, >else: return
Менеджер и ограничение контекста
Чтобы избежать конфликтов и автоматически очищать данные, aiogram-dialog ограничивает работу с контекстом не одним State, а одной StatesGroup . Таким образом мы можем иметь более одного окна с общим контекстом, что упрощает реализацию некоторых сценариев.
Так же как стейты объединяются в StatesGroup , окна объединяются в объект Dialog .
from aiogram_dialog import Dialog main_menu = Dialog(window)
Если быть более точным, новый контекст создается каждый раз, когда вы добавляете что-то в стек переходов, но вы может сохранять контекст меняя текущий State в стеке в пределах одной StatesGroup .
Для управления переходами и контекстом, используется класс DialogManager . Он прилетает в getter и во все обработчики, обычно под именем dialog_manager .
Модифицируем наш геттер так, чтобы он выбирал текст исходя из состояния чекбокса. С dialog_manager мы найдем состояние виджета по его айди и проверим, есть ли там галочка.
from aiogram_dialog import DialogManager async def getter(dialog_manager: DialogManager, **kwargs) -> Dict[str, Any]: if dialog_manager.find(EXTEND_BTN_ID).is_checked(): return < "extended_str": "on", "extended": True, >else: return
Прежде чем запускать бота нам необходимо реализовать переход к нашему диалогу. Делается это с помощью DialogManager.start
router = Router() @router.message(CommandStart()) async def start(message: Message, dialog_manager: DialogManager): await dialog_manager.start(MainMenu.START)
Остался последний подготовительный подключить конкретные диалоги к боту и настроить сам Dispatcher на работу с библиотекой:
dp = Dispatcher() dp.include_router(main_menu) dp.include_router(router) setup_dialogs(dp)
Таким образом, целиком всё будет выглядеть так:
import asyncio import os from typing import Dict, Any from aiogram.filters import CommandStart from aiogram.fsm.state import State, StatesGroup from aiogram import Router, F, Bot, Dispatcher from aiogram.types import Message from aiogram_dialog import Dialog, Window, setup_dialogs, DialogManager from aiogram_dialog.widgets.text import Format, Const from aiogram_dialog.widgets.kbd import Checkbox, Button, Row class Step1(StatesGroup): START = State() EXTEND_BTN_ID = "extend" async def getter(dialog_manager: DialogManager, **kwargs) -> Dict[str, Any]: if dialog_manager.find(EXTEND_BTN_ID).is_checked(): return < "extended_str": "on", "extended": True, >else: return < "extended_str": "off", "extended": False, >main_menu = Dialog( Window( Format( "Hello, . \n\n" "Extended mode is .\n" ), Const( "Here is some additional text, which is visible only in extended mode", when="extended", ), Row( Checkbox( checked_text=Const("[x] Extended mode"), unchecked_text=Const("[ ] Extended mode"), ), Button(Const("Settings"), ), ), getter=getter, state=Step1.START ) ) router = Router() @router.message(CommandStart()) async def start(message: Message, dialog_manager: DialogManager): await dialog_manager.start(Step1.START) async def main(): bot = Bot(token=os.getenv("BOT_TOKEN")) dp = Dispatcher() dp.include_router(main_menu) dp.include_router(router) setup_dialogs(dp) await dp.start_polling(bot) asyncio.run(main())
Второй диалог
Второй диалог добавляется полностью аналогично первому. Мы воспользуемся новым виджетом Cancel() , который возвращает нас в предыдущее меню (с очисткой контекста, естественно).
class Settings(StatesGroup): START = State() NOTIFICATIONS_BTN_ID = "notify" ADULT_BTN_ID = "adult" settings = Dialog( Window( Const("Setting"), Checkbox( checked_text=Const("[x] Send notifications"), unchecked_text=Const("[ ] Send notifications"), ), Checkbox( checked_text=Const("[x] Adult mode"), unchecked_text=Const("[ ] Adult mode"), ), Row( Cancel(), Cancel(text=Const("Save"), ), ), state=Settings.START, ) )
Переход ко второму диалогу из первого мы можем организовать так же вызвав dialog_manager.start из обработчика кнопки next , либо заменить её на специальный виджет
Start(Const("Settings"), , state=Settings.START)
import asyncio import os from typing import Dict, Any from aiogram.filters import CommandStart from aiogram.fsm.state import State, StatesGroup from aiogram import Router, F, Bot, Dispatcher from aiogram.types import Message from aiogram_dialog import Dialog, Window, setup_dialogs, DialogManager from aiogram_dialog.widgets.text import Format, Const from aiogram_dialog.widgets.kbd import Checkbox, Button, Row, Cancel, Start class MainMenu(StatesGroup): START = State() class Settings(StatesGroup): START = State() EXTEND_BTN_ID = "extend" async def getter(dialog_manager: DialogManager, **kwargs) -> Dict[str, Any]: if dialog_manager.find(EXTEND_BTN_ID).is_checked(): return < "extended_str": "on", "extended": True, >else: return < "extended_str": "off", "extended": False, >main_menu = Dialog( Window( Format( "Hello, . \n\n" "Extended mode is .\n" ), Const( "Here is some additional text, which is visible only in extended mode", when="extended", ), Row( Checkbox( checked_text=Const("[x] Extended mode"), unchecked_text=Const("[ ] Extended mode"), ), Start(Const("Settings"), , state=Settings.START), ), getter=getter, state=MainMenu.START ) ) NOTIFICATIONS_BTN_ID = "notify" ADULT_BTN_ID = "adult" settings = Dialog( Window( Const("Settings"), Checkbox( checked_text=Const("[x] Send notifications"), unchecked_text=Const("[ ] Send notifications"), ), Checkbox( checked_text=Const("[x] Adult mode"), unchecked_text=Const("[ ] Adult mode"), ), Row( Cancel(), Cancel(text=Const("Save"), ), ), state=Settings.START, ) ) router = Router() @router.message(CommandStart()) async def start(message: Message, dialog_manager: DialogManager): await dialog_manager.start(MainMenu.START) async def main(): bot = Bot(token=os.getenv("BOT_TOKEN")) dp = Dispatcher() dp.include_router(main_menu) dp.include_router(settings) dp.include_router(router) setup_dialogs(dp) await dp.start_polling(bot) asyncio.run(main())
Заключение
Почему-то про разработку телеграмм-ботов в основном пишут статьи, рассчитанные на изучающих языки программирования, да и фреймворки почти не предлагают высокоуровневых подходов к реализации интерфейса бота. Между тем, тут можно провести параллели и с веб-разработкой и созданием мобильных приложений, перенимая концепции и паттерны (VIPER, HTTP Session, Widget, Back stack). Грамотно подходя к организации кода мы можем вложить свои силы в разработку бизнес-логики или проектирование UX вместо того, чтобы тратить их на очередное повторение реализации чекбокса в новом разделе.
Если вас заинтересовал проект, приглашаю ознакомиться с документацией и github проекта. Так же на гитхабе доступны примеры, показывающие как использовать те или иные возможности и разные виджеты.
Чем хороша библиотека:
- готовые паттерны обновления сообщений с меню
- готовые заменяемые виджеты для различных моделей поведения: чекбоксы, радио кнопки, календарь, пагинаторы, форматирование текста и многие другие
- возможность разделения реакции на события и логики отображения
- возможность писать переиспользуемые меню
- сокращение времени разработки
При этом вы можете совмещать код, написанный с использованием «диалогов», с обычным кодом на aiogram.
В данной статье отражена ничтожная часть возможностей, это введение показывающее подходы и готовую их реализацию. Так же упущены некоторые детали настройки в production-режиме.
К сожалению, вынужден признать, что даже в документации сейчас не описана часть доступной функциональности. Буду рад новым участникам проекта, который помогут решить эту проблему.
Команды. Как создать меню с командами в Telegram-боте от конструктора Fleep.Бизнес
Fleep.бизнес — конструктор ботов для тех, кто хочет зарабатывать больше.
Если перейти по ссылке t.me/FleepBot , то можно создать свой первый бот бесплатно.
Содержание
Что такое команды telegram-бота
В telegram-ботах кроме кнопок, которые могут быть снизу и под сообщением (инлайн-кнопки), есть ещё и команды меню. Они появляются если нажать синюю кнопку с тремя полосками.
Кнопки снизу и кнопки под сообщением (инлайн-кнопки)
Е сли нижн ие кноп ки свернуть, то в синей кнопке появляется название «Меню». Там и спрятан список команд
Если «Меню» нажать, то мы увидим список команд
В конструкторе Fleep.Бизнес можно создать команды и назначить, что будет показывать ваш бот по вызову каждой команды.
Любая команда состоит из трёх частей:
- описание команды, например, «Перезапуск бота сначала»;
- название команды, например, /start (эта команда одинаковая у любого бота, она перезапускает бот сначала), остальные названия вы придумываете сами и только латиницей;
- действие команды — это то, что выполнит бот после получения команды, например, отправит фотографию, или сообщение, или перезапустит бота, или откроет назначенное меню, или что угодно, что вы ей назначите.
Так же команду можно вызывать не открывая меню команд. Если ввести в поле для сообщений название команды, то бот выполнит действие этой команды.
С оздаём команду /start
Открываем свой бот, который сделали в конструкторе Fleep.Бизнес.
ВНИМАНИЕ! Если у вас ещё нет бота в конструкторе, то вот отдельная статья, как его быстро создать — telega.su/fleep-business/telegram-bot . Если бот уже есть, то переходим к следующему шагу .
После того, как открыли свой бот нажимаем кнопку «Настройки» снизу и жмём кнопку «Команды»:
После выбора в меню «Команды» попадаем в создание и настройки команд
Нажимаем большой плюс ➕, чтобы создать свою первую команду. В ответе всё понятно, комментарии излишни:
Первую команду, которую я создам будет /start она нужна для того, чтоб любой пользователь мог начать с самого начала, если у него что-то пойдёт не так или если пропадёт нижняя клавиатура.
Отправляю боту слово start и получаю ответ:
Отправляю описание команды: «Нажмите и начните сначала если пропала клавиатура снизу» и получаю ответ, в котором подсказывают, как можно потом отредактировать ответ на эту команду, если потребуется.
Так как это самая первая команда, которую отправляют пользователи вашему боту, то редактировать ответ на неё можно не только здесь, в меню «Команды», но и в другом меню путь к которому указан в ответе:
Создаём свою команду
Теперь создадим свою команду и назначим ей действие. Сначала всё делаем так же как и в первый раз:
- в меню «Команды» и нажимаем ➕.
- п отом придумаем название команды и отправим его. Я назову /kod_skidki
- теперь добавим описание команды. Моё описание такое: «Получить код на 95% скидки».
И вот после этого бот нам присылает такой ответ:
Меню. Создаём меню с кнопками в telegram-боте конструктора Fleep.Бизнес
Fleep.бизнес — конструктор ботов для тех, кто хочет зарабатывать больше.
Если перейти по ссылке t.me/FleepBot , то можно создать свой первый бот бесплатно.
Меню в ботах Fleep.Бизнес даёт нам огромные возможности. Более того, без меню бизнес-бот практически теряет свою актуальность. Давайте глянем, как это работает начиная с простых меню и заканчивая сложными. И посмотрим, как их создать и привязать к сообщениям бота, как связывать с товарами, если у вас интернет-магазин. И узнаем, где ещё можно использовать меню с кнопками, для заработка.
Оглавление
Что такое «Меню» и какие они бывают
Меню — это набор кнопок, которые ведут в другие меню с кнопками, или присылают в ответ на нажатие контент (сообщение, фото, видео, стикер и т.д.), или отправляют по ссылке внутри бота (UTM-метки) или за его пределы (в каналы и чаты, на сайты в интернете).
Меню с обычными кнопками
В первую очередь это может быть простое меню, которое ведёт пользователя по вашему боту и отвечает на его вопросы. Такой пример я показал на картинке в заголовке этой статьи. Мой бот t.me/culturenetbot при старте выдаёт именно это меню. Кнопки такого меню называются: «обычные». Обычные кнопки находятся внизу экрана под строкой для ввода сообщений.
Меню с inline-кнопками
Кнопки в меню, которые прикрепляются к сообщениям бота — называются inline-кнопки. Они выглядят вот так:
В меню может быть минимум одна кнопка и максимум 9 9 кнопок
Вложенное меню
Меню могут быть «вложенным». Это когда при нажатии какой-то из кнопок в меню бот показывает новое меню. С помощью вложенных меню создают интернет магазины и в оронк и продаж, примерно как на картинке ниже:
Воронка продаж начинается с первого меню, с пары-тройки кнопок. После нажатия одной из кнопок пользователь получает сообщение и с ним новое меню с кнопками. Каждая кнопка ведёт покупателя и пользователя бота в своё новое меню. Так до тех пор пока ваша воронка не заканчивается.
Преимущество такой ветвистости в том, что вы ведёте клиента туда, куда он хочет и в итоге продаёте ему. А ещё можете отследить всех пользователей на любом этапе. Вы увидите какие кнопки нажимали ваши пользователи и сможете разослать предложения только им — тем, кто нажимал конкретные кнопки. Но об этом позже, а сейчас продолжим уточнять, как создать меню с кнопками.
Главное меню
Итак, мы увидели, что меню — это набор кнопок, которые мы в него добавляем. Таких наборов мы можем сделать около сотни. В каждый набор можно добавить до 100 кнопок, если это нам нужно, конечно.
Любое меню, которое вы создадите существует само по себе и поначалу ни к чему не прикреплено. Оно просто существует в разделе «Меню» пока вы сами не укажете, где его показать.
Но «Главного меню» уже есть по умолчанию в боте. В «Главном меню» со старта нет кнопок. Как только вы добавите кнопки в «Главное меню» — это меню будет прикрепляться к первому сообщению, которое получит пользователь.
Давайте глянем как это выглядит в боте для вас, как творца бота, отправляем боту команду — /start:
После старта своего бизнес-бота получаем кнопку «Настройки»: жмём её
Видим по умолчанию только
«Главное меню»
«Главное меню» есть всегда. Его нельзя переименовать или удалить. Оно просто есть. Как пустой кувшин для компота. Пока мы кувшин не наполним, то разливать из него нечего. Он просто стоит и ждёт когда его наполнят, чтоб появится на столе.
Так и меню — пока в нём нет кнопок, то оно не может нигде появиться. Оно ждёт, пока вы его наполните кнопками и прикрепите куда вам нужно. Это касается любого меню, не только «главного меню».
Но у «главного меню» есть своя особенность: как только мы добавим в него хотя бы одну кнопку, то его можно не только привязать куда захотите, но также оно появится при первом старте бота пользователем. Вот что написано в нашем конструкторе после того как мы нажмём кнопку «Главное меню» с предыдущей картинки:
Собственно, это то самое место, где мы уже можем создать кнопки в «Главном меню». Но перед тем, как создать кнопки давайте посмотрим как создать наше новое меню, которое понадобится позже.
Создание меню
Представьте, что вы хотите пить и у вас есть компот. Компот не может быть сам по себе, он всегда где-то хранится. Вы хотите пить — наливаете из кувшина. Но перед тем как налить компот в стакан из кувшина вы наполнили этот кувшин. Согласитесь, трудно наливать компот из пустого кувшина, как и без кувшина налить компот в стакан.
С кнопками такая же ситуация. Кнопки это наш компот и есть.
Перед тем как привязать какую-то кнопку к нужному сообщению или товару (налить компот), вам всегда понадобится создать меню (взять пустой кувшин), потом в меню создать кнопку (наполнить кувшин компотом) и только уже после привязать это меню к нужному сообщению или товару (налить из кувшина в стакан).
Вообще, когда собираешь бот, лучше продумать заранее структуру бота и записать всё это отдельно. После чего легко увидеть сколько меню понадобиться, какие в них будут кнопки и как они должны быть расположены (обычные — снизу или inline — под сообщением или под товарами).
Но сейчас мы хотели увидеть процесс рождения меню. И начнём, как обычно: /start -> Настройки -> Меню -> ➕: