Модерация: мут, бан, фильтры

Учим Цыплёнка-помощника наводить порядок в чате клана: затыкать болтунов на время, выгонять нарушителей, ловить запрещённые слова и стирать сообщения, которые ломают правила.
Модерация — это поддержание порядка в чате силами бота: он по правилам ограничивает, удаляет или выгоняет тех, кто мешает остальным, делая это мгновенно и без эмоций.

Зачем боту уметь наводить порядок

Представь чат твоего игрового клана на сорок человек. Идёт обсуждение рейда, и тут приходит чужак и начинает кидать рекламу казино каждые десять секунд. Ты — админ, но ты на тренировке, телефон в рюкзаке. Через пятнадцать минут в чате уже сотня спам-сообщений, и половина клана его покинула. Обидно.

А теперь представь, что в чате сидит Цыплёнок-помощник с правами админа. Он замечает запрещённое слово в первом же сообщении, моментально его удаляет, а болтуна затыкает на час. Чужак даже не успевает развернуться. Ты возвращаешься с тренировки — а в чате тишина и порядок, будто ничего и не было.

Вот к чему мы придём в этом уроке. Цыплёнок научится трём вещам: временно лишать человека права писать (это называется мут), полностью выгонять нарушителя из чата (бан) и автоматически ловить запрещённые слова, стирая такие сообщения. По сути, мы превращаем бота в дежурного по чату, который не спит, не отвлекается и не поддаётся на провокации.

Чужак:  Залетай на лучшее казино, дарю промокод! 🎰
Бот:    (молча удаляет сообщение)
Бот:    @chuzhak, за рекламу — мут на 1 час. Правила в закрепе 🐥

Результат: в чате бот сам убирает спам-сообщение и пишет короткое предупреждение нарушителю, заодно лишая его права писать на час. Никто из клана не успевает даже прочитать рекламу — модерация срабатывает быстрее человека.

Как это работает: бот как дежурный по классу

Помнишь дежурного по классу? Учитель выдаёт ему повязку и говорит: «Следи за порядком, можешь делать замечания и выгонять из кабинета хулиганов». Без повязки дежурный — обычный ученик, его никто не слушает. С повязкой у него появляются права.

С ботом всё точно так же. Сам по себе бот в группе — рядовой участник, он не может никого ни замутить, ни забанить. Чтобы получить «повязку», его нужно сделать администратором группы и в настройках админ-прав включить галочки «Блокировка участников» и «Удаление сообщений». Без этих прав любая твоя команда модерации просто вернёт ошибку «недостаточно прав». Это первое и главное правило урока.

Бот без админ-прав в группе ничего модерировать не может. Сначала повязка (права админа), потом обязанности (мут, бан, удаление).

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

Три разных инструмента — не путай

У дежурного есть разные меры строгости, и важно с самого начала понимать, чем они отличаются.

ДействиеЧто делаетМетод aiogram
Удалить сообщениеСтирает одно сообщение, человек остаётся в чатеbot.delete_message
МутЗапрещает писать, но человек остаётся в чате (можно на время)bot.restrict_chat_member
БанПолностью выкидывает из чатаbot.ban_chat_member

Грубо говоря: удаление — это стереть с доски одну надпись, мут — заклеить человеку рот скотчем на время, а бан — вывести за дверь совсем. Разные ситуации требуют разной строгости, и хороший модератор не банит за первую же опечатку. Чаще всего эти инструменты идут в связке: сначала удаляешь нарушающее сообщение, чтобы его не видели остальные, а потом, в зависимости от тяжести, либо мутишь автора на время, либо банишь насовсем. Именно так мы и будем их сочетать в нашем фильтре чуть дальше.

Шаг 1. Мут: затыкаем болтуна через restrict_chat_member

Начнём с самой мягкой и самой частой меры — мута. Мут в Telegram — это не отдельная кнопка «замолчи», а ограничение прав участника. Мы говорим Telegram: «вот этому человеку в этом чате запрети отправлять сообщения». Делается это методом restrict_chat_member, которому передают объект ChatPermissions со списком того, что человеку можно. Если в разрешениях всё выставить в False — он не сможет писать вообще ничего. Это и есть мут.

