Skip to content

Return False from text filters when the message has no text - #2645

Merged
Badiboy merged 1 commit into
eternnoir:masterfrom
beduldul:fix/text-filters-none-text
Oct 25, 2026
Merged

Badiboy merged 1 commit into
eternnoir:masterfrom
beduldul:fix/text-filters-none-text

Conversation

@beduldul

Copy link
Copy Markdown
Contributor

Description

TextContainsFilter, TextStartsFilter and IsDigitFilter read message.text without checking that it exists. Photos, stickers and every other non-text message have text = None:

>>> TextContainsFilter().check(photo_message, "23")
TypeError: argument of type 'NoneType' is not a container or iterable
>>> TextStartsFilter().check(photo_message, "12")
AttributeError: 'NoneType' object has no attribute 'startswith'
>>> IsDigitFilter().check(photo_message)
AttributeError: 'NoneType' object has no attribute 'isdigit'

The dispatcher doesn't swallow it either - _check_filter() calls the filter directly - so a @bot.message_handler(text_contains='...') handler raises on every photo instead of being skipped.

TextFilter already gets this right: it takes obj.text or obj.caption and returns False when there is nothing to match. The three filters now do the same.

Describe your tests

New tests/test_custom_filters.py; the module had no tests before.

Three cases build a photo message through Message.de_json and check each filter returns False instead of raising. A fourth keeps the normal text-message path covered.

Without the change: 3 failed, 1 passed. With it: 4 passed. Full suite: 288 passed, 2 skipped.

Python version: 3.14.9

OS: macOS

Checklist:

  • I added/edited example on new feature/change (if exists)
  • My changes won't break backward compatibility
  • I made changes both for sync and async

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.
@Badiboy

Badiboy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

👍

@Badiboy
Badiboy merged commit 8ba52ca into eternnoir:master Oct 25, 2026
7 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The asynchronous counterparts still raise on missing text, leaving the stated sync-and-async fix incomplete.

1 open issue
What changed in this PR

This PR prevents three synchronous text filters from raising exceptions when a message has no text, allowing handlers to skip non-text messages safely.

Changes:

  • Add None guards to TextContainsFilter, TextStartsFilter, and IsDigitFilter.
  • Add regression tests for photos without text and preserve coverage of successful text matching.
File Description
tests/​test_custom_filters.py Tests non-text rejection and normal text matching.
telebot/​custom_filters.py Returns False from three filters when message text is None.

Review effort: Balanced

Comment thread telebot/custom_filters.py
Comment on lines +250 to +251
if message.text is None:
return False

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beduldul - please, fix also.

Badiboy pushed a commit that referenced this pull request Oct 31, 2026
#2645 added the 'no text' guard to the sync TextContainsFilter, TextStartsFilter and IsDigitFilter, and an older change added it to the sync TextFilter. The asyncio_filters versions still raise on a message with neither text nor caption, which aborts the remaining handlers for that update.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants