From 69e4a239ab5fa0f145501f4012eda575a4e99b4e Mon Sep 17 00:00:00 2001 From: beduldul Date: Sun, 25 Oct 2026 19:17:33 +0700 Subject: [PATCH] Return False from text filters when the message has no text TextContainsFilter, TextStartsFilter and IsDigitFilter read message.text without checking that it exists. Photos, stickers and every other non-text message have text=None, so a handler using one of these filters raises instead of being skipped: @bot.message_handler(text_contains='account') # a photo arrives -> TypeError: argument of type 'NoneType' is not iterable The dispatcher doesn't catch it either - _check_filter calls the filter directly - so the exception reaches the polling loop and the update is lost. TextFilter already handles this: it takes obj.text or obj.caption and returns False when there is nothing to match. Apply the same rule here. --- telebot/custom_filters.py | 6 +++++ tests/test_custom_filters.py | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/test_custom_filters.py diff --git a/telebot/custom_filters.py b/telebot/custom_filters.py index f56535a57..2f37b54d4 100644 --- a/telebot/custom_filters.py +++ b/telebot/custom_filters.py @@ -247,6 +247,8 @@ def check(self, message, text): elif isinstance(text, list) or isinstance(text, tuple): text = [i for i in text if isinstance(i, str)] + if message.text is None: + return False return any([i in message.text for i in text]) @@ -268,6 +270,8 @@ def check(self, message, text): """ :meta private: """ + if message.text is None: + return False return message.text.startswith(text) @@ -452,4 +456,6 @@ def check(self, message): """ :meta private: """ + if message.text is None: + return False return message.text.isdigit() diff --git a/tests/test_custom_filters.py b/tests/test_custom_filters.py new file mode 100644 index 000000000..3d8386e6b --- /dev/null +++ b/tests/test_custom_filters.py @@ -0,0 +1,52 @@ +import sys + +sys.path.append('../') + +import json + +import pytest + +from telebot import types +from telebot.custom_filters import IsDigitFilter, TextContainsFilter, TextStartsFilter + + +def make_message(**fields): + payload = { + "message_id": 1, + "date": 0, + "chat": {"id": 1, "type": "private"}, + "from": {"id": 1, "is_bot": False, "first_name": "Test"}, + } + payload.update(fields) + return types.Message.de_json(json.dumps(payload)) + + +@pytest.fixture +def photo_message(): + return make_message( + photo=[{"file_id": "f", "file_unique_id": "u", "width": 1, "height": 1}] + ) + + +@pytest.fixture +def text_message(): + return make_message(text="12345") + + +def test_text_contains_filter_skips_message_without_text(photo_message): + assert photo_message.text is None + assert TextContainsFilter().check(photo_message, "23") is False + + +def test_text_startswith_filter_skips_message_without_text(photo_message): + assert TextStartsFilter().check(photo_message, "12") is False + + +def test_is_digit_filter_skips_message_without_text(photo_message): + assert IsDigitFilter().check(photo_message) is False + + +def test_text_filters_still_match_text_messages(text_message): + assert TextContainsFilter().check(text_message, "23") is True + assert TextStartsFilter().check(text_message, "12") is True + assert IsDigitFilter().check(text_message) is True