or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api-methods.mdbot-and-dispatcher.mdfilters-and-handlers.mdindex.mdstate-management.mdtypes-and-objects.mdutilities-and-enums.md

filters-and-handlers.mddocs/

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

```