MadelineProto provides a powerful filtering system that allows you to handle only the updates you care about. Filters can be combined using PHP 8.1+ intersection types for precise control.
Filters only work with SimpleEventHandler. Make sure your class extends SimpleEventHandler, not just EventHandler.
use danog\MadelineProto\EventHandler\CommandType;#[FilterCommand('test', types: [CommandType::SLASH])]public function slashOnly(Message $message): void{ // Only matches /test}#[FilterCommand('test', types: [CommandType::BANG, CommandType::DOT])]public function bangOrDot(Message $message): void{ // Matches !test or .test}
use danog\MadelineProto\EventHandler\Filter\{FilterText, FilterTextCaseInsensitive};#[FilterText('hello')]public function exactMatch(Message $message): void{ // Only matches exactly "hello" $message->reply('Hello!');}#[FilterTextCaseInsensitive('hi')]public function caseInsensitive(Message $message): void{ // Matches "hi", "Hi", "HI", etc. $message->reply('Hey there!');}
use danog\MadelineProto\EventHandler\Filter\FilterRegex;#[FilterRegex('/.*(mt?proto)[^.]?.*/i')]public function regexMatch(Message $message): void{ // Access captured groups via $message->matches $matched = $message->matches[1]; $message->reply("Did you mean MadelineProto instead of $matched?");}
From examples/bot.php:230, regex captures are available in $message->matches.
use danog\MadelineProto\EventHandler\Filter\FilterButtonQueryData;#[FilterButtonQueryData('button_action')]public function handleButton(CallbackQuery $query): void{ $query->answer('Button clicked!');}
use danog\MadelineProto\EventHandler\Message\{PrivateMessage, GroupMessage, ChannelMessage};use danog\MadelineProto\EventHandler\Attributes\Handler;#[Handler]public function privateOnly(PrivateMessage $message): void{ // Only private messages}#[Handler]public function groupOnly(GroupMessage $message): void{ // Only group messages}#[Handler]public function channelOnly(ChannelMessage $message): void{ // Only channel posts}
From EventHandler/Filter/Filter.php:98, type hints are automatically converted to filters.
use danog\MadelineProto\EventHandler\SimpleFilter\{Incoming, Outgoing};#[Handler]public function incomingOnly(Incoming&Message $message): void{ // Only incoming messages}#[Handler]public function outgoingOnly(Outgoing&Message $message): void{ // Only outgoing messages (sent by bot)}
From EventHandler/SimpleFilter/Incoming.php:19, these are marker interfaces for filtering message direction.
use danog\MadelineProto\EventHandler\SimpleFilter\{ IsReply, IsEdited, IsForwarded, FromAdmin, HasMedia, HasNoMedia};#[Handler]public function replyOnly(IsReply&Message $message): void{ // Only messages that are replies $original = $message->getReply(Message::class);}#[Handler]public function adminOnly(FromAdmin&Message $message): void{ // Only messages from admins}#[Handler]public function editedMessages(IsEdited&Message $message): void{ // Only edited messages}
Combine multiple filters using & (intersection types):
use danog\MadelineProto\EventHandler\SimpleFilter\{Incoming, FromAdmin, HasPhoto};#[Handler]public function adminPhotos(Incoming&FromAdmin&HasPhoto&Message $message): void{ // Only incoming photos from admins}#[FilterCommand('broadcast')]public function broadcast(Message & FromAdmin $message): void{ // /broadcast command only from admins}
From examples/bot.php:156, intersection types combine multiple filter conditions.
use danog\MadelineProto\EventHandler\Message\{PrivateMessage, GroupMessage};#[Handler]public function privateOrGroup(PrivateMessage|GroupMessage $message): void{ // Both private and group messages}
Create custom filter attributes by extending Filter:
use danog\MadelineProto\EventHandler\Filter\Filter;use danog\MadelineProto\EventHandler\Update;use Attribute;#[Attribute(Attribute::TARGET_METHOD)]class FilterPremiumUser extends Filter{ public function apply(Update $update): bool { if (!($update instanceof Message)) { return false; } $sender = $this->getInfo($update->senderId); return $sender['premium'] ?? false; }}// Usage#[FilterPremiumUser]public function premiumOnly(Message $message): void{ $message->reply('Thanks for being a premium user!');}
From EventHandler/Filter/Filter.php:74, custom filters must implement the apply() method.
use danog\MadelineProto\EventHandler;class FilterFromChannel extends Filter{ private int $channelId; public function __construct(string $username) { // Will be resolved during initialization $this->username = $username; } public function initialize(EventHandler $API): Filter { // Resolve username to ID $info = $API->getInfo($this->username); $this->channelId = $info['bot_api_id']; return $this; } public function apply(Update $update): bool { return $update instanceof Message && $update->chatId === $this->channelId; }}
From EventHandler/Filter/Filter.php:78, the initialize() method is called during setup.
#[FilterCommand('dl')]public function downloadCommand(Incoming&Message $message): void{ $reply = $message->getReply(Message::class); if (!$reply?->media) { $message->reply('Reply to a media message!'); return; } $message->reply('Download: ' . $reply->media->getDownloadLink());}
From examples/bot.php:280, this pattern checks for media in replied messages.
use danog\MadelineProto\EventHandler\SimpleFilter\{HasQuizPoll, HasSinglePoll, HasMultiplePoll};#[Handler]public function quizPoll(HasQuizPoll&Message $message): void{ // Only quiz polls}#[Handler]public function regularPoll(HasSinglePoll&Message $message): void{ // Only single-choice polls}#[Handler]public function multiplePoll(HasMultiplePoll&Message $message): void{ // Only multiple-choice polls}
use danog\MadelineProto\EventHandler\Filter\Combinator\{FiltersAnd, FiltersOr, FilterNot};// Manual filter combination (usually not needed with intersection types)class MyFilter extends Filter{ public function apply(Update $update): bool { $filter = new FiltersAnd( new FilterPrivate, new FilterIncoming, new FilterNot(new FilterFromAdmin) ); return $filter->apply($update); }}
From EventHandler/Filter/Combinator/FiltersAnd.php, combinators allow complex filter logic.