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:
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user