Большинство Telegram-ботов выглядят одинаково. /start — стена текста — кнопки. Пользователь тыкает, получает ответ, закрывает. Никакого ощущения что за ботом стоит что-то живое. Конверсия падает, люди не возвращаются, и ты не понимаешь почему — ведь функционал вроде работает.
Проблема обычно не в функционале. Проблема в деталях. Бот отвечает мгновенно как машина, не помнит кто ты, не даёт ощущения прогресса, не реагирует на действия. Пользователь это чувствует — даже если не осознаёт. И просто уходит.
В этой статье я собрал 7 конкретных фич с кодом на aiogram 3.x которые это исправляют. Некоторые внедряются за пять минут, некоторые требуют больше времени — но каждая влияет либо на удержание, либо на монетизацию, либо на рост аудитории. Без воды, сразу к делу.
1. Рассылка за Telegram Stars: монетизация контента прямо в мессенджере
Telegram Stars — внутренняя валюта Telegram. Ты как разработчик получаешь звёзды и выводишь их в деньги через Fragment. Метод send_paid_media позволяет продавать контент прямо внутри бота — без подключения платёжных шлюзов, без форм, без головной боли с эквайрингом.
Как это выглядит для пользователя: приходит сообщение с описанием и размытым превью. Нажимает «Оплатить», подтверждает списание Stars — медиа мгновенно открывается. Весь процесс — секунд десять.
Где применяют:
-
Авторский контент: фотографы, художники, дизайнеры продают работы напрямую
-
Обучение: уроки и разборы в формате видео
-
Эксклюзивные рассылки: пользователь видит интригующий анонс и платит за полную версию
-
Закрытые материалы каналов и сообществ
from aiogram import Bot, Routerfrom aiogram.types import Message, InputPaidMediaPhoto, FSInputFilefrom aiogram.filters import Commandrouter = Router()@router.message(Command("buy"))async def send_paid_content(message: Message, bot: Bot): await bot.send_paid_media( chat_id=message.chat.id, star_count=50, # цена в Stars media=[InputPaidMediaPhoto(media=FSInputFile("exclusive.jpg"))], caption="🔥 Эксклюзивный контент — только для тех, кто платит" )# Фиксируем покупку в базе данных@router.message(F.successful_payment)async def on_successful_payment(message: Message): user_id = message.from_user.id # await db.save_purchase(user_id) # сохраняем в БД await message.answer("✅ Оплата прошла! Приятного просмотра.")
Главное преимущество — Telegram сам показывает медиа размытым, сам принимает оплату, сам открывает контент после списания Stars. Твоя задача — только зафиксировать покупку в базе.
Ограничения:
-
Stars пока нельзя вывести в некоторых странах — проверяй на Fragment
-
Пользователь должен сначала купить Stars — не все к этому готовы
-
Курс конвертации устанавливает Telegram
2. Обязательная подписка на канал: быстрый рост аудитории
Один из самых эффективных приёмов для роста — пользователь не может пользоваться ботом пока не подпишется на канал. Звучит агрессивно, но работает. Особенно если бот полезный.
Схема: пользователь запускает бота → видит сообщение с просьбой подписаться → подписывается → нажимает кнопку «Проверить» → бот открывает полный доступ.
Совет: добавляй в это сообщение гифку или фото — так оно выглядит живее и не как стена текста.
from aiogram import Bot, Router, Ffrom aiogram.types import ( Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, FSInputFile)from aiogram.filters import CommandStartrouter = Router()CHANNEL_ID = "@your_channel" # или числовой ID каналаasync def check_subscription(bot: Bot, user_id: int) -> bool: member = await bot.get_chat_member( chat_id=CHANNEL_ID, user_id=user_id ) return member.status not in ["left", "kicked"]def subscribe_keyboard() -> InlineKeyboardMarkup: return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton( text="📢 Подписаться на канал", url=f"https://t.me/your_channel" )], [InlineKeyboardButton( text="✅ Проверить подписку", callback_data="check_sub" )] ])@router.message(CommandStart())async def start(message: Message, bot: Bot): is_subscribed = await check_subscription(bot, message.from_user.id) if not is_subscribed: await message.answer_animation( animation=FSInputFile("welcome.gif"), caption=( "👋 Привет! Чтобы пользоваться ботом,\n" "сначала подпишись на наш канал 👇" ), reply_markup=subscribe_keyboard() ) return await message.answer("Добро пожаловать! 🎉")@router.callback_query(F.data == "check_sub")async def check_sub_callback(callback: CallbackQuery, bot: Bot): is_subscribed = await check_subscription(bot, callback.from_user.id) if not is_subscribed: await callback.answer( "❌ Ты ещё не подписался!", show_alert=True ) return await callback.message.delete() await callback.message.answer("✅ Подписка подтверждена! Добро пожаловать.") await callback.answer()
Плюсы подхода:
-
Быстрый рост подписчиков канала
-
Канал можно дополнительно монетизировать через рекламу и платные посты
-
Пользователи уже «тёплые» — знакомы с брендом до того как начали пользоваться ботом
3. Эффекты сообщений: бот с настроением
Мало кто знает, что Telegram позволяет отправлять сообщения с анимационными эффектами — конфетти, огонь, сердечки. Задаётся через параметр message_effect_id.
Доступные эффекты:
|
Эффект |
ID |
|---|---|
|
🔥 Огонь |
|
|
👍 Лайк |
|
|
👎 Дизлайк |
|
|
❤️ Сердце |
|
|
🎉 Конфетти |
|
|
💩 Какашка |
|
from aiogram import Routerfrom aiogram.types import Messagefrom aiogram.filters import Commandrouter = Router()# ID эффектовEFFECT_FIRE = "5104841245755180586"EFFECT_CONFETTI = "5046509860389126442"EFFECT_HEART = "5044134455711629726"@router.message(Command("buy_success"))async def payment_success(message: Message): await message.answer( "🎉 Оплата прошла успешно!", message_effect_id=EFFECT_CONFETTI )@router.message(Command("like"))async def send_like(message: Message): await message.answer( "Спасибо за отзыв! ❤️", message_effect_id=EFFECT_HEART )@router.message(Command("hot_deal"))async def hot_deal(message: Message): await message.answer( "🔥 Горячее предложение — только сегодня!", message_effect_id=EFFECT_FIRE )
Где использовать:
-
Успешная оплата → конфетти
-
Приветствие нового пользователя → сердечко
-
Срочная акция → огонь
-
Ошибка → дизлайк (да, и такое бывает уместно)
Эффекты — мелочь, но именно из таких мелочей складывается ощущение «живого» бота.
4. Стриминг ответа: текст появляется в реальном времени
Вместо того чтобы ждать пока бот обработает запрос и пришлёт сообщение целиком — текст появляется прямо по мере генерации. Так работает ChatGPT, так работают все современные AI-боты.
Механика простая: отправляем пустое сообщение, потом редактируем его каждые N миллисекунд добавляя новые куски текста.
import asynciofrom aiogram import Routerfrom aiogram.types import Messagefrom aiogram.filters import Commandfrom typing import AsyncGeneratorrouter = Router()# Симуляция стриминга — замени на реальный AI (OpenAI, etc.)async def fake_stream(text: str) -> AsyncGenerator[str, None]: for word in text.split(): yield word + " " await asyncio.sleep(0.1)async def stream_reply(message: Message, generator: AsyncGenerator) -> None: # Отправляем сообщение с курсором sent = await message.answer("▌") full_text = "" last_edit = asyncio.get_event_loop().time() async for chunk in generator: full_text += chunk now = asyncio.get_event_loop().time() # Редактируем не чаще чем раз в 0.5 сек — иначе flood limit if now - last_edit >= 0.5: await sent.edit_text(full_text + "▌") last_edit = now # Финальное сообщение без курсора await sent.edit_text(full_text)@router.message(Command("ask"))async def handle_ask(message: Message) -> None: response_text = "Это пример стриминга в Telegram боте. Текст появляется постепенно прямо как в ChatGPT." await stream_reply(message, fake_stream(response_text))
С реальным OpenAI стримингом:
from openai import AsyncOpenAIclient = AsyncOpenAI(api_key="your_key")async def openai_stream(user_message: str) -> AsyncGenerator[str, None]: stream = await client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": user_message}], stream=True ) async for chunk in stream: delta = chunk.choices[0].delta.content if delta: yield delta@router.message(Command("gpt"))async def handle_gpt(message: Message) -> None: await stream_reply(message, openai_stream(message.text))
Главное про flood limit: Telegram не даёт редактировать сообщение чаще чем раз в секунду. Поэтому копим чанки и редактируем с задержкой 0.5s — так и плавно и без бана.
Если хочешь попробовать как это ощущается на практике, то у меня есть бесплатный телеграм-бот. Это бот с четырьмя моделями внутри: ChatGPT, Gemini, Grok и DeepSeek. Отвечает в режиме реального времени — текст появляется прямо по мере генерации.
5. Онбординг с прогресс-баром: снижаем drop-off при регистрации
Когда пользователь регистрируется в боте и не видит прогресса — он бросает на середине. Прогресс-бар [■■■□□] решает это. Человек видит что осталось 2 шага из 5 и продолжает.
from aiogram import Router, Ffrom aiogram.types import Message, ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemovefrom aiogram.filters import CommandStartfrom aiogram.fsm.context import FSMContextfrom aiogram.fsm.state import State, StatesGrouprouter = Router()class Registration(StatesGroup): name = State() age = State() city = State() phone = State()def progress_bar(current: int, total: int) -> str: filled = "■" * current empty = "□" * (total - current) return f"[{filled}{empty}] Шаг {current} из {total}"@router.message(CommandStart())async def start_registration(message: Message, state: FSMContext): await state.set_state(Registration.name) await message.answer( f"{progress_bar(1, 4)}\n\n" f"👋 Привет! Давай познакомимся.\n" f"Как тебя зовут?", reply_markup=ReplyKeyboardRemove() )@router.message(Registration.name)async def get_name(message: Message, state: FSMContext): await state.update_data(name=message.text) await state.set_state(Registration.age) await message.answer( f"{progress_bar(2, 4)}\n\n" f"Отлично, {message.text}! Сколько тебе лет?" )@router.message(Registration.age)async def get_age(message: Message, state: FSMContext): await state.update_data(age=message.text) await state.set_state(Registration.city) skip_keyboard = ReplyKeyboardMarkup( keyboard=[[KeyboardButton(text="Пропустить")]], resize_keyboard=True ) await message.answer( f"{progress_bar(3, 4)}\n\n" f"Из какого ты города?", reply_markup=skip_keyboard )@router.message(Registration.city)async def get_city(message: Message, state: FSMContext): city = None if message.text == "Пропустить" else message.text await state.update_data(city=city) await state.set_state(Registration.phone) phone_keyboard = ReplyKeyboardMarkup( keyboard=[[KeyboardButton(text="📱 Поделиться номером", request_contact=True)]], resize_keyboard=True ) await message.answer( f"{progress_bar(4, 4)}\n\n" f"Последний шаг — поделись номером телефона:", reply_markup=phone_keyboard )@router.message(Registration.phone, F.contact)async def get_phone(message: Message, state: FSMContext): data = await state.get_data() await state.clear() await message.answer( f"✅ Готово! Регистрация завершена.\n\n" f"Имя: {data['name']}\n" f"Возраст: {data['age']}\n" f"Город: {data.get('city', 'не указан')}", reply_markup=ReplyKeyboardRemove() )
Прогресс-бар — психологический трюк. Люди не любят бросать незаконченное дело. Особенно когда видят что осталось чуть-чуть.
6. Персонализация: бот который тебя помнит
Персонализация — это не просто «Привет, Иван». Это когда бот помнит что ты делал, предлагает то что тебе актуально и не заставляет вводить одно и то же по второму кругу.
from aiogram import Router, Ffrom aiogram.types import Messagefrom aiogram.filters import CommandStartfrom datetime import datetimerouter = Router()# Простой in-memory стор (в продакшене заменяй на БД)user_storage: dict = {}def get_user(user_id: int) -> dict: if user_id not in user_storage: user_storage[user_id] = { "name": None, "visits": 0, "last_visit": None, "last_action": None } return user_storage[user_id]@router.message(CommandStart())async def start(message: Message): user_id = message.from_user.id first_name = message.from_user.first_name user = get_user(user_id) user["visits"] += 1 user["name"] = first_name last_visit = user["last_visit"] user["last_visit"] = datetime.now() # Первый визит if user["visits"] == 1: await message.answer( f"👋 Привет, {first_name}! Рад познакомиться.\n" f"Вот что я умею: ..." ) # Возвращающийся пользователь elif user["last_action"]: await message.answer( f"С возвращением, {first_name}! 👋\n" f"В прошлый раз ты {user['last_action']}.\n" f"Продолжим?" ) # Постоянный пользователь else: await message.answer( f"О, {first_name}! Уже {user['visits']}-й визит 🔥\n" f"Рад видеть постоянного гостя!" )@router.message(F.text == "🔍 Поиск")async def search(message: Message): user = get_user(message.from_user.id) user["last_action"] = "использовал поиск" # ... логика поиска
Что ещё можно персонализировать:
-
Язык интерфейса по
language_codeизmessage.from_user -
Рекомендации на основе истории действий
-
Напоминания с упоминанием имени
-
Разные онбординги для разных сегментов пользователей
7. Кастомный плейсхолдер: подсказка прямо в строке ввода
Когда пользователь открывает бота, в строке ввода написано «Сообщение». Нейтрально, но не информативно. Параметр input_field_placeholder позволяет заменить этот текст на свой.
Обычный плейсхолдер:

Кастомный плейсхолдер:

from aiogram import Routerfrom aiogram.types import Message, ReplyKeyboardMarkup, KeyboardButtonfrom aiogram.filters import CommandStartrouter = Router()def main_keyboard() -> ReplyKeyboardMarkup: return ReplyKeyboardMarkup( keyboard=[[KeyboardButton(text="🏠 Главное меню")]], resize_keyboard=True, is_persistent=True, # клавиатура не скрывается после нажатия input_field_placeholder="Введите название города...", )@router.message(CommandStart())async def start(message: Message): await message.answer( text="Привет! Введи город и я покажу погоду.", reply_markup=main_keyboard(), )
Параметр is_persistent=True нужен чтобы клавиатура не скрывалась после нажатия — иначе плейсхолдер пропадёт вместе с ней.
Примеры под разные боты:
# Бот погодыinput_field_placeholder="Введите название города..."# Бот поддержкиinput_field_placeholder="Опишите вашу проблему..."# Финансовый ботinput_field_placeholder="Введите сумму в рублях..."# Поиск по каталогуinput_field_placeholder="Начните вводить название товара..."
Ограничения:
-
Максимум 64 символа
-
Поддерживает эмодзи
-
Работает только с
ReplyKeyboardMarkup— для инлайн-клавиатур недоступно -
Чисто визуальная штука, на логику бота не влияет
Если после этой статьи захотелось сделать своего бота — попробуй @GenerateYourAIBot. Пишешь промпт с описанием что должен делать бот, он генерирует готовую архитектуру: структуру файлов, хэндлеры, FSM, логику. Не заменяет руки, но сильно ускоряет старт — особенно когда проект нестандартный и не хочется думать с чего начать.
Итого
Вот что у нас получилось:
|
Фича |
Сложность |
Что даёт |
|---|---|---|
|
Stars / платный контент |
⭐⭐⭐ |
Монетизация без эквайринга |
|
Обязательная подписка |
⭐⭐ |
Быстрый рост канала |
|
Эффекты сообщений |
⭐ |
Ощущение живого бота |
|
Typing imitation |
⭐ |
Естественный диалог |
|
Прогресс-бар онбординга |
⭐⭐ |
Снижение drop-off |
|
Персонализация |
⭐⭐ |
Лояльность пользователей |
|
Кастомный плейсхолдер |
⭐ |
Чище UX без лишнего текста |
Если времени мало — начни с эффектов сообщений и typing imitation. Пять минут кода, а разница заметна сразу. Потом добавляй прогресс-бар и персонализацию — это уже влияет на удержание.
ссылка на оригинал статьи https://habr.com/ru/articles/1043608/