Бот в группах: права и события

Учим «Цыплёнка-помощника» жить не в личке, а в общем чате: понимать, что ему можно видеть, какие права ему нужны и как ловить, кто вошёл в группу, а кто из неё ушёл.

Группа для бота — это совсем другой мир, чем личный диалог: здесь много людей, бот по умолчанию полуслепой и почти бесправный, а вместо «привет» и «пока» прилетают особые события — ChatMemberUpdated, по которым бот узнаёт, что состав участников изменился.

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

Зачем боту вообще уметь работать в группах

Представь чат твоего класса на сотню человек. Каждый день туда вступают новенькие, кто-то выходит, кто-то кидает спам со ссылками на «бесплатные подписки». Сидеть и вручную всех приветствовать и чистить — скучно. А бот может: новичка встретить тёплым «Привет, читай закреп!», спамера — молча выкинуть, а на команду /правила прислать список правил чата.

Или чат игрового клана: бот ведёт список состава, и как только кто-то вышел из беседы — автоматически вычёркивает его из таблицы рейдов. Никто не забыт, никто не лишний.

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

[Маша вступила в группу «9Б чат»]

🐤 Привет, Маша! Добро пожаловать в чат. Загляни в закреп — там всё важное.

[Петя вышел из группы]

🐤 Петя покинул чат. Будет не хватать! 👋

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

Группа — это не личка: бот в наушниках с шумоподавлением

Чтобы понять главную странность ботов в группах, держи в голове такую картину. В личном диалоге бот — как собеседник один на один: ты говоришь — он слышит каждое слово. А в группе бот по умолчанию надевает наушники с сильным шумоподавлением: вокруг гудит сотня человек, и бот специально не вслушивается в общий гул. Он «снимает наушники» и слышит сообщение только в двух случаях: если оно начинается с команды (например /start или /правила) или если это ответ на его собственное сообщение либо упоминание через @имя_бота.

Это поведение называется режимом приватности (privacy mode). Telegram включает его всем ботам по умолчанию — и это забота о людях, а не вредность. Представь, если бы каждый бот в чате читал вообще все сообщения подряд: твою переписку с друзьями молча сканировали бы десятки чужих программ. Поэтому Telegram говорит: «По умолчанию бот глухой к обычной болтовне, видит только то, что адресовано лично ему».

Именно из-за режима приватности твой бот «оглох» в группе. Хэндлер на обычный текст, который отлично работал в личке, в группе просто не получает эти сообщения — они до него не доходят. И тут у тебя есть выбор из двух путей.

Путь первый: оставить приватность включённой

Если боту достаточно реагировать на команды (/start, /правила, /погода) и на прямые упоминания — ничего менять не надо. Это самый аккуратный и уважительный к людям вариант. Большинству ботов в группах именно это и нужно: их зовут командой, они отвечают. Болтовню людей бот при этом не видит — и хорошо.

Путь второй: выключить приватность

Если боту нужно видеть все сообщения — например, антиспам-бот должен проверять каждое сообщение на ссылки, — режим приватности придётся выключить. Делается это не в коде, а в настройках бота у BotFather: команда /setprivacy, выбираешь своего бота и ставишь Disable. Запомни две важные детали:

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

Права бота в группе: гость или администратор

Со «слухом» разобрались. Теперь про права. Когда бот заходит в группу, он сначала обычный участник — такой же, как любой человек. Он может писать сообщения и читать команды, но не может ничего «администраторского»: удалять чужие сообщения, банить, закреплять посты, приглашать по ссылке.

Это как разница между гостем на вечеринке и хозяином квартиры. Гость может болтать и есть печеньки, но не может выгнать кого-то за дверь или переставить мебель. Чтобы бот мог «переставлять мебель» — модерировать чат, — его нужно сделать администратором группы и выдать конкретные права. Делается это людьми, вручную, через настройки группы: «Администраторы → Добавить администратора → выбрать бота → отметить галочками, что ему можно».

Права выдаются по отдельности, и это удобно: можно дать боту ровно столько власти, сколько нужно, и ни капли больше. Вот основные права, которые тебе встретятся:

ПравоЧто позволяет боту
Удалять сообщенияСтирать чужие сообщения — основа любого антиспама
Блокировать участниковКикать и банить нарушителей
Закреплять сообщенияПрикалывать важный пост наверх чата
Приглашать по ссылкеСоздавать ссылки-приглашения в группу

