feat: add ChatTypeFilterMiddleware to ignore group/forum messages

Drop all messages and callback queries from non-private chats
(groups, supergroups with forum topics, channels) before they
reach any handler or heavy middleware (DB, throttle, blacklist).

- Registered after ContextVarsMiddleware, before GlobalErrorMiddleware
- chat_member events intentionally excluded (needed for channel tracking)
- pre_checkout_query excluded (no chat context, always private)
- Uses ChatType.PRIVATE enum for type safety
- Debug logging on dropped events for observability
This commit is contained in:
Fringg
2026-02-24 06:19:03 +03:00
parent 6f473defef
commit 25f014fd89
2 changed files with 51 additions and 0 deletions
+4
View File
@@ -64,6 +64,7 @@ from app.handlers.stars_payments import register_stars_handlers
from app.middlewares.auth import AuthMiddleware
from app.middlewares.blacklist import BlacklistMiddleware
from app.middlewares.button_stats import ButtonStatsMiddleware
from app.middlewares.chat_type_filter import ChatTypeFilterMiddleware
from app.middlewares.context_binding import ContextVarsMiddleware
from app.middlewares.global_error import GlobalErrorMiddleware
from app.middlewares.logging import LoggingMiddleware
@@ -117,6 +118,9 @@ async def setup_bot() -> tuple[Bot, Dispatcher]:
dp.message.middleware(ContextVarsMiddleware())
dp.callback_query.middleware(ContextVarsMiddleware())
dp.pre_checkout_query.middleware(ContextVarsMiddleware())
chat_type_filter = ChatTypeFilterMiddleware()
dp.message.middleware(chat_type_filter)
dp.callback_query.middleware(chat_type_filter)
dp.message.middleware(GlobalErrorMiddleware())
dp.callback_query.middleware(GlobalErrorMiddleware())
dp.pre_checkout_query.middleware(GlobalErrorMiddleware())
+47
View File
@@ -0,0 +1,47 @@
"""Middleware to ignore non-private chat messages.
When the bot is added as admin to a group or supergroup (including forums
with topics), it should silently drop all incoming messages and callback
queries from those chats. Only private (DM) interactions are processed.
Not registered on chat_member — channel_member.py needs ChatMemberUpdated
events from groups/channels to track required channel subscriptions.
Not registered on pre_checkout_query — no chat context, always private.
"""
from collections.abc import Awaitable, Callable
from typing import Any
import structlog
from aiogram import BaseMiddleware
from aiogram.enums import ChatType
from aiogram.types import CallbackQuery, Message, TelegramObject
logger = structlog.get_logger(__name__)
class ChatTypeFilterMiddleware(BaseMiddleware):
"""Drop messages and callback queries from non-private chats."""
async def __call__(
self,
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: dict[str, Any],
) -> Any:
chat = None
if isinstance(event, Message):
chat = event.chat
elif isinstance(event, CallbackQuery) and event.message:
chat = event.message.chat
if chat is not None and chat.type != ChatType.PRIVATE:
logger.debug(
'Dropping non-private chat event',
chat_id=chat.id,
chat_type=chat.type,
)
return None
return await handler(event, data)