Repository: python-telegram-bot/python-telegram-bot

Issue:
Add msg_in filter
Filter that allows exact matches with messages in a list.

Can be used to make sure that user replies with one of the custom keyboard buttons.


Discussion:
Thank you a lot for taking the time to write this code, and submitting this PR! :D
I am, however, unsure of the benefit of adding this filter, for these 2 reasons:
- Doesn't provide which keyword triggered the filter to the handler function, which means you need to check it in your handler function afterwards
- Usually one would just use a RegexHandler for this sort of thing. This has the added benefit of actually passing to the handler function which keyword matched.

Any of the devs feel differently?
The purpose of this filter is different. It doesn't check if a keyword exists in message, but if the message is an exact match with one of the strings in the list. 

For example if you have a keyboard markup generated from a list:
```
buttons = ['Start', 'Settings', 'Back']
markup = ReplyKeyboardMarkup([[x] for x in buttons])
bot.send_message(chat_id, text, reply_markup=markup)
```
You can use `MessageHandler(Filters.in_list(buttons), callback)` in one of the states of `ConversationHandler` to filter out any replies that don't match one of the buttons and direct them to fallback handler. And you can get the string that triggered that filter from `update.message.text`, as it is an exact match to the one in the list.
Alright, you're right. I agree that we need much a filter. I think we should simply name it `in` instead of `in_list` since it should work on everything that the python keyword `in` does.
@jsmnbom It doesn't specify what attribute of an update you're actually comparing though. Shouldn't it be text_in(), caption_in(), callback_query_in() perhaps?
Yes, `in` is a reserved keyword. Maybe `msg_in`?
It might be a good idea to go with @JosXa 's suggestion then: `text_in` ?

I'm definitely not a fan of having `list` in there, since it works with more things than lists (other sequences and sets and stuff too).
@icecom-dg I'd like msg_in with parameters to control whether to check message text or caption
Review requested. Probably not the best docstring.

Resolving patch:
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -909,6 +909,39 @@ def filter(self, message):
             return message.from_user.language_code and any(
                 [message.from_user.language_code.startswith(x) for x in self.lang])
 
+    class msg_in(BaseFilter):
+        """Filters messages to only allow those whose text/caption appears in a given list.
+
+        Examples:
+            A simple usecase is to allow only messages that were send by a custom
+            :class:`telegram.ReplyKeyboardMarkup`::
+
+                buttons = ['Start', 'Settings', 'Back']
+                markup = ReplyKeyboardMarkup.from_column(buttons)
+                ...
+                MessageHandler(Filters.msg_in(buttons), callback_method)
+
+        Args:
+            list_ (List[:obj:`str`]): Which messages to allow through. Only exact matches
+                are allowed.
+            caption (:obj:`bool`): Optional. Whether the caption should be used instead of text.
+                Default is ``False``.
+
+        """
+
+        def __init__(self, list_, caption=False):
+            self.list_ = list_
+            self.caption = caption
+            self.name = 'Filters.msg_in({!r}, caption={!r})'.format(self.list_, self.caption)
+
+        def filter(self, message):
+            if self.caption:
+                txt = message.caption
+            else:
+                txt = message.text
+
+            return txt in self.list_
+
     class _UpdateType(BaseFilter):
         update_filter = True
 


Test patch:
diff --git a/tests/test_filters.py b/tests/test_filters.py
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -604,6 +604,16 @@ def test_language_filter_multiple(self, update):
         update.message.from_user.language_code = 'da'
         assert f(update)
 
+    def test_msg_in_filter(self, update):
+        update.message.text = 'test'
+        update.message.caption = 'caption'
+
+        assert Filters.msg_in(['test'])(update)
+        assert Filters.msg_in(['caption'], caption=True)(update)
+
+        assert not Filters.msg_in(['test'], caption=True)(update)
+        assert not Filters.msg_in(['caption'])(update)
+
     def test_and_filters(self, update):
         update.message.text = 'test'
         update.message.forward_date = datetime.datetime.now()