Главное правило: сначала права, потом код. Можно написать идеальный хэндлер, который банит спамеров, но если боту не выдали право «Блокировать участников», Telegram просто откажет — и бот получит ошибку вместо результата. Если бот «ничего не делает» в группе, первым делом проверь, что он администратор и нужная галочка стоит.

События состава: кто вошёл и кто вышел

Переходим к самому интересному — к событиям. Когда в группе кто-то вступает, выходит, банится или получает права администратора, Telegram присылает боту особое обновление. В личке такого не бывает — это чисто «групповая» история. Называется это обновление ChatMemberUpdated — дословно «участник чата изменился».

Вспомни из самых первых уроков: Update (обновление) — это единица входящих данных от Telegram. Обычно это сообщение или нажатая кнопка. А ChatMemberUpdated — это просто ещё один вид обновления, только рассказывает он не про текст, а про то, как изменился статус какого-то участника: был «не в группе» — стал «участник», был «участник» — стал «вышел».

Удобнее всего думать об этом как о турникете на входе в клуб. Турникет не слышит, о чём болтают внутри, — он фиксирует только одно: человек прошёл внутрь или вышел наружу, и кто именно. ChatMemberUpdated — это и есть сигнал от такого турникета.

Что лежит внутри события

В каждом ChatMemberUpdated есть два ключевых поля — было и стало:

  • event.old_chat_member.status — каким был статус участника до события;
  • event.new_chat_member.status — каким он стал после.

Статус — это просто строка: "member" (участник), "left" (вышел), "kicked" (забанен), "administrator" (админ), "creator" (создатель чата). Сравнивая «было» и «стало», бот понимает, что именно произошло. Человек был не в группе, а стал "member" — значит, вступил. Был "member", а стал "left" — значит, вышел сам.

Давай на чистом Python, без всякого Telegram, прочувствуем эту логику «было → стало». Напишем маленькую функцию, которая по двум статусам решает, что случилось:

def describe_change(old_status, new_status):
    if old_status in ("left", "kicked") and new_status == "member":
        return "вступил в группу"
    if old_status == "member" and new_status == "left":
        return "вышел из группы"
    if old_status == "member" and new_status == "kicked":
        return "был забанен"
    return "что-то поменялось"

print("Маша:", describe_change("left", "member"))
print("Петя:", describe_change("member", "left"))
print("Спамер:", describe_change("member", "kicked"))

Вывод:

Маша: вступил в группу
Петя: вышел из группы
Спамер: был забанен

Видишь приём? Мы не гадаем по одному значению, а смотрим пару «откуда → куда». Это и есть сердце обработки ChatMemberUpdated: вся разница между «вступил» и «вышел» — в том, как изменилась пара статусов. Ровно эту же логику мы сейчас перенесём в бота.

Ловим событие в боте: my_chat_member и chat_member

В aiogram есть два разных хэндлера для событий состава, и их легко перепутать — поэтому разберём оба сразу.

  • @dp.my_chat_member() — срабатывает, когда меняется статус самого бота: бота добавили в группу, выгнали, сделали админом. «My» — «мой собственный» статус.
  • @dp.chat_member() — срабатывает, когда меняется статус любого другого участника: вошёл человек, вышел человек. Именно это нам нужно для приветствий.

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

from aiogram.types import ChatMemberUpdated
from aiogram.filters import ChatMemberUpdatedFilter, IS_NOT_MEMBER, IS_MEMBER

@dp.my_chat_member(ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER))
async def bot_added(event: ChatMemberUpdated):
    await event.answer(
        "\ud83d\udc24 Привет всем! Я Цыплёнок-помощник. "
        "Напишите /правила, чтобы узнать правила чата."
    )

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

Здесь появилась новая штука — фильтр перехода ChatMemberUpdatedFilter. Это удобный помощник aiogram, чтобы не сравнивать статусы руками. Запись IS_NOT_MEMBER >> IS_MEMBER читается буквально как стрелка: «было — не участник, стало — участник», то есть «зашёл». Стрелка >> здесь — это «перешёл из состояния в состояние». aiogram сам разберёт «было → стало» за нас, и хэндлер сработает только на нужный переход.

Теперь — главное блюдо. Ловим вступление и выход других участников и шлём персональное приветствие/прощание:

from aiogram.types import ChatMemberUpdated
from aiogram.filters import ChatMemberUpdatedFilter, JOIN_TRANSITION, LEAVE_TRANSITION