Чтобы мут был временным (например, на час), мы добавляем параметр until_date — момент времени, когда ограничение само снимется. Удобно посчитать его как «сейчас плюс столько-то секунд».

from datetime import datetime, timedelta
from aiogram.types import Message, ChatPermissions
from aiogram.filters import Command

@dp.message(Command("mute"))
async def cmd_mute(message: Message):
    # команду пишут в ответ на сообщение нарушителя
    if not message.reply_to_message:
        await message.answer("Ответь этой командой на сообщение того, кого мутить.")
        return

    target = message.reply_to_message.from_user
    until = datetime.now() + timedelta(hours=1)

    await bot.restrict_chat_member(
        chat_id=message.chat.id,
        user_id=target.id,
        permissions=ChatPermissions(can_send_messages=False),
        until_date=until,
    )
    await message.answer(f"{target.full_name} в муте на 1 час 🤐")

Результат: в чате, если ответить на сообщение болтуна командой /mute, бот лишит его права писать на час и сообщит об этом. Через час Telegram сам вернёт человеку возможность писать — отдельно размучивать не нужно.

Разберём по шагам. Сначала проверяем message.reply_to_message — это сообщение, на которое ответили нашей командой. Если его нет, значит админ просто написал /mute в воздух, и непонятно, кого мутить, — вежливо просим ответить на конкретное сообщение. Если ответ есть, достаём из него автора через .from_user — это и есть нарушитель. Считаем until как «сейчас + 1 час». И зовём restrict_chat_member: указываем чат, id нарушителя, разрешения с can_send_messages=False (писать нельзя) и время, до которого мут держится.

Почему именно «в ответ на сообщение»? Потому что так не надо вручную выяснять чей-то id — Telegram сам подскажет автора того сообщения, на которое ты ответил. Это самый удобный приём для команд модерации, и мы будем использовать его и дальше.

Шаг 2. Бан: выгоняем нарушителя через ban_chat_member

Если человек пришёл откровенно вредить и мут не помогает — пора банить. Бан полностью удаляет участника из чата, и просто так он назад уже не вернётся. Метод называется ban_chat_member и в простейшем виде требует только чат и id того, кого выгоняем.

@dp.message(Command("ban"))
async def cmd_ban(message: Message):
    if not message.reply_to_message:
        await message.answer("Ответь этой командой на сообщение того, кого банить.")
        return

    target = message.reply_to_message.from_user

    await bot.ban_chat_member(
        chat_id=message.chat.id,
        user_id=target.id,
    )
    await message.answer(f"{target.full_name} забанен и удалён из чата 🚪")

Результат: в чате, если ответить на сообщение нарушителя командой /ban, бот выкинет его из группы и напишет об этом. Сам нарушитель обратно по старой ссылке уже не зайдёт, пока его не разбанят.

Код почти повторяет мут — та же проверка на ответ, тот же способ достать нарушителя через reply_to_message.from_user. Разница только в действии: вместо ограничения прав мы зовём ban_chat_member, который выгоняет человека целиком. Заметь, насколько похожи команды модерации между собой — освоив одну, ты почти бесплатно получаешь остальные.

Важный нюанс: бан — это не то же самое, что «удалить и забыть». Забаненный остаётся в чёрном списке чата, и если захочешь дать ему второй шанс, его придётся явно разбанить методом unban_chat_member. Поэтому баном не разбрасываются: это самая суровая мера, после неё человек чувствует себя выставленным за дверь насовсем. Хорошее правило для бота клана: бан приберегай для откровенных спамеров и тех, кто пришёл явно вредить, а с обычными участниками, которые просто перегнули палку в споре, хватает мута на пару часов — пусть остынут и вернутся.

Шаг 3. Фильтр запрещённых слов

Команды /mute и /ban — это ручной режим: ты сам реагируешь на нарушителя. Но дежурный хорош тем, что следит постоянно. Сделаем автоматический фильтр: бот будет проверять каждое входящее сообщение и, если в нём есть запрещённое слово, сразу удалять его и мутить автора.

Сначала разберём саму проверку «есть ли в тексте плохое слово» на чистом Python, без бота. Это маленькая самодостаточная задача: приводим текст к нижнему регистру и смотрим, встречается ли в нём хоть одно слово из чёрного списка.

