Управление сообщениями в боте телеграм

В aiogram 3 бот отправляет сообщение тремя способами: message.answer() пишет в тот же чат, message.reply() пишет в тот же чат ответом с цитатой, bot.send_message(chat_id, text) пишет в любой чат по его номеру. Редактируют сообщение через message.edit_text() или bot.edit_message_text(), удаляют через message.delete(). Ниже каждый метод с примером и три готовых бота, которые можно запустить.

Примеры проверены на aiogram 3.x (версия 3.31.0) и Python 3.14.

Если у вас старый код с executor.start_polling и @dp.message_handler, это aiogram 2. В третьей версии он не запустится: обработчики живут в роутере, а запуск идёт через asyncio.run(). Все примеры ниже написаны под третью версию.

Объект Message: что приходит боту

Каждое входящее сообщение aiogram превращает в объект Message из модуля aiogram.types. Через него бот узнаёт, кто написал, куда и что. У этого же объекта есть методы, чтобы ответить, отредактировать или удалить сообщение.

Поля, которые нужны чаще всего:

  • message_id — номер сообщения внутри чата. Нужен, чтобы потом отредактировать или удалить именно его.
  • from_user — отправитель, объект User (имя, ник, id).
  • chat — чат, где появилось сообщение, объект Chat. Его chat.id передают в send_message.
  • date — дата и время отправки.
  • text — текст. Для фото, стикера или документа здесь None, подпись к фото лежит в caption.
  • entities — разметка внутри текста: упоминания, ссылки, хэштеги, команды.
  • reply_to_message — сообщение, на которое ответил пользователь.
  • forward_origin — откуда переслано сообщение. В старых версиях API для этого были поля forward_from и forward_from_chat.
  • reply_markup — клавиатура, прикреплённая к сообщению.

Методы: answer(), reply(), edit_text(), delete(), а также answer_photo(), answer_document() и другие для медиа. Подробнее про Message, User и Chat — в статье про модуль aiogram.types.

Как отправить сообщение

bot.send_message: в любой чат по номеру

Метод принимает номер чата (chat_id) и текст. Подходит, когда бот пишет сам, без входящего сообщения: напоминание, рассылка, уведомление администратору.

await bot.send_message(chat_id, "Привет, мир!")

Писать первым бот может только тому, кто уже нажал «Старт» в диалоге с ним. Иначе Telegram вернёт ошибку.

message.answer: в тот же чат

Короткая запись для частого случая. Номер чата aiogram берёт из входящего сообщения сам.

await message.answer("Спасибо за ваше сообщение!")

message.reply: ответ с цитатой

Тоже пишет в тот же чат, но новое сообщение привязано к исходному. Над ответом видна цитата, по нажатию на неё Telegram прокручивает к оригиналу. В группах это помогает понять, кому бот отвечает.

await message.reply("Спасибо за ваше сообщение!")

Ответ на конкретное сообщение через send_message

Если нужно ответить на сообщение с известным номером, передайте его в reply_parameters. Старый параметр reply_to_message_id в aiogram 3 пока принимается, но в документации Bot API его заменил reply_parameters.

from aiogram.types import ReplyParameters

await bot.send_message(
    chat_id,
    "Ответ на ваше сообщение",
    reply_parameters=ReplyParameters(message_id=message_id),
)

Разница между send_message, answer и reply

send_message отправляет в чат, который вы укажете. answer отправляет в тот чат, откуда пришло сообщение, без цитаты. reply отправляет туда же, но с цитатой исходного сообщения. На скриншоте сверху ответ через answer, снизу через reply:

Результат работы функций answer и reply

Разметка текста: жирный, курсив, ссылки

Чтобы теги <b> и <i> в тексте превращались в оформление, боту задают режим разметки (parse mode). В aiogram 3 это делают один раз при создании бота через DefaultBotProperties:

from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode

bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))

Раньше режим передавали прямо в Bot(parse_mode=...). В aiogram 3.7 и новее так писать нельзя: бот не создастся.

Готовый бот: /start, /help и эхо

Бот здоровается на /start, показывает справку на /help и повторяет любой другой текст. Токен (ключ бота от BotFather) лежит в переменной окружения BOT_TOKEN, а не в коде: так он не попадёт в репозиторий или на скриншот.

import asyncio
import os

