0
# Filters and Handlers
1
2
Powerful filtering system with magic filters, command matching, state filtering, and custom filters. Handler registration with decorators and functional approaches for organizing bot logic.
3
4
## Capabilities
5
6
### Base Filter Classes
7
8
Foundation classes for creating filters and handling updates.
9
10
```python { .api }
11
class Filter:
12
"""Base class for all filters"""
13
14
async def __call__(self, obj: TelegramObject) -> bool | dict[str, Any]:
15
"""
16
Check if the filter matches the object.
17
18
Parameters:
19
- obj: Telegram object to filter
20
21
Returns:
22
- bool: True if filter matches
23
- dict: Filter data to pass to handler if matches
24
- False: Filter doesn't match
25
"""
26
27
class BaseFilter(Filter):
28
"""Alias for Filter class"""
29
pass
30
```
31
32
### Built-in Filters
33
34
Pre-built filters for common use cases.
35
36
```python { .api }
37
class Command(Filter):
38
"""Filter for bot commands"""
39
40
def __init__(
41
self,
42
*commands: str,
43
prefix: str = "/",
44
ignore_case: bool = False,
45
ignore_mention: bool = False
46
):
47
"""
48
Initialize command filter.
49
50
Parameters:
51
- commands: Command names (without prefix)
52
- prefix: Command prefix (default: "/")
53
- ignore_case: Case-insensitive matching
54
- ignore_mention: Ignore bot mentions in commands
55
"""
56
57
class CommandStart(Command):
58
"""Specialized filter for /start command with deep linking support"""
59
60
def __init__(
61
self,
62
deep_link: bool = False,
63
deep_link_encoded: bool = False,
64
ignore_case: bool = False,
65
ignore_mention: bool = False
66
):
67
"""
68
Initialize /start command filter.
69
70
Parameters:
71
- deep_link: Expect deep link parameter
72
- deep_link_encoded: Deep link parameter is base64 encoded
73
- ignore_case: Case-insensitive matching
74
- ignore_mention: Ignore bot mentions
75
"""
76
77
class StateFilter(Filter):
78
"""Filter for FSM states"""
79
80
def __init__(self, *states: State | str | None):
81
"""
82
Initialize state filter.
83
84
Parameters:
85
- states: States to match (None matches any state)
86
"""
87
88
class ChatMemberUpdatedFilter(Filter):
89
"""Filter for chat member status changes"""
90
91
def __init__(
92
self,
93
member_status_changed: bool = True,
94
is_member: bool | None = None,
95
join_transition: bool = False,
96
leave_transition: bool = False
97
):
98
"""
99
Initialize chat member updated filter.
100
101
Parameters:
102
- member_status_changed: Filter member status changes
103
- is_member: Filter by membership status
104
- join_transition: Filter join transitions
105
- leave_transition: Filter leave transitions
106
"""
107
108
class ExceptionTypeFilter(Filter):
109
"""Filter exceptions by type"""
110
111
def __init__(self, *exception_types: type[Exception]):
112
"""
113
Initialize exception type filter.
114
115
Parameters:
116
- exception_types: Exception types to match
117
"""
118
119
class ExceptionMessageFilter(Filter):
120
"""Filter exceptions by message content"""
121
122
def __init__(self, *messages: str, ignore_case: bool = True):
123
"""
124
Initialize exception message filter.
125
126
Parameters:
127
- messages: Exception messages to match
128
- ignore_case: Case-insensitive matching
129
"""
130
```
131
132
### Magic Filters
133
134
Advanced filtering system with chained conditions and dynamic property access.
135
136
```python { .api }
137
class MagicFilter:
138
"""Magic filter for complex conditions"""
139
140
def __getattr__(self, name: str) -> MagicFilter:
141
"""Access object attributes dynamically"""
142
143
def __call__(self, *args, **kwargs) -> MagicFilter:
144
"""Call the filtered object as function"""
145
146
def __eq__(self, other: Any) -> MagicFilter:
147
"""Equality comparison"""
148
149
def __ne__(self, other: Any) -> MagicFilter:
150
"""Inequality comparison"""
151
152
def __lt__(self, other: Any) -> MagicFilter:
153
"""Less than comparison"""
154
155
def __le__(self, other: Any) -> MagicFilter:
156
"""Less than or equal comparison"""
157
158
def __gt__(self, other: Any) -> MagicFilter:
159
"""Greater than comparison"""
160
161
def __ge__(self, other: Any) -> MagicFilter:
162
"""Greater than or equal comparison"""
163
164
def __and__(self, other: MagicFilter) -> MagicFilter:
165
"""Logical AND operation"""
166
167
def __or__(self, other: MagicFilter) -> MagicFilter:
168
"""Logical OR operation"""
169
170
def __invert__(self) -> MagicFilter:
171
"""Logical NOT operation"""
172
173
def in_(self, container: Any) -> MagicFilter:
174
"""Check if value is in container"""
175
176
def contains(self, item: Any) -> MagicFilter:
177
"""Check if container contains item"""
178
179
def startswith(self, prefix: str) -> MagicFilter:
180
"""Check if string starts with prefix"""
181
182
def endswith(self, suffix: str) -> MagicFilter:
183
"""Check if string ends with suffix"""
184
185
def regexp(self, pattern: str) -> MagicFilter:
186
"""Match against regular expression"""
187
188
def func(self, function: Callable[[Any], bool]) -> MagicFilter:
189
"""Apply custom function"""
190
191
def as_(self, name: str) -> MagicFilter:
192
"""Capture result with given name"""
193
194
# Global magic filter instance
195
F: MagicFilter
196
```
197
198
### Callback Data Filters
199
200
Structured callback data handling with automatic serialization.
201
202
```python { .api }
203
class CallbackData:
204
"""Callback data factory for structured inline keyboard data"""
205
206
def __init__(self, *parts: str, sep: str = ":"):
207
"""
208
Initialize callback data factory.
209
210
Parameters:
211
- parts: Data part names
212
- sep: Separator between parts
213
"""
214
215
def new(self, **values: str | int) -> str:
216
"""Create callback data string"""
217
218
def filter(self, **values: str | int | None) -> CallbackDataFilter:
219
"""Create filter for this callback data"""
220
221
def parse(self, callback_data: str) -> dict[str, str]:
222
"""Parse callback data string"""
223
224
class CallbackDataFilter(Filter):
225
"""Filter for structured callback data"""
226
pass
227
```
228
229
### Logic Operations
230
231
Combine filters with logical operations.
232
233
```python { .api }
234
def and_f(*filters: Filter) -> Filter:
235
"""Combine filters with logical AND"""
236
237
def or_f(*filters: Filter) -> Filter:
238
"""Combine filters with logical OR"""
239
240
def invert_f(filter: Filter) -> Filter:
241
"""Invert filter with logical NOT"""
242
```
243
244
### Handler Registration
245
246
Handler registration through decorators and programmatic methods.
247
248
```python { .api }
249
# Decorator usage on router observers
250
@router.message(Command("start"))
251
async def start_handler(message: Message): ...
252
253
@router.callback_query(F.data == "button_clicked")
254
async def button_handler(callback_query: CallbackQuery): ...
255
256
@router.message(F.text.contains("hello"))
257
async def text_filter_handler(message: Message): ...
258
259
# Programmatic registration
260
router.message.register(start_handler, Command("start"))
261
router.callback_query.register(button_handler, F.data == "button_clicked")
262
```
263
264
### FSM Integration
265
266
Finite State Machine integration with filters.
267
268
```python { .api }
269
class State:
270
"""Represents a single state in FSM"""
271
272
def __init__(self, state: str, group_name: str | None = None):
273
"""
274
Initialize state.
275
276
Parameters:
277
- state: State name
278
- group_name: Group name (auto-detected from StatesGroup)
279
"""
280
281
class StatesGroup:
282
"""Base class for grouping related states"""
283
pass
284
285
# Example states group
286
class Form(StatesGroup):
287
name = State()
288
age = State()
289
email = State()
290
291
# Handler with state filter
292
@router.message(StateFilter(Form.name), F.text)
293
async def process_name(message: Message, state: FSMContext):
294
await state.set_data({"name": message.text})
295
await state.set_state(Form.age)
296
await message.answer("What's your age?")
297
```
298
299
## Usage Examples
300
301
### Basic Filter Usage
302
303
```python
304
from aiogram import Router, F
305
from aiogram.types import Message, CallbackQuery
306
from aiogram.filters import Command, CommandStart, StateFilter
307
308
router = Router()
309
310
# Command filter
311
@router.message(Command("help", "info"))
312
async def help_handler(message: Message):
313
await message.answer("Help information")
314
315
# Start command with deep linking
316
@router.message(CommandStart(deep_link=True))
317
async def start_with_link(message: Message, command: CommandObject):
318
link_param = command.args
319
await message.answer(f"Started with parameter: {link_param}")
320
321
# Magic filter examples
322
@router.message(F.text == "hello")
323
async def exact_text(message: Message):
324
await message.answer("Hello to you too!")
325
326
@router.message(F.text.contains("python"))
327
async def contains_python(message: Message):
328
await message.answer("I love Python too!")
329
330
@router.message(F.text.startswith("/"))
331
async def starts_with_slash(message: Message):
332
await message.answer("That looks like a command!")
333
334
@router.message(F.from_user.is_bot == False)
335
async def from_human(message: Message):
336
await message.answer("Hello, human!")
337
```
338
339
### Advanced Magic Filter Usage
340
341
```python
342
# Complex conditions
343
@router.message(
344
(F.text.contains("hello") | F.text.contains("hi"))
345
& (F.from_user.id != 123456)
346
)
347
async def greeting_not_from_specific_user(message: Message):
348
await message.answer("Hello!")
349
350
# Multiple property access
351
@router.message(F.chat.type == "private")
352
async def private_chat_only(message: Message):
353
await message.answer("This is a private chat")
354
355
@router.message(F.photo[0].file_size > 1000000) # Photo larger than 1MB
356
async def large_photo(message: Message):
357
await message.answer("That's a large photo!")
358
359
# Content type filtering
360
@router.message(F.content_type.in_({"photo", "video"}))
361
async def media_handler(message: Message):
362
await message.answer("Nice media!")
363
364
# Regular expressions
365
@router.message(F.text.regexp(r"^\d+$"))
366
async def numbers_only(message: Message):
367
await message.answer("That's a number!")
368
369
# Custom function filter
370
def is_weekend(message):
371
return datetime.now().weekday() >= 5
372
373
@router.message(F.func(is_weekend))
374
async def weekend_handler(message: Message):
375
await message.answer("Happy weekend!")
376
```
377
378
### Callback Data Usage
379
380
```python
381
from aiogram.utils.keyboard import InlineKeyboardBuilder
382
from aiogram.filters.callback_data import CallbackData
383
384
# Define callback data structure
385
class ProductCallbackData(CallbackData, prefix="product"):
386
action: str
387
product_id: int
388
category: str
389
390
# Create keyboard with structured callback data
391
def create_product_keyboard(product_id: int, category: str):
392
builder = InlineKeyboardBuilder()
393
builder.button(
394
text="Buy",
395
callback_data=ProductCallbackData(
396
action="buy",
397
product_id=product_id,
398
category=category
399
).pack()
400
)
401
builder.button(
402
text="Details",
403
callback_data=ProductCallbackData(
404
action="details",
405
product_id=product_id,
406
category=category
407
).pack()
408
)
409
return builder.as_markup()
410
411
# Handle callback with filter
412
@router.callback_query(ProductCallbackData.filter(action="buy"))
413
async def buy_product(callback: CallbackQuery, callback_data: ProductCallbackData):
414
product_id = callback_data.product_id
415
category = callback_data.category
416
await callback.message.answer(f"Buying product {product_id} from {category}")
417
await callback.answer()
418
419
# Filter by specific values
420
@router.callback_query(ProductCallbackData.filter(category="electronics"))
421
async def electronics_handler(callback: CallbackQuery, callback_data: ProductCallbackData):
422
await callback.answer("Electronics selected!")
423
```
424
425
### State Filtering
426
427
```python
428
from aiogram.fsm.context import FSMContext
429
from aiogram.fsm.state import State, StatesGroup
430
431
class Registration(StatesGroup):
432
waiting_name = State()
433
waiting_age = State()
434
waiting_email = State()
435
436
# Start registration
437
@router.message(Command("register"))
438
async def start_registration(message: Message, state: FSMContext):
439
await state.set_state(Registration.waiting_name)
440
await message.answer("What's your name?")
441
442
# Handle name input
443
@router.message(StateFilter(Registration.waiting_name), F.text)
444
async def process_name(message: Message, state: FSMContext):
445
await state.update_data(name=message.text)
446
await state.set_state(Registration.waiting_age)
447
await message.answer("What's your age?")
448
449
# Handle age input with validation
450
@router.message(StateFilter(Registration.waiting_age), F.text.regexp(r"^\d+$"))
451
async def process_valid_age(message: Message, state: FSMContext):
452
age = int(message.text)
453
if 13 <= age <= 120:
454
await state.update_data(age=age)
455
await state.set_state(Registration.waiting_email)
456
await message.answer("What's your email?")
457
else:
458
await message.answer("Please enter a valid age (13-120)")
459
460
# Handle invalid age
461
@router.message(StateFilter(Registration.waiting_age))
462
async def process_invalid_age(message: Message):
463
await message.answer("Please enter your age as a number")
464
465
# Complete registration
466
@router.message(StateFilter(Registration.waiting_email), F.text.regexp(r".+@.+\..+"))
467
async def process_email(message: Message, state: FSMContext):
468
await state.update_data(email=message.text)
469
data = await state.get_data()
470
await state.clear()
471
472
await message.answer(
473
f"Registration complete!\n"
474
f"Name: {data['name']}\n"
475
f"Age: {data['age']}\n"
476
f"Email: {data['email']}"
477
)
478
```
479
480
### Custom Filters
481
482
```python
483
class AdminFilter(Filter):
484
"""Custom filter for admin users"""
485
486
def __init__(self, admin_ids: list[int]):
487
self.admin_ids = admin_ids
488
489
async def __call__(self, message: Message) -> bool:
490
return message.from_user and message.from_user.id in self.admin_ids
491
492
class WorkingHoursFilter(Filter):
493
"""Filter for working hours"""
494
495
def __init__(self, start_hour: int = 9, end_hour: int = 17):
496
self.start_hour = start_hour
497
self.end_hour = end_hour
498
499
async def __call__(self, obj: TelegramObject) -> bool:
500
current_hour = datetime.now().hour
501
return self.start_hour <= current_hour < self.end_hour
502
503
# Usage
504
admin_filter = AdminFilter([123456789, 987654321])
505
working_hours = WorkingHoursFilter()
506
507
@router.message(admin_filter, Command("admin"))
508
async def admin_command(message: Message):
509
await message.answer("Admin command executed!")
510
511
@router.message(working_hours, F.text)
512
async def working_hours_handler(message: Message):
513
await message.answer("Message received during working hours")
514
```
515
516
### Error Handling Filters
517
518
```python
519
@router.error(ExceptionTypeFilter(TelegramBadRequest))
520
async def bad_request_handler(error_event: ErrorEvent):
521
print(f"Bad request: {error_event.exception}")
522
523
@router.error(ExceptionMessageFilter("Forbidden"))
524
async def forbidden_handler(error_event: ErrorEvent):
525
print("Bot was blocked or lacks permissions")
526
```
527
528
## Types
529
530
### Filter Results
531
532
```python { .api }
533
class CommandObject:
534
"""Command filter result object"""
535
prefix: str
536
command: str
537
mention: str | None
538
args: str | None
539
540
class MagicData:
541
"""Magic filter result data container"""
542
pass
543
```