@dp.chat_member(ChatMemberUpdatedFilter(JOIN_TRANSITION))
async def on_user_join(event: ChatMemberUpdated):
    name = event.new_chat_member.user.full_name
    await event.answer(
        f"\ud83d\udc24 Привет, {name}! Добро пожаловать в чат. "
        "Загляни в закреп — там всё важное."
    )

@dp.chat_member(ChatMemberUpdatedFilter(LEAVE_TRANSITION))
async def on_user_leave(event: ChatMemberUpdated):
    name = event.new_chat_member.user.full_name
    await event.answer(f"\ud83d\udc24 {name} покинул чат. Будет не хватать! \ud83d\udc4b")

Результат: когда в группу вступает новый человек, бот пишет «Привет, Маша! Добро пожаловать...» с его именем; когда участник выходит — «Петя покинул чат...». Ровно тот сценарий, что мы рисовали в начале урока.

Разберём по шагам:

  • JOIN_TRANSITION и LEAVE_TRANSITION — готовые наборы переходов из aiogram. JOIN_TRANSITION ловит любой вариант «человек оказался в группе» (вступил сам, добавили, вернулся из бана), а LEAVE_TRANSITION — любой вариант ухода. Не надо помнить все строки статусов — библиотека уже собрала их за тебя.
  • event.new_chat_member.user — это объект пользователя, которого касается событие. У него есть привычные поля: full_name (имя и фамилия), id, username.
  • event.answer(...) — отправляет сообщение в тот же чат, где произошло событие. Удобно: не надо вручную вытаскивать id группы.

Обрати внимание: имя мы берём из new_chat_member — «нового» состояния — и для входа, и для выхода. Почему? Потому что new_chat_member — это всегда тот же самый человек, просто в его новом статусе (member при входе, left при выходе). Это один и тот же участник, а не разные люди.

Важная тонкость: chat_member надо включить отдельно

А вот и первая большая ловушка, на которой застревают почти все. Ты написал идеальный хэндлер @dp.chat_member, добавил бота в группу, кто-то вступил — а бот молчит. Код правильный, а тишина. В чём дело?

Дело в том, что Telegram по умолчанию не присылает боту обновления типа chat_member про других участников. Их надо явно запросить при запуске. Помнишь polling — способ, когда бот сам регулярно спрашивает Telegram «есть что новое?». При старте polling-а надо сказать: «и про смену состава тоже присылай». Делается это списком allowed_updates:

from aiogram.types import Update

async def main():
    await dp.start_polling(
        bot,
        allowed_updates=dp.resolve_used_update_types(),
    )

Результат: при запуске бот сам соберёт список всех типов обновлений, которые использует (включая chat_member, раз у нас есть такой хэндлер), и попросит Telegram присылать их все. После этого приветствия начнут работать.

Метод dp.resolve_used_update_types() — твой лучший друг: он сам пробегает по всем твоим хэндлерам и понимает, какие типы обновлений тебе нужны. Тебе не надо вручную перечислять строки — aiogram сделает это сам. Просто запомни правило: если используешь chat_member — передавай allowed_updates в start_polling, иначе событий о составе не будет.

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

1. «Бот не видит сообщения в группе» — это режим приватности

Самая частая жалоба новичка: «в личке всё работает, а в группе бот игнорит обычный текст». Это не баг — это режим приватности, включённый по умолчанию. Бот видит только команды и упоминания. Решений два: либо переводи общение в группе на команды (/правила вместо «правила»), либо выключай приватность через BotFather командой /setprivacy — и обязательно передобавь бота в группу после этого.

2. Забыл allowed_updates — и chat_member молчит

Хэндлер @dp.chat_member написан верно, но приветствий нет. Почти всегда причина одна: при запуске не передали allowed_updates, и Telegram просто не шлёт эти события. Самый надёжный способ — всегда стартовать polling с allowed_updates=dp.resolve_used_update_types(). Тогда aiogram сам не забудет включить нужные типы.

3. Путаешь my_chat_member и chat_member

Эти два хэндлера легко перепутать, и тогда бот «здоровается сам с собой». Запомни намертво: my_chat_member — про самого бота (его добавили/выгнали/сделали админом), chat_member — про остальных людей (вошёл/вышел участник). Для приветствия новичков нужен именно chat_member.

4. Бот не может банить или удалять — нет прав

Код для бана написан, а Telegram возвращает ошибку вроде «not enough rights». Причина не в коде: боту просто не выдали права администратора. Сначала сделай бота админом в настройках группы и поставь нужные галочки («Блокировать участников», «Удалять сообщения»), и только потом проверяй код. Помни правило: сначала права, потом код.

5. Берёшь имя из old_chat_member вместо new_chat_member

