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

Issue:
[Suggestion] Add chat(s) parameter to ChatJoinRequestHandler
This param should allow to filter out chats which will be handled by the ChatJoinRequestHandler, much like the pattern argument of the CallbackQueryHandler. It should allow "username" strings as well as ids and if set, the handler should check if the incoming update is from that chat.

For first time contributors, check how CallbackQueryHandler implements the pattern argument in check_update: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/_callbackqueryhandler.py#L123


Discussion:
@Poolitzer CQH.pattern is rather different though, right? I&#x27;d rather compare with <a href="https://docs.python-telegram-bot.org/telegram.ext.filters.html#telegram.ext.filters.Chat">telegram.ext.filters.Chat</a> <a href="https://core.telegram.org/bots/api#chat">ᵀᴱᴸᴱᴳᴿᴬᴹ</a>
is this still available for a newbie like me?
Yes it is!

Resolving patch:
diff --git a/telegram/ext/_chatjoinrequesthandler.py b/telegram/ext/_chatjoinrequesthandler.py
--- a/telegram/ext/_chatjoinrequesthandler.py
+++ b/telegram/ext/_chatjoinrequesthandler.py
@@ -18,16 +18,27 @@
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 """This module contains the ChatJoinRequestHandler class."""
 
+from typing import FrozenSet, Optional
 
 from telegram import Update
+from telegram._utils.defaultvalue import DEFAULT_TRUE
+from telegram._utils.types import RT, SCT, DVInput
 from telegram.ext._handler import BaseHandler
-from telegram.ext._utils.types import CCT
+from telegram.ext._utils.types import CCT, HandlerCallback
 
 
 class ChatJoinRequestHandler(BaseHandler[Update, CCT]):
     """BaseHandler class to handle Telegram updates that contain
     :attr:`telegram.Update.chat_join_request`.
 
+    Note:
+        If neither of :paramref:`username` and the :paramref:`chat_id` are passed, this handler
+        accepts *any* join request. Otherwise, this handler accepts all requests to join chats
+        for which the chat ID is listed in :paramref:`chat_id` or the username is listed in
+        :paramref:`username`, or both.
+
+        .. versionadded:: 20.0
+
     Warning:
         When setting :paramref:`block` to :obj:`False`, you cannot rely on adding custom
         attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
@@ -43,6 +54,14 @@ async def callback(update: Update, context: CallbackContext)
 
             The return value of the callback is usually ignored except for the special case of
             :class:`telegram.ext.ConversationHandler`.
+        chat_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only
+            those which are asking to join the specified chat ID(s).
+
+            .. versionadded:: 20.0
+        username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only
+            those which are asking to join the specified username(s).
+
+            .. versionadded:: 20.0
         block (:obj:`bool`, optional): Determines whether the return value of the callback should
             be awaited before processing the next handler in
             :meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
@@ -53,7 +72,38 @@ async def callback(update: Update, context: CallbackContext)
 
     """
 
-    __slots__ = ()
+    __slots__ = (
+        "_chat_ids",
+        "_usernames",
+    )
+
+    def __init__(
+        self,
+        callback: HandlerCallback[Update, CCT, RT],
+        chat_id: SCT[int] = None,
+        username: SCT[str] = None,
+        block: DVInput[bool] = DEFAULT_TRUE,
+    ):
+        super().__init__(callback, block=block)
+
+        self._chat_ids = self._parse_chat_id(chat_id)
+        self._usernames = self._parse_username(username)
+
+    @staticmethod
+    def _parse_chat_id(chat_id: Optional[SCT[int]]) -> FrozenSet[int]:
+        if chat_id is None:
+            return frozenset()
+        if isinstance(chat_id, int):
+            return frozenset({chat_id})
+        return frozenset(chat_id)
+
+    @staticmethod
+    def _parse_username(username: Optional[SCT[str]]) -> FrozenSet[str]:
+        if username is None:
+            return frozenset()
+        if isinstance(username, str):
+            return frozenset({username[1:] if username.startswith("@") else username})
+        return frozenset({usr[1:] if usr.startswith("@") else usr for usr in username})
 
     def check_update(self, update: object) -> bool:
         """Determines whether an update should be passed to this handler's :attr:`callback`.
@@ -65,4 +115,12 @@ def check_update(self, update: object) -> bool:
             :obj:`bool`
 
         """
-        return isinstance(update, Update) and bool(update.chat_join_request)
+        if isinstance(update, Update) and update.chat_join_request:
+            if not self._chat_ids and not self._usernames:
+                return True
+            if update.chat_join_request.chat.id in self._chat_ids:
+                return True
+            if update.chat_join_request.from_user.username in self._usernames:
+                return True
+            return False
+        return False


Test patch:
diff --git a/tests/test_chatjoinrequesthandler.py b/tests/test_chatjoinrequesthandler.py
--- a/tests/test_chatjoinrequesthandler.py
+++ b/tests/test_chatjoinrequesthandler.py
@@ -78,7 +78,7 @@ def time():
 def chat_join_request(time, bot):
     return ChatJoinRequest(
         chat=Chat(1, Chat.SUPERGROUP),
-        from_user=User(2, "first_name", False),
+        from_user=User(2, "first_name", False, username="user_a"),
         date=time,
         bio="bio",
         invite_link=ChatInviteLink(
@@ -127,6 +127,43 @@ async def callback(self, update, context):
             )
         )
 
+    def test_with_chat_id(self, chat_join_request_update):
+        handler = ChatJoinRequestHandler(self.callback, chat_id=1)
+        assert handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, chat_id=[1])
+        assert handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, chat_id=2, username="@user_a")
+        assert handler.check_update(chat_join_request_update)
+
+        handler = ChatJoinRequestHandler(self.callback, chat_id=2)
+        assert not handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, chat_id=[2])
+        assert not handler.check_update(chat_join_request_update)
+
+    def test_with_username(self, chat_join_request_update):
+        handler = ChatJoinRequestHandler(self.callback, username="user_a")
+        assert handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, username="@user_a")
+        assert handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, username=["user_a"])
+        assert handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, username=["@user_a"])
+        assert handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, chat_id=1, username="@user_b")
+        assert handler.check_update(chat_join_request_update)
+
+        handler = ChatJoinRequestHandler(self.callback, username="user_b")
+        assert not handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, username="@user_b")
+        assert not handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, username=["user_b"])
+        assert not handler.check_update(chat_join_request_update)
+        handler = ChatJoinRequestHandler(self.callback, username=["@user_b"])
+        assert not handler.check_update(chat_join_request_update)
+
+        chat_join_request_update.chat_join_request.from_user.username = None
+        assert not handler.check_update(chat_join_request_update)
+
     def test_other_update_types(self, false_update):
         handler = ChatJoinRequestHandler(self.callback)
         assert not handler.check_update(false_update)