BAD_WORDS = {"казино", "промокод", "ставки"}

def has_bad_word(text):
    lowered = text.lower()
    for word in BAD_WORDS:
        if word in lowered:
            return True
    return False

print(has_bad_word("Залетай на лучшее КАЗИНО!"))
print(has_bad_word("Привет, как дела?"))
print(has_bad_word("дарю промокод друзьям"))

Вывод:

True
False
True

Смотри, что здесь происходит. BAD_WORDS — это множество (set) запрещённых слов; множество удобно тем, что проверка «есть ли слово внутри» быстрая. Метод text.lower() переводит всё в нижний регистр, чтобы «КАЗИНО», «Казино» и «казино» считались одним и тем же — иначе хитрый спамер обошёл бы фильтр одной заглавной буквой. Дальше мы перебираем чёрный список и для каждого слова проверяем, входит ли оно в текст через in. Нашли — сразу возвращаем True; прошли весь список без находок — возвращаем False.

Теперь подключим эту проверку к боту. Сделаем хэндлер, который ловит любое текстовое сообщение в группе, прогоняет его через has_bad_word и при срабатывании удаляет сообщение и мутит автора.

from aiogram import F
from aiogram.types import Message, ChatPermissions
from datetime import datetime, timedelta

BAD_WORDS = {"казино", "промокод", "ставки"}

def has_bad_word(text: str) -> bool:
    lowered = text.lower()
    return any(word in lowered for word in BAD_WORDS)

@dp.message(F.text)
async def filter_messages(message: Message):
    if not has_bad_word(message.text):
        return  # обычное сообщение — не трогаем

    await message.delete()

    until = datetime.now() + timedelta(hours=1)
    await bot.restrict_chat_member(
        chat_id=message.chat.id,
        user_id=message.from_user.id,
        permissions=ChatPermissions(can_send_messages=False),
        until_date=until,
    )
    await message.answer(
        f"{message.from_user.full_name}, за рекламу — мут на 1 час. Правила в закрепе 🐥"
    )

Результат: в чате, как только кто-то напишет сообщение со словом из чёрного списка, бот мгновенно удалит это сообщение, замутит автора на час и оставит короткое предупреждение. Обычные сообщения без запрещённых слов проходят без помех — бот их просто пропускает.

Главная новинка здесь — @dp.message(F.text). Раньше мы вешали хэндлеры на конкретные команды, а этот ловит вообще любое текстовое сообщение. Внутри мы первым делом зовём has_bad_word: если слово не найдено, делаем return и тихо отпускаем сообщение — это важно, иначе бот реагировал бы на каждую фразу в чате. Если слово найдено — message.delete() стирает сообщение, а дальше идёт уже знакомый по шагу 1 мут автора. Функция has_bad_word тут чуть короче за счёт any(...), но делает ровно то же, что и в разминке.

Частые ошибки и подводные камни

Вот грабли, на которые наступают почти все, кто впервые делает модерацию. Прочитай сейчас — сэкономишь себе нервы и пару часов отладки.

  • Забыть выдать боту права админа. Самая частая. Если бот не админ группы или у него не включены «Блокировка участников» и «Удаление сообщений», то restrict_chat_member, ban_chat_member и delete_message вернут ошибку вроде TelegramBadRequest: not enough rights. Сначала повязка, потом обязанности.
  • Путать мут и бан. Мут (restrict_chat_member) оставляет человека в чате, просто молчащим, — это мягкая мера. Бан (ban_chat_member) выкидывает совсем. Не банят за первую опечатку, и не удивляются, что замученный всё ещё в списке участников — так и задумано.
  • Команда без reply ничего не понимает. Если написать просто /ban, не отвечая ни на чьё сообщение, у бота нет message.reply_to_message — он не знает, кого банить. Всегда проверяй if not message.reply_to_message в начале и подсказывай, что командой надо отвечать на сообщение нарушителя.
  • Сравнивать текст без приведения регистра. Если в фильтре забыть text.lower(), то слово «казино» поймается, а «КАЗИНО» или «Казино» проскользнут. Спамеры этим пользуются. Всегда приводи и текст, и слова чёрного списка к одному регистру.
  • Пытаться забанить другого админа. Telegram не даст боту замутить или забанить участника, у которого прав больше или столько же. Попытка вернёт ошибку. Это защита от того, чтобы бот случайно (или по чьей-то злой команде) не выгнал создателя чата.