При обработке выхода новички иногда лезут в old_chat_member, думая «человек же был, возьму из старого состояния». Это лишнее: и old_chat_member, и new_chat_member описывают одного и того же участника, просто в разных статусах. Поле user в них одинаковое, поэтому имя удобнее всегда брать из new_chat_member.user.full_name — и для входа, и для выхода, чтобы не путаться.

Мини-практика: бот-вахтёр клана

Теперь твой ход. Прокачай «Цыплёнка-помощника» до бота-вахтёра для чата игрового клана:

  • Когда новый человек вступает (JOIN_TRANSITION) — поприветствуй его по имени и попроси написать /ник, чтобы добавиться в состав. Не забудь включить allowed_updates при запуске, иначе событие не дойдёт!
  • Когда участник выходит (LEAVE_TRANSITION) — напиши прощание и добавь подсказку администратору: «не забудь убрать его из таблицы рейдов».
  • Сделай команду /состав, которая работает даже при включённом режиме приватности (это же команда!) и присылает заглушку-список из трёх ников.

Когда заработает — попробуй усложнить. Добавь хэндлер @dp.my_chat_member с фильтром на переход в админы: пусть бот, когда его сделали администратором, радостно пишет «Спасибо за права! Теперь я могу следить за порядком». Подсказка: для этого пригодится фильтр перехода вроде ... >> IS_ADMIN. А ещё подумай, как с помощью SQLite (мы проходили базу в модуле про хранение) реально вести список состава, а не заглушку.

Итоги

Сегодня «Цыплёнок-помощник» переехал из уютной лички в шумную группу и научился там жить. Главное, что стоит унести с собой:

  • Группа — не личка. По умолчанию работает режим приватности: бот видит только команды и упоминания, а не всю болтовню. Выключается у BotFather через /setprivacy — но только если это правда нужно.
  • Сначала права, потом код. Модерировать (банить, удалять, закреплять) бот может, только если его сделали администратором и выдали конкретные галочки-права.
  • События состава — это ChatMemberUpdated. У него есть «было» (old_chat_member) и «стало» (new_chat_member); по паре статусов бот понимает, вошёл человек или вышел.
  • Два хэндлера: my_chat_member — про самого бота, chat_member — про остальных участников. Для приветствий нужен второй.
  • Не забудь allowed_updates при запуске polling-а, иначе chat_member просто не придёт. Спасает dp.resolve_used_update_types().

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

Проверьте себя
1. Почему бот, который отлично отвечал на текст в личке, перестаёт реагировать на обычные сообщения в группе?
AВ группах aiogram не поддерживает текстовые хэндлеры
BИз-за режима приватности бот по умолчанию видит только команды и упоминания, а не всю болтовню
CБот видит сообщения, но не успевает их обработать из-за нагрузки
DВ группе нужно использовать webhook вместо polling
2. Как выключить режим приватности, чтобы бот видел все сообщения в группе?
AПередать privacy=False в объект Bot при создании
BЧерез BotFather командой /setprivacy выбрать Disable, а затем заново добавить бота в группу
CВызвать bot.disable_privacy() при запуске
DСделать бота администратором группы
3. Чем хэндлер @dp.my_chat_member отличается от @dp.chat_member?
Amy_chat_member работает только в личке, а chat_member — только в группах
Bmy_chat_member срабатывает на изменение статуса самого бота, а chat_member — на изменение статуса других участников
CРазницы нет, это синонимы
Dmy_chat_member ловит сообщения, а chat_member — нажатия кнопок
4. Хэндлер @dp.chat_member написан верно, но бот не реагирует на вступление участников. Что забыли сделать?
AСделать бота администратором группы
BПередать allowed_updates при запуске polling — иначе Telegram не присылает события chat_member
CВыключить режим приватности через BotFather
DДобавить декоратор @dp.message рядом с хэндлером
5. Как по объекту ChatMemberUpdated понять, что именно произошло — вход или выход?
AПо длине поля event.text
BСравнить статусы old_chat_member.status и new_chat_member.status: например, было left, стало member — значит, вступил
CПо времени события: вход всегда раньше выхода
DChatMemberUpdated сам по себе означает только вход
6. Бот должен банить спамеров, код написан правильно, но Telegram возвращает ошибку о нехватке прав. В чём причина?
AНужно выключить режим приватности
BБоту не выданы права администратора с разрешением блокировать участников — права настраиваются вручную в группе
CБан работает только через webhook
DНужно перезапустить polling с другим токеном