Repository: python-telegram-bot/python-telegram-bot
Pull request: add supergroup filter
- Created a new supergroup filter
- Specified that group filter will accept both normal groups and supergroups

closes #2126

Problem statement:
[FEATURE] Filter for chat types
We currently have 

* `Filters.private` for private chats
* `Filters.updates.channel_post` for channel posts
* `Filters.group` for normal *and* supergroups

We're missing a standalone supergroup filter. Different ideas listed below. Personally, I'd go for 3.

# Proposal 1: Minimal invasive

* Add `Filters.supergroup`
* Document that `Filters.group` allows both

# Proposal 2: Adding flags

* Add a flag to `Filters.group` to specify, whether supergroups should be allowed or not

# Proposal 3: The full way

* Add `Filters.chat_type` that basically acts like `Filters.chat/user/via_bot`, just with `update.effective_message.chat.type`. We don't need to have `Filters.chat_type` updatetable, though IMHO. I really don't see a usecase for that.
* Add shortcuts for `private, channel, (super)group` as attributes to `Filters.chat_type` (similar to the `Filters.dice`)
* Make `Filters.private, group` shortcuts that work with `Filters.chat_type` and deprecate them (adding `TelegramDeprecationWarning` and documenting it)

By this, we would have two checks for `channel` (`Filters.chat_type.channel` and `Filters.update.channel_post(s)`), but especially is the latter allows to differentiate between original and edited channel post, I thinks both have their value.

Hints from issue discussion:
I'll do the standalone supergroup filter, **Proposal 1**

Theoretically, suggestion 1 would be enough, yes. But I would go with three. `update.effective_chat.type` should be more straightforward though.

How well is the difference between the two channel post filters documented? Might be even worth an example in the filters main page.
> Theoretically, suggestion 1 would be enough, yes. But I would go with three.

Cool, then we're on the same page

> `update.effective_chat.type` should be more straightforward though.

Yes, that's good

> How well is the difference between the two channel post filters documented? Might be even worth an example in the filters main page.

```
channel_post - Updates with telegram.Update.channel_post

edited_channel_post - Updates with telegram.Update.edited_channel_post

channel_posts - Updates with either telegram.Update.channel_post or telegram.Update.edited_channel_post
```

I'd say that's documented well enough and we don't need an example

Commit message: add chat_type filter

Patch to review:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index f49a3b99247..b91595e74bf 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -39,6 +39,7 @@ The following wonderful people contributed directly or indirectly to this projec
 - `evgfilim1 <https://github.com/evgfilim1>`_
 - `franciscod <https://github.com/franciscod>`_
 - `gamgi <https://github.com/gamgi>`_
+- `Gauthamram Ravichandran <https://github.com/GauthamramRavichandran>`_
 - `Harshil <https://github.com/harshil21>`_
 - `Hugo Damer <https://github.com/HakimusGIT>`_
 - `ihoru <https://github.com/ihoru>`_
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
index 8cb4d278750..ad3fc1a5155 100644
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -304,6 +304,15 @@ def filter(self, message: Message) -> bool:
         return False
 
 
+class _ChatType(MessageFilter):
+    def __init__(self, chat_type: str = Chat.PRIVATE):
+        self.chat_type = chat_type
+        self.name = 'Filters.chat_type.{}'.format(chat_type)
+
+    def filter(self, message: Message) -> bool:
+        return message.chat.type == self.chat_type
+
+
 class Filters:
     """Predefined filters for use as the ``filter`` argument of
     :class:`telegram.ext.MessageHandler`.
@@ -961,7 +970,25 @@ def filter(self, message: Message) -> bool:
             return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
 
     group = _Group()
-    """Messages sent in a group chat."""
+    """Messages sent in a group or a supergroup chat."""
+
+    class _SuperGroup(MessageFilter):
+        name = 'Filters.supergroup'
+
+        def filter(self, message: Message) -> bool:
+            return message.chat.type == Chat.SUPERGROUP
+
+    supergroup = _SuperGroup()
+    """Messages sent in a supergroup chat."""
+
+    class _ChatTypes(_ChatType):
+        """Filter using chat type"""
+        channel = _ChatType(Chat.CHANNEL)
+        group = _ChatType(Chat.GROUP)
+        supergroup = _ChatType(Chat.SUPERGROUP)
+        private = _ChatType(Chat.PRIVATE)
+
+    chat_type = _ChatTypes()
 
     class user(MessageFilter):
         """Filters messages to allow only those which are from specified user ID(s) or
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 969def023d0..488f574c751 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -581,6 +581,21 @@ def test_group_filter(self, update):
         update.message.chat.type = 'supergroup'
         assert Filters.group(update)
 
+    def test_supergroup_filter(self, update):
+        update.message.chat.type = 'group'
+        assert not Filters.supergroup(update)
+        update.message.chat.type = 'supergroup'
+        assert Filters.supergroup(update)
+
+    def test_chat_type_filter(self, update):
+        assert Filters.chat_type.private(update)
+        update.message.chat.type = 'group'
+        assert Filters.chat_type.group(update)
+        update.message.chat.type = 'supergroup'
+        assert Filters.chat_type.supergroup(update)
+        update.message.chat.type = 'channel'
+        assert Filters.chat_type.channel(update)
+
     def test_filters_user_init(self):
         with pytest.raises(RuntimeError, match='in conjunction with'):
             Filters.user(user_id=1, username='user')


Code review comments:
diff_hunk: @@ -304,6 +304,15 @@ def filter(self, message: Message) -> bool:
         return False
 
 
+class _ChatType(MessageFilter):
+    def __init__(self, chat_type: str = Chat.PRIVATE):
path: telegram/ext/filters.py
text: @user1:
The idea was that you should also be able to pass a list of strings to e.g. filter for private *or* channel

diff_hunk: @@ -304,6 +304,15 @@ def filter(self, message: Message) -> bool:
         return False
 
 
+class _ChatType(MessageFilter):
+    def __init__(self, chat_type: str = Chat.PRIVATE):
+        self.chat_type = chat_type
+        self.name = 'Filters.chat_type.{}'.format(chat_type)
path: telegram/ext/filters.py
text: @user1:
Please use f-strings, see #2124

diff_hunk: @@ -961,7 +970,25 @@ def filter(self, message: Message) -> bool:
             return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
 
     group = _Group()
-    """Messages sent in a group chat."""
+    """Messages sent in a group or a supergroup chat."""
+
+    class _SuperGroup(MessageFilter):
+        name = 'Filters.supergroup'
+
+        def filter(self, message: Message) -> bool:
+            return message.chat.type == Chat.SUPERGROUP
+
+    supergroup = _SuperGroup()
+    """Messages sent in a supergroup chat."""
+
+    class _ChatTypes(_ChatType):
+        """Filter using chat type"""
path: telegram/ext/filters.py
text: @user1:
This class is private, it shouldn't get a docstring

@author:
yes, sure. will replace this to line:992

diff_hunk: @@ -961,7 +970,25 @@ def filter(self, message: Message) -> bool:
             return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
 
     group = _Group()
-    """Messages sent in a group chat."""
+    """Messages sent in a group or a supergroup chat."""
+
+    class _SuperGroup(MessageFilter):
path: telegram/ext/filters.py
text: @user1:
please remove. the idea was to move everything to `chat_types`