Эхо-бот и работа с текстом
Учимся доставать текст из сообщения, повторять его и переделывать: кричать капсом, переворачивать задом наперёд и считать слова — всё это твой «Цыплёнок-помощник» сделает прямо в чате.
Главная мысль урока: любое сообщение, которое пользователь шлёт боту, приходит к тебе обычной строкой Python в message.text — а со строками ты уже умеешь делать что угодно.Зачем это вообще нужно
Представь: ты сидишь в чате игрового клана, и кто-то пишет сообщение шёпотом, мелкими буквами, а ты хочешь, чтобы оно прозвучало громко, КАПСОМ, как боевой клич. Или другу нужно быстро узнать, сколько слов он наболтал в своём эссе по литературе, чтобы не превысить лимит. Или просто хочется прикольнуться и отправить текст задом наперёд, чтобы получился секретный шифр.
Всё это умеет делать бот, который сначала читает твоё сообщение, а потом переделывает его и отправляет обратно. Самый простой такой бот — это эхо-бот. Ты пишешь ему «привет», он отвечает «привет». Звучит как игрушка, но именно с эхо-бота начинается вся серьёзная работа с текстом: ведь чтобы что-то переделать, надо сначала научиться это что-то взять в руки.
Вот к чему мы придём в конце урока — бот, который не просто повторяет, а ещё и кричит твоё сообщение капсом:
Ты: привет, цыплёнок
Цыплёнок-помощник: ПРИВЕТ, ЦЫПЛЁНОК
В прошлом уроке про хэндлеры сообщений ты научился ловить любое текстовое сообщение и реагировать на него. Сегодня мы пойдём дальше: научимся доставать содержимое этого сообщения и работать с ним как с обычной строкой.
Откуда у бота берётся текст: объект message
Когда кто-то пишет твоему боту, Telegram упаковывает это сообщение в коробку с кучей информации: кто отправил, когда, в каком чате, есть ли картинка, и — самое для нас важное — какой там текст. Эту коробку aiogram передаёт в твой хэндлер под именем message.
Думай про message как про посылку с почты. На посылке есть наклейки: от кого (message.from_user), в какой чат (message.chat), а внутри лежит сам текст письма — это message.text. Чтобы прочитать письмо, ты не разрываешь всю коробку, а просто достаёшь нужную наклейку: message.text и есть та самая строка, которую напечатал пользователь.
Запомни:message.text— это обычная строка Python (типstr). Всё, что ты знаешь про строки —.upper(),.split(), срезы[::-1]— работает с ней без всякой магии.
Маленький, но важный нюанс: если пользователь прислал не текст, а стикер, фото или голосовое, то message.text будет равен None — то есть «пусто». Про это мы поговорим в разделе про ошибки, потому что именно тут новички спотыкаются чаще всего.
Почему вообще текст приходит строкой, а не как-то иначе? Потому что под капотом Telegram общается с твоей программой через Bot API — это набор HTTP-запросов, в которых данные передаются текстом в формате JSON. Когда приходит новое сообщение, Telegram присылает целое обновление (update) — пакет данных о том, что произошло. aiogram разбирает этот пакет за тебя и собирает удобный объект message, из которого ты достаёшь поле text в одну строчку. Тебе не нужно вручную парсить JSON или думать про HTTP — библиотека делает всю грязную работу, а тебе остаётся работать с обычной строкой. Это и есть главная причина, почему мы используем aiogram, а не дёргаем Bot API напрямую: меньше рутины, больше времени на интересную логику.
Пример 1. Самый простой эхо-бот
Начнём с классики. Наш «Цыплёнок-помощник» уже умеет отвечать на /start из прошлых уроков. Теперь добавим к нему хэндлер, который ловит любое текстовое сообщение и отправляет его обратно слово в слово. Мы продолжаем работать в том же файле bot.py с теми же объектами bot и dp.
from aiogram import F
from aiogram.types import Message
# bot и dp уже созданы выше, как в прошлых уроках
@dp.message(F.text)
async def echo_handler(message: Message):
text = message.text # достаём строку из сообщения
await message.answer(text) # отправляем её обратноРезультат: в чате бот ответит ровно тем же текстом, который ты ему написал. Напишешь «эй, цыплёнок» — получишь «эй, цыплёнок».
Разберём по строчкам, что тут происходит:
@dp.message(F.text)— это декоратор. Он говорит диспетчеру: «вызывай эту функцию, когда придёт сообщение, у которого есть текст». КусочекF.text— это фильтр, такой проверочный пропуск: внутрь пускаем только сообщения с текстом, а стикеры и фото отсеиваем сразу. Так мы заранее страхуемся от пустогоmessage.text.async def echo_handler(message: Message)— сама функция-хэндлер. Словоasyncозначает, что функция асинхронная, но тебе пока достаточно помнить правило: внутри неё перед вызовами бота ставимawait. Бот как официант — пока он несёт ответ на «кухню» Telegram, программа не зависает, а может обслуживать других.text = message.text— достаём строку в отдельную переменную, чтобы с ней было удобно работать дальше.await message.answer(text)— отправляем строку обратно в тот же чат. Методanswer— это удобный способ ответить туда, откуда пришло сообщение, не указываяchat_idвручную. Про разные способы отправки текста мы подробно говорили в уроке про отправку и форматирование текста.
Готово — это уже рабочий эхо-бот. Но повторять одно и то же скучно. Давай научим Цыплёнка менять текст.
Пример 2. Бот, который кричит капсом
У строк в Python есть метод .upper() — он возвращает новую строку, где все буквы стали заглавными. Прежде чем встраивать его в бота, проверим, как он работает, на маленьком чистом сниппете — его можно запустить прямо в браузере, потому что он использует только стандартный Python без всякого aiogram.
text = "тише, идёт урок"
print(text.upper())Вывод:
ТИШЕ, ИДЁТ УРОК
Видишь? .upper() аккуратно поднял регистр даже у русских букв, включая «ё». Теперь встроим это в нашего бота — заменим тело хэндлера всего одной строкой:
@dp.message(F.text)
async def shout_handler(message: Message):
loud = message.text.upper() # делаем текст громким
await message.answer(loud)Результат: в чате бот ответит твоим же сообщением, но капсом. Напишешь «привет, цыплёнок» — получишь «ПРИВЕТ, ЦЫПЛЁНОК», как в обещанном вначале примере.
Обрати внимание: мы вызвали .upper() прямо у message.text, не сохраняя промежуточную переменную. Это нормально — message.text ведёт себя как любая строка, и методы можно навешивать на него цепочкой. Главное правило: строковые методы не меняют исходную строку, а возвращают новую. Поэтому результат обязательно надо куда-то положить или сразу передать в answer.
Пример 3. Переворачиваем строку — секретный шифр
А теперь немного магии для друзей. Сделаем бота, который переворачивает текст задом наперёд. В Python это делается срезом [::-1] — он читает строку с конца к началу. Снова проверим на чистом сниппете:
text = "цыплёнок"
print(text[::-1])Вывод:
конёлпыц
Срез [::-1] — это «возьми всю строку, но иди шагом минус один», то есть справа налево. Встроим в бота:
@dp.message(F.text)
async def reverse_handler(message: Message):
reversed_text = message.text[::-1]
await message.answer(reversed_text)Результат: в чате бот ответит твоим сообщением, написанным наоборот. Напишешь «секрет» — получишь «теркес». Покажи другу — пусть гадает, как ты это зашифровал.
Почему это работает именно так? Срез — это способ взять из строки кусок: в квадратных скобках через двоеточие записывают «откуда», «докуда» и «с каким шагом». Когда первые два места пустые, Python берёт строку целиком, а третье число — это шаг. Шаг 1 означает «иди по одному символу слева направо», а шаг -1 — «иди по одному символу, но в обратную сторону». Поэтому [::-1] и читается как «вся строка задом наперёд». Этот же приём работает не только со строками, но и со списками — так что ты только что выучил универсальный трюк Python, а не какую-то особенность ботов.
Пример 4. Считаем слова в сообщении
Последний приём на сегодня — самый полезный для учёбы. Сделаем так, чтобы бот считал, сколько слов прислал пользователь. Это спасает, когда нужно уложиться в лимит по сочинению или проверить длину поста.
Чтобы посчитать слова, мы сначала разрежем строку по пробелам методом .split(). Он возвращает список слов. А длину списка узнаём функцией len(). Проверим логику на чистом сниппете:
text = "бот считает мои слова"
words = text.split()
print(words)
print(len(words))Вывод:
['бот', 'считает', 'мои', 'слова'] 4
Метод .split() без аргументов умный: он сам схлопывает лишние пробелы и не создаёт пустых слов, даже если ты поставил два пробела подряд. Теперь соберём ответ бота. Чтобы вставить число в текст, используем f-строку — это строка с буквой f перед кавычками, в которую можно подставлять значения через фигурные скобки:
@dp.message(F.text)
async def count_handler(message: Message):
words = message.text.split()
count = len(words)
await message.answer(f"В твоём сообщении {count} слов(а).")Результат: в чате бот посчитает слова. Напишешь «бот считает мои слова» — получишь «В твоём сообщении 4 слов(а).».
Собираем всё вместе: бот-меню преобразований
В реальном боте обычно не хочется выбирать заранее, что он будет делать. Давай объединим приёмы: пусть Цыплёнок на одно сообщение выдаёт сразу все три превращения. Так ты увидишь, как несколько строковых операций уживаются в одном хэндлере.
@dp.message(F.text)
async def transform_handler(message: Message):
text = message.text
loud = text.upper()
reversed_text = text[::-1]
count = len(text.split())
answer = (
f"Капсом: {loud}\n"
f"Наоборот: {reversed_text}\n"
f"Слов: {count}"
)
await message.answer(answer)Результат: в чате на сообщение «привет цыплёнок» бот ответит тремя строками — «Капсом: ПРИВЕТ ЦЫПЛЁНОК», «Наоборот: конёлпыц тевирп» и «Слов: 2». Заметь: символ \n внутри строки означает перенос строки, поэтому ответ красиво разбивается на три строчки.
Частые ошибки и подводные камни
Когда начинаешь работать с текстом, легко наступить на пару классических граблей. Разберём их заранее, чтобы ты не терял время в панике.
1. message.text равен None — и всё падает
Если у тебя в хэндлере не стоит фильтр F.text, бот будет ловить вообще все сообщения, включая стикеры и фото. А у стикера message.text — это None, и попытка вызвать None.upper() приводит к ошибке AttributeError. Лечение простое: всегда ставь фильтр @dp.message(F.text), как мы делали во всех примерах. Тогда внутрь хэндлера попадут только сообщения с текстом.
2. Строковые методы ничего не меняют «на месте»
Очень частая ошибка новичка — написать так:
text = message.text
text.upper() # результат потерян!
await message.answer(text) # отправит исходный текст, не капсЗдесь text.upper() создаёт новую строку, но её никуда не сохранили, поэтому она тут же исчезает. Бот отправит исходный текст без изменений. Правильно — text = text.upper() или сразу передать результат в answer.
3. Путаница: answer и send_message
Метод message.answer(text) отвечает в тот чат, откуда пришло сообщение — это самый удобный способ. Иногда новички пытаются вызвать message.send_message(...) — такого метода нет, и будет ошибка. Метод send_message есть у объекта bot, и ему нужно явно передавать chat_id. Для ответа в тот же чат используй message.answer.
4. Забыл await
Если написать message.answer(text) без await, бот промолчит, а в консоли появится предупреждение вроде «coroutine was never awaited». Запомни: любой вызов, который шлёт что-то в Telegram, должен идти с await. Это как сказать официанту «отнеси заказ» и дождаться, что он его действительно отнёс.
5. Лишние пробелы и пустые строки
Если хочешь убрать пробелы по краям сообщения (например, пользователь случайно нажал пробел в начале), пригодится метод .strip(): message.text.strip() вернёт строку без пробелов по краям. Для подсчёта слов .split() и так справляется с пробелами, но при сравнении текста .strip() часто спасает от неожиданных багов.
Мини-практика: бот-зеркало с командой
Теперь твоя очередь. Доработай «Цыплёнка-помощника» так, чтобы он стал умным зеркалом. Вот задание:
- Сделай хэндлер на текстовые сообщения, который убирает пробелы по краям (
.strip()) и отправляет назад текст, где первая буква каждого слова заглавная. Подсказка: у строк есть готовый метод.title()— проверь его на чистом сниппете сprint(), прежде чем встраивать в бота. - Добавь подсчёт символов в сообщении (не слов, а букв и пробелов) — это просто
len(message.text). Пусть бот в конце ответа дописывает «Символов: N». - Подумай, что произойдёт, если прислать боту пустое сообщение из одних пробелов. Защитись от этого: если после
.strip()строка пустая, пусть бот ответит «Ты прислал пустое сообщение, цыплёнок не понял».
Если получится — у тебя в руках настоящий мини-инструмент обработки текста, который не стыдно показать друзьям. А если застрянешь — вернись к примерам 2 и 4, там есть все нужные кусочки.
Итоги и что дальше
Сегодня ты сделал большой шаг: твой бот перестал быть «попугаем» и научился понимать и переделывать текст. Давай закрепим главное:
- Текст сообщения лежит в
message.text— это обычная строка Python. - Фильтр
F.textстрахует отNone, пропуская только сообщения с текстом. - Со строкой работают все привычные приёмы:
.upper()для капса, срез[::-1]для переворота,.split()иlen()для подсчёта слов. - Строковые методы возвращают новую строку — результат надо сохранять.
- Отправляем ответ через
await message.answer(...), не забывая проawait.
В следующих уроках мы научим Цыплёнка не просто отвечать текстом, а показывать кнопки и меню, чтобы пользователю не приходилось всё печатать руками. А ещё дальше — вести с человеком настоящий диалог по шагам через FSM, как анкета, где поля заполняются по очереди. Но фундамент ты уже заложил: умение читать и преобразовывать текст пригодится в каждом из этих уроков. Молодец, что дошёл до конца — двигаемся дальше!