diff --git a/src/bot/plugins/builtin/spam.py b/src/bot/plugins/builtin/spam.py index ce94bc1..0a34d7a 100644 --- a/src/bot/plugins/builtin/spam.py +++ b/src/bot/plugins/builtin/spam.py @@ -44,11 +44,14 @@ def register_inline_keyboard_spam(application: Application) -> list[BaseHandler] def register_bio_bait_spam(application: Application) -> list[BaseHandler]: # type: ignore[type-arg] """Register bio bait spam handler (group=4). - Callback wrapped with ``guard_plugin("bio_bait_spam")``. + Callback wrapped with ``guard_plugin("bio_bait_spam")``. Shares group=4 + and filter shape with ``duplicate_spam``; ``block=False`` so both get a + chance to run instead of the first match swallowing the update. """ handler: BaseHandler = MessageHandler( BIO_BAIT_FILTER, guard_plugin("bio_bait_spam")(handle_bio_bait_spam), + block=False, ) application.add_handler(handler, group=4) logger.info("Registered handler: bio_bait_spam_handler (group=4)") @@ -83,11 +86,15 @@ def register_new_user_spam(application: Application) -> list[BaseHandler]: # ty def register_duplicate_spam(application: Application) -> list[BaseHandler]: # type: ignore[type-arg] """Register duplicate message spam handler (group=4). - Callback wrapped with ``guard_plugin("duplicate_spam")``. + Callback wrapped with ``guard_plugin("duplicate_spam")``. Registered + before ``bio_bait_spam`` in the same group with the same filter shape; + ``block=False`` so PTB still checks the next handler in group=4 instead + of stopping after this one matches. """ handler: BaseHandler = MessageHandler( filters.ChatType.GROUPS & ~filters.COMMAND, guard_plugin("duplicate_spam")(handle_duplicate_spam), + block=False, ) application.add_handler(handler, group=4) logger.info("Registered handler: duplicate_spam_handler (group=4)") diff --git a/tests/test_main_plugins_bootstrap.py b/tests/test_main_plugins_bootstrap.py index 2fb72c0..bd85cfb 100644 --- a/tests/test_main_plugins_bootstrap.py +++ b/tests/test_main_plugins_bootstrap.py @@ -321,6 +321,27 @@ def test_spam_has_duplicate_spam_registrar(self): assert hasattr(spam, "register_duplicate_spam") assert callable(spam.register_duplicate_spam) + def test_duplicate_spam_and_bio_bait_spam_share_group_and_filter_shape(self): + """duplicate_spam and bio_bait_spam sit in group=4 with the same filter shape. + + Regression guard for the collision this test class is named after: both + handlers match on ChatType.GROUPS & ~COMMAND. If either loses its + ``block=False``, PTB's default first-match-wins behavior within a group + makes whichever registers first (duplicate_spam, per MANIFEST_ORDER) + permanently swallow every update in group=4, and the other handler's + callback is never invoked. + """ + from bot.plugins.builtin import spam + + app = MagicMock() + app.add_handler = MagicMock() + + dup_handlers = spam.register_duplicate_spam(app) + bait_handlers = spam.register_bio_bait_spam(app) + + assert dup_handlers[0].block is False + assert bait_handlers[0].block is False + def test_captcha_has_registrar(self): """bot.plugins.builtin.captcha has register_captcha function.""" from bot.plugins.builtin import captcha as captcha_mod