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

Issue:
[FEATURE] Add bot mentioned filter
### What kind of feature are you missing? Where do you notice a shortcoming of PTB?

There is currently no filter defined by us which simply checks if message contains a bot mention, so `@botusername message`.

### Describe the solution you'd like

Make a new filter which checks if a message contains the mention of a bot. So first check if the message has the [`telegram.constants.MessageEntityType.MENTION`](https://docs.python-telegram-bot.org/en/stable/telegram.constants.html#telegram.constants.MessageEntityType.MENTION), and then check if the username is the one of the bot

### Describe alternatives you've considered

One can do this filter by themselves, but providing one would be nice

### Additional context

Maybe having the only_start parameter here would be nice, same as with the command filter.

## Assignees:

~Current assignee: @ElmurodovJavohir - #3857. If they don't commit/reply by Oct 7th, the next in line to tackle this issue is:~

~- @Prathmesh-rajurkar~
~- @AbhigyanBaruah~

Issue is being tackled by dev team due to the continued inactivity from the contributors.


Discussion:
I would love to work on this. Please assign me if you would like me to do this.
@shivamsn97 Assigned you 👍
Before work goes into a PR for this, I would like to clarify at least the naming of the filter. Also I have two more thoughts:

- I think it would be good to make the filter generic in the way that only the bot but any specified user/chat can be mentioned
- it might even be beneficial to filter for any specified MessageEntity object, optionally with ignoring the offset and length. e.g. You could filter for messages that contain a specific underlined word 
> it might even be beneficial to filter for any specified MessageEntity object

We already have [filters.Entity](https://docs.python-telegram-bot.org/en/doc-fixes/telegram.ext.filters.html#telegram.ext.filters.Entity) for this?
Got them. Let me know what you think about the naming of the filter.
@harshil21 This filters only for the type, not a specific instance. I.e. you can filter for messages that contain any bold word, but not for a specific one
Okay, my rambling was nonsense, as `MessageEntity` objects don't contain the text that is formatted. One *could* pass a `MessageEntity` object along with the expected text, but I see that this clumsy to use and the use case is surely minimal.
I would like to see a generic "mention" filter though instead of a specifc "bot mention" filter. I guess it should accept as input a

* a username
* a user id (as string or int)
* a `User` object

and then use a combination of `MessageEntity.user` and `Message.parse_entities` to check if any of the `MENTION` *or* `TEXT_MENTION` entities mentions that user.
Accepting a sequence as input also sounds reasonable, allowing several users to be mentioned.

I guess `filters.Mention` would make sense as name.

@Poolitzer @harshil21 what do you think?
Hi @shivamsn97, are you still interested in this? I'd love to take it if you found yourself busy, no pressure tho
@aelkheir if shivam doesn't reply fover the weekend, I guess you can go ahead and pr :)
@Bibo-Joshi the name wasn't finalized yet right? That's what I was waiting for. There was no update from harshil and poolitzer.
Anyways, @aelkheir you can take it if you want, no issues.
Ah, right, my bad. let me ping them offline
> @Bibo-Joshi the name wasn't finalized yet right? That's what I was waiting for. There was no update from harshil and poolitzer. Anyways, @aelkheir you can take it if you want, no issues.

Oh sorry for the confusion. I was just a little excited for my first contribution :). @shivamsn97 you can wait for their input and I'll take the next issue 😄.
> I guess `filters.Mention` would make sense as name.
> 
> @Poolitzer @harshil21 what do you think?

Yes that name and the implementation seems fine with me
Poolitzer is currently unavailable, but with harshils approval I'd say we're good to go @shivamsn97  :)
Thanks. I'll let you know about any update or questions.
I see no sign of another good first issue. So consider me standby for this one 👍
@aelkheir #3825 is also a good first issue, if you'd like to do that
Yes, totally. Replied on the issue.
Due to inactivity, I guess this issue is up for the taking again. If anyone is working on a PR, please leave a short comment.

Resolving patch:
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -66,6 +66,7 @@
     "LOCATION",
     "Language",
     "MessageFilter",
+    "Mention",
     "PASSPORT_DATA",
     "PHOTO",
     "POLL",
@@ -91,7 +92,6 @@
     "VOICE",
     "ViaBot",
 )
-
 import mimetypes
 import re
 from abc import ABC, abstractmethod
@@ -99,6 +99,7 @@
     Collection,
     Dict,
     FrozenSet,
+    Iterable,
     List,
     Match,
     NoReturn,
@@ -1521,6 +1522,73 @@ def filter(self, message: Message) -> bool:
 """Messages that contain :attr:`telegram.Message.location`."""
 
 