from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import Command, CommandStart
from aiogram.types import Message

router = Router()


@router.message(CommandStart())
async def handle_start(message: Message):
    await message.reply("Привет! Я бот. Как дела?")


@router.message(Command("help"))
async def handle_help(message: Message):
    await message.answer(
        "Что я умею:\n"
        "/start — поздороваться\n"
        "/help — показать эту справку\n"
        "Любой другой текст я повторю."
    )


@router.message(F.text)
async def handle_text(message: Message):
    await message.reply(f"Вы отправили сообщение: {message.text}")


async def main():
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp = Dispatcher()
    dp.include_router(router)
    await bot.delete_webhook(drop_pending_updates=True)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Что здесь от aiogram 3. Router (роутер) — набор обработчиков, его подключают к диспетчеру через dp.include_router(). Декоратор @router.message(...) говорит «эта функция обрабатывает сообщения». В скобках фильтр: CommandStart() ловит /start, Command("help") ловит /help, F.text ловит любое сообщение с текстом. Порядок важен: aiogram проверяет обработчики сверху вниз, поэтому эхо стоит последним. delete_webhook(drop_pending_updates=True) выбрасывает сообщения, которые пришли, пока бот был выключен.

Запуск в Windows: set BOT_TOKEN=ваш_токен, затем python bot.py. В Linux и macOS: export BOT_TOKEN=ваш_токен.

Как отредактировать сообщение

Бот может менять только свои сообщения. Чужие, в том числе сообщения пользователя, он не отредактирует. Обычно редактируют то, что бот отправил чуть раньше: answer() возвращает объект отправленного сообщения, у него и вызывают edit_text().

sent = await message.answer("Считаю...")
await sent.edit_text("Готово: 42")

Если объекта под рукой нет, но известны номер чата и номер сообщения, подойдёт метод бота:

await bot.edit_message_text("Новый текст", chat_id=chat_id, message_id=message_id)

edit_text меняет только текст. У фото и документа меняют подпись через edit_caption(), а кнопки под сообщением — через edit_reply_markup().

Ошибка «message is not modified»

Если отправить на редактирование тот же текст и те же кнопки, Telegram ответит ошибкой TelegramBadRequest: message is not modified. Так бывает, когда пользователь дважды нажал одну кнопку. Ошибку ловят и пропускают:

from aiogram.exceptions import TelegramBadRequest

try:
    await sent.edit_text("Готово: 42")
except TelegramBadRequest as e:
    if "message is not modified" not in str(e):
        raise

Готовый бот: счётчик на кнопках

Самый частый случай редактирования — нажатие встроенной кнопки (inline-кнопки, она висит под сообщением). Telegram присылает боту не сообщение, а событие CallbackQuery (запрос от кнопки). Сообщение с кнопкой лежит в callback.message, его и редактируют.

import asyncio
import os

from aiogram import Bot, Dispatcher, F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import Command
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder

router = Router()


def counter_keyboard() -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()
    builder.button(text="-1", callback_data="minus")
    builder.button(text="+1", callback_data="plus")
    builder.button(text="Сброс", callback_data="reset")
    builder.adjust(2, 1)
    return builder.as_markup()


@router.message(Command("counter"))
async def show_counter(message: Message):
    await message.answer("Счёт: 0", reply_markup=counter_keyboard())


@router.callback_query(F.data.in_({"minus", "plus", "reset"}))
async def change_counter(callback: CallbackQuery):
    value = int(callback.message.text.split(": ")[1])
    if callback.data == "plus":
        value += 1
    elif callback.data == "minus":
        value -= 1
    else:
        value = 0

    try:
        await callback.message.edit_text(f"Счёт: {value}", reply_markup=counter_keyboard())
    except TelegramBadRequest as e:
        if "message is not modified" not in str(e):
            raise
    await callback.answer()


async def main():
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

InlineKeyboardBuilder собирает клавиатуру: button() добавляет кнопку, adjust(2, 1) раскладывает их в ряды по две и по одной. callback_data — строка, которую Telegram вернёт боту при нажатии. Фильтр F.data.in_({...}) пропускает только наши три значения. Сброс при нуле как раз даёт «message is not modified», поэтому ошибка поймана. В конце обязателен callback.answer(): без него у пользователя на кнопке будут крутиться часики.