Мини-практика: добавь предупреждения перед мутом

Сейчас фильтр сразу мутит за первое же запрещённое слово. Это строго. В реальных чатах часто дают пару предупреждений и только потом наказывают. Доработай Цыплёнка, чтобы он вёл счётчик предупреждений.

  1. Заведи словарь warnings = {}, где ключ — id пользователя, а значение — сколько раз он уже нарушил. Подсказка: warnings[user_id] = warnings.get(user_id, 0) + 1 аккуратно увеличивает счётчик, даже если пользователя там ещё не было.
  2. В хэндлере фильтра при срабатывании сначала удаляй сообщение и увеличивай счётчик. Если предупреждений меньше трёх — просто пиши «Предупреждение N из 3, за рекламу можно получить мут».
  3. Когда счётчик дойдёт до трёх — вот тогда мути на час и сбрасывай счётчик обратно в ноль.
  4. Проверь со второго аккаунта-друга: первые два запрещённых сообщения дают предупреждение, третье — мут.

Бонус для смелых: вынеси список BAD_WORDS в базу SQLite (помнишь таблицы из модуля про данные?) и сделай команду /addbad слово для админа, которая добавляет новое запрещённое слово прямо из чата, без перезапуска бота. Так модерацию можно настраивать на лету.

Итоги и что дальше

Сегодня Цыплёнок-помощник стал настоящим дежурным по чату. Мы разобрали три инструмента модерации и чётко развели их по строгости: delete_message стирает одно сообщение, restrict_chat_member с пустыми ChatPermissions и until_date временно лишает права писать (мут), а ban_chat_member выгоняет нарушителя из чата совсем (бан). Команды /mute и /ban мы научились применять в ответ на сообщение, чтобы не возиться с id вручную. А ещё собрали автоматический фильтр на @dp.message(F.text), который проверяет каждое сообщение функцией has_bad_word и сам удаляет спам и мутит автора. И запомнили главное правило: без админ-прав в группе бот не модерирует ничего.

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

Проверьте себя
1. Что нужно сделать, чтобы бот вообще мог мутить и банить участников группы?
AНичего, любой бот в группе может модерировать по умолчанию
BСделать бота администратором группы и включить права «Блокировка участников» и «Удаление сообщений»
CНаписать боту команду /admin в личке
DДобавить бота в группу через BotFather
2. Чем мут (restrict_chat_member) отличается от бана (ban_chat_member)?
AЭто одно и то же, просто разные названия
BМут удаляет человека из чата, а бан только запрещает писать
CМут лишает права писать, но человек остаётся в чате; бан полностью выгоняет из чата
DМут работает только в личке, а бан только в группах
3. Как удобнее всего указать боту, кого замутить или забанить?
AНаписать команду в ответ на сообщение нарушителя — бот возьмёт автора из message.reply_to_message
BВписать имя пользователя прямо в команду текстом
CБот сам угадывает нарушителя по контексту
DПередать номер телефона нарушителя
4. Почему в фильтре запрещённых слов текст приводят к нижнему регистру через text.lower()?
AТак сообщение занимает меньше места
BЧтобы «КАЗИНО», «Казино» и «казино» считались одним словом и спамер не обошёл фильтр заглавными буквами
CЭтого требует aiogram для всех текстовых хэндлеров
DЧтобы бот отвечал быстрее
5. Что делает until_date в вызове restrict_chat_member?
AЗадаёт дату, когда бот напомнит о нарушителе
BУказывает, до какого момента действует ограничение — после него мут снимется сам
CЗапрещает писать навсегда
DЭто дата, когда был создан чат
6. Зачем в хэндлере @dp.message(F.text) сначала проверяют has_bad_word и делают return, если слова нет?
AЧтобы бот удалял вообще все сообщения в чате
BЧтобы пропускать обычные сообщения без помех и трогать только те, где есть запрещённое слово
CЭто обязательная строка для любого хэндлера
DЧтобы сохранять каждое сообщение в базу