+class Mention(MessageFilter):
+    """Messages containing mentions of specified users or chats.
+
+    Examples:
+        .. code-block:: python
+
+            MessageHandler(filters.Mention("username"), callback)
+            MessageHandler(filters.Mention(["@username", 123456]), callback)
+
+    .. versionadded:: NEXT.VERSION
+
+    Args:
+        mentions (:obj:`int` | :obj:`str` | :class:`telegram.User` | Collection[:obj:`int` | \
+            :obj:`str` | :class:`telegram.User`]):
+            Specifies the users and chats to filter for. Messages that do not mention at least one
+            of the specified users or chats will not be handled. Leading ``'@'`` s in usernames
+            will be discarded.
+    """
+
+    __slots__ = ("_mentions",)
+
+    def __init__(self, mentions: SCT[Union[int, str, TGUser]]):
+        super().__init__(name=f"filters.Mention({mentions})")
+        if isinstance(mentions, Iterable) and not isinstance(mentions, str):
+            self._mentions = {self._fix_mention_username(mention) for mention in mentions}
+        else:
+            self._mentions = {self._fix_mention_username(mentions)}
+
+    @staticmethod
+    def _fix_mention_username(mention: Union[int, str, TGUser]) -> Union[int, str, TGUser]:
+        if not isinstance(mention, str):
+            return mention
+        return mention.lstrip("@")
+
+    @classmethod
+    def _check_mention(cls, message: Message, mention: Union[int, str, TGUser]) -> bool:
+        if not message.entities:
+            return False
+
+        entity_texts = message.parse_entities(
+            types=[MessageEntity.MENTION, MessageEntity.TEXT_MENTION]
+        )
+
+        if isinstance(mention, TGUser):
+            return any(
+                mention.id == entity.user.id
+                or mention.username == entity.user.username
+                or mention.username == cls._fix_mention_username(entity_texts[entity])
+                for entity in message.entities
+                if entity.user
+            ) or any(
+                mention.username == cls._fix_mention_username(entity_text)
+                for entity_text in entity_texts.values()
+            )
+        if isinstance(mention, int):
+            return bool(
+                any(mention == entity.user.id for entity in message.entities if entity.user)
+            )
+        return any(
+            mention == cls._fix_mention_username(entity_text)
+            for entity_text in entity_texts.values()
+        )
+
+    def filter(self, message: Message) -> bool:
+        return any(self._check_mention(message, mention) for mention in self._mentions)
+
+
 class _PassportData(MessageFilter):
     __slots__ = ()
 


Test patch:
diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py
--- a/tests/ext/test_filters.py
+++ b/tests/ext/test_filters.py
@@ -2423,3 +2423,65 @@ def test_filters_attachment(self, update):
             ),
         )
         assert filters.ATTACHMENT.check_update(up)
+
+    def test_filters_mention_no_entities(self, update):
+        update.message.text = "test"
+        assert not filters.Mention("@test").check_update(update)
+        assert not filters.Mention(123456).check_update(update)
+        assert not filters.Mention("123456").check_update(update)
+        assert not filters.Mention(User(1, "first_name", False)).check_update(update)
+        assert not filters.Mention(
+            ["@test", 123456, "123456", User(1, "first_name", False)]
+        ).check_update(update)
+
+    def test_filters_mention_type_mention(self, update):
+        update.message.text = "@test1 @test2 user"
+        update.message.entities = [
+            MessageEntity(MessageEntity.MENTION, 0, 6),
+            MessageEntity(MessageEntity.MENTION, 7, 6),
+        ]
+
+        user_no_username = User(123456, "first_name", False)
+        user_wrong_username = User(123456, "first_name", False, username="wrong")
+        user_1 = User(111, "first_name", False, username="test1")
+        user_2 = User(222, "first_name", False, username="test2")
+
+        for username in ("@test1", "@test2"):
+            assert filters.Mention(username).check_update(update)
+            assert filters.Mention({username}).check_update(update)
+
+        for user in (user_1, user_2):
+            assert filters.Mention(user).check_update(update)
+            assert filters.Mention({user}).check_update(update)
+
+        assert not filters.Mention(
+            ["@test3", 123, user_no_username, user_wrong_username]
+        ).check_update(update)
+
+    def test_filters_mention_type_text_mention(self, update):
+        user_1 = User(111, "first_name", False, username="test1")
+        user_2 = User(222, "first_name", False, username="test2")
+        user_no_username = User(123456, "first_name", False)
+        user_wrong_username = User(123456, "first_name", False, username="wrong")
+
+        update.message.text = "test1 test2 user"
+        update.message.entities = [
+            MessageEntity(MessageEntity.TEXT_MENTION, 0, 5, user=user_1),
+            MessageEntity(MessageEntity.TEXT_MENTION, 6, 5, user=user_2),
+        ]
+
+        for username in ("@test1", "@test2"):
+            assert filters.Mention(username).check_update(update)
+            assert filters.Mention({username}).check_update(update)
+
+        for user in (user_1, user_2):
+            assert filters.Mention(user).check_update(update)
+            assert filters.Mention({user}).check_update(update)
+
+        for user_id in (111, 222):
+            assert filters.Mention(user_id).check_update(update)
+            assert filters.Mention({user_id}).check_update(update)
+
+        assert not filters.Mention(
+            ["@test3", 123, user_no_username, user_wrong_username]
+        ).check_update(update)