Как удалить сообщение

await message.delete()
# или, если известны номера:
await bot.delete_message(chat_id, message_id)

Ограничения Telegram такие. Свои сообщения бот удаляет в любом чате. Чужие — только в группе, где он администратор с правом удалять сообщения. В личном чате бот может удалить и сообщение пользователя. Сообщения старше 48 часов бот удалить не может (кроме своих исходящих в личке и в каналах).

Как отправить фото, файл, аудио и видео

У каждого типа свой метод: send_photo, send_audio, send_document, send_video, send_voice. Файл с диска передают через FSInputFile (FS — файловая система), ссылку из интернета — просто строкой.

from aiogram.types import FSInputFile

await bot.send_photo(chat_id, FSInputFile("photo.jpg"), caption="Красивая фотография")
await bot.send_audio(chat_id, FSInputFile("audio.mp3"), caption="Музыкальный трек")
await bot.send_document(chat_id, FSInputFile("document.pdf"), caption="PDF-документ")
await bot.send_video(chat_id, FSInputFile("video.mp4"), caption="Видеоролик")
await bot.send_voice(chat_id, FSInputFile("voice.ogg"), caption="Голосовое сообщение")

await bot.send_photo(chat_id, "https://example.com/cat.jpg")

Старый приём open('photo.jpg', 'rb') в aiogram 3 не работает. Голосовое сообщение Telegram покажет как голосовое, только если файл в формате OGG с кодеком OPUS. Иначе он придёт как обычный файл.

Внутри обработчика удобнее короткие методы сообщения: message.answer_photo(), answer_document(), answer_video() и так далее. Номер чата они подставят сами.

Готовый бот: семь команд и разный контент

import asyncio
import os

from aiogram import Bot, Dispatcher, Router
from aiogram.filters import Command, CommandStart
from aiogram.types import FSInputFile, Message

router = Router()


@router.message(CommandStart())
async def handle_start(message: Message):
    await message.answer("Команды: /text /photo /video /audio /document /voice")


@router.message(Command("text"))
async def handle_text(message: Message):
    await message.answer("Пример текстового сообщения")


@router.message(Command("photo"))
async def handle_photo(message: Message):
    await message.answer_photo(FSInputFile("photo.jpg"), caption="Фото с диска")


@router.message(Command("video"))
async def handle_video(message: Message):
    await message.answer_video(FSInputFile("video.mp4"))


@router.message(Command("audio"))
async def handle_audio(message: Message):
    await message.answer_audio(FSInputFile("audio.mp3"))


@router.message(Command("document"))
async def handle_document(message: Message):
    await message.answer_document(FSInputFile("document.pdf"))


@router.message(Command("voice"))
async def handle_voice(message: Message):
    await message.answer_voice(FSInputFile("voice.ogg"))


async def main():
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Положите рядом со скриптом файлы photo.jpg, video.mp4, audio.mp3, document.pdf и voice.ogg. Если файла нет, обработчик упадёт с ошибкой FileNotFoundError, а бот продолжит работать.

Бот — фильтр ненормативной лексики

Бот читает все сообщения в группе, ищет слова из списка и, если нашёл, предупреждает автора и удаляет сообщение.

import asyncio
import os

from aiogram import Bot, Dispatcher, F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.types import Message

router = Router()

FORBIDDEN_WORDS = ["ругаться", "оскорблять", "ненормативная_лексика"]


@router.message(F.text)
async def check_words(message: Message):
    text = message.text.lower()
    if not any(word in text for word in FORBIDDEN_WORDS):
        return

    await message.reply("В чате нельзя ругаться!")
    try:
        await message.delete()
    except TelegramBadRequest:
        # у бота нет прав администратора или сообщение старше 48 часов
        pass


async def main():
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Чтобы бот видел все сообщения группы, а не только команды, отключите у него режим приватности: в BotFather команда /setprivacy, значение Disable. Потом удалите бота из группы и добавьте снова. Чтобы удаление сработало, сделайте бота администратором с правом удалять сообщения. Список слов замените своим: сейчас в нём слова-заглушки.

Уроки по теме

Спокойный вход с мини-проектами — уроки курса (в нём более простая библиотека telebot):

Понравилась статья? Поделиться с друзьями:
Школа Виктора Комлева
Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.