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

Issue:
Overhaul & extension of constants
We have constants in different locations, e.g.

* `telegram.constants`
* class attributes of `telegram.ParseMode`
* class attributes of `telegram.Dice`

That should be unified, i.e.:

- [x] Gather all constants in `telegram.constants` (I guess a regex-search for `SCREAMING_SNAIL_CASE` should do the trick …)
- [x] Repace the class attributes by links to the constant from `telegram.constant`, (e.g. `ParseMode.HTML = constants.PARSE_MODE_HTML`)
- [x] Add additional links, where suitable (e.g. `Message.MAX_LENGTH = constants.MAX_MESSAGE_LENGTH`)

Moreover, in light of the recent TG update, we should add

- [x] constants for user ID `777000` and `1087968824`, which act as senders for forwarded channel posts and messages from anonymous admins (see [here](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Code-snippets#exclude-messages-from-anonymous-admins))
- [x] Add properties `is_anonymous_admin` and `is_service_chat` (or similar namings) to `User` and `Chat` that compare the user/chat_id to the IDs menotioned above

*Edit:* there should be notes on the constants, if they are not documented. And going through the docs to see if there are constants not covered already probably won't hurt …


Discussion:
maybe document that the ids are just from our experience and not officially mentioned?
I would like to do this. Did I understand correctly that I need to transfer the attributes of all classes to constants? Or should it be done selectively? I made a test commit, please tell me, am I moving in the right way? https://github.com/NikitaPirate/python-telegram-bot/commit/d599259dfda0622b87894d88303ad802630fefa9
> I would like to do this.

👍 I'm assigning you.

> Did I understand correctly that I need to transfer the attributes of all classes to constants?

yes, but also make the class attributes link to the constants. In your example e.g.

```python
PRIVATE = constants.PRIVATE_CHAT
```
Also, we will be releasing v13 in the next few days, so I'd recommand to wait for that. Should save you some merging ;)


Maybe it will be better if I only move constants to constants.py and make the class attributes link to it and leave everything else unchanged? Or is it better to change `Class.ATTR` to `constants.CONSTANT` in all code? 
@NikitaPirate No in order to maintain backwards compatibility link class.ATTR to constant.CONSTANT :)

Resolving patch:
diff --git a/telegram/chat.py b/telegram/chat.py
--- a/telegram/chat.py
+++ b/telegram/chat.py
@@ -150,6 +150,21 @@ def link(self) -> Optional[str]:
             return "https://t.me/{}".format(self.username)
         return None
 
+    @property
+    def is_anonymous_admin(self) -> bool:
+        """:obj:`bool`: Convenience property. Returns :obj:`True`, if this chat is with is the bot
+        representing anonymous admins. This behaviour is undocumented and might be changed
+        by Telegram. """
+
+        return self.id == constants.ANONYMOUS_ADMIN_ID
+
+    @property
+    def is_service_chat(self) -> bool:
+        """:obj:`bool`: Convenience property. Returns :obj:`True`, if this chat is the Telegram
+        service chat. This behaviour is undocumented and might be changed by Telegram. """
+
+        return self.id == constants.SERVICE_CHAT_ID
+
     @classmethod
     def de_json(cls, data: JSONDict, bot: 'Bot') -> Optional['Chat']:
         data = cls.parse_data(data)
diff --git a/telegram/constants.py b/telegram/constants.py
--- a/telegram/constants.py
+++ b/telegram/constants.py
@@ -38,6 +38,9 @@
 Attributes:
     MAX_MESSAGE_ENTITIES (:obj:`int`): 100 (Beyond this cap telegram will simply ignore further
         formatting styles)
+    ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin)
+    SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of
+        channel posts forwarded to discussion groups)
 
 The following constants are related to specific classes and are also available
 as attributes of those classes:
@@ -128,6 +131,8 @@
 
 MAX_MESSAGE_LENGTH: int = 4096
 MAX_CAPTION_LENGTH: int = 1024
+ANONYMOUS_ADMIN_ID: int = 1087968824
+SERVICE_CHAT_ID: int = 777000
 
 # constants above this line are tested
 
diff --git a/telegram/user.py b/telegram/user.py
--- a/telegram/user.py
+++ b/telegram/user.py
@@ -19,7 +19,7 @@
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 """This module contains an object that represents a Telegram User."""
 
-from telegram import TelegramObject
+from telegram import TelegramObject, constants
 from telegram.utils.helpers import mention_html as util_mention_html
 from telegram.utils.helpers import mention_markdown as util_mention_markdown
 
@@ -122,6 +122,20 @@ def link(self) -> Optional[str]:
             return "https://t.me/{}".format(self.username)
         return None
 
+    @property
+    def is_anonymous_admin(self) -> bool:
+        """:obj:`bool`: Convenience property. Returns :obj:`True`, if this user is
+        an anonymous admin. This behaviour is undocumented and might be changed by Telegram."""
+
+        return self.id == constants.ANONYMOUS_ADMIN_ID
+
+    @property
+    def is_service_chat(self) -> bool:
+        """:obj:`bool`: Convenience property. Returns :obj:`True`, if this user is
+        the telegram service. This behaviour is undocumented and might be changed by Telegram."""
+
+        return self.id == constants.SERVICE_CHAT_ID
+
     def get_profile_photos(self, *args: Any, **kwargs: Any) -> 'UserProfilePhotos':
         """
         Shortcut for::


Test patch:
diff --git a/tests/test_chat.py b/tests/test_chat.py
--- a/tests/test_chat.py
+++ b/tests/test_chat.py
@@ -19,7 +19,7 @@
 
 import pytest
 
-from telegram import Chat, ChatAction, ChatPermissions
+from telegram import Chat, ChatAction, ChatPermissions, constants
 from telegram import User
 
 
@@ -95,6 +95,16 @@ def test_link(self, chat):
         chat.username = None
         assert chat.link is None
 
+    def test_anonymous_admin(self, chat):
+        assert chat.is_anonymous_admin is False
+        chat.id = constants.ANONYMOUS_ADMIN_ID
+        assert chat.is_anonymous_admin
+
+    def test_service_chat(self, chat):
+        assert chat.is_service_chat is False
+        chat.id = constants.SERVICE_CHAT_ID
+        assert chat.is_service_chat
+
     def test_send_action(self, monkeypatch, chat):
         def test(*args, **kwargs):
             id_ = args[0] == chat.id
diff --git a/tests/test_user.py b/tests/test_user.py
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -18,7 +18,7 @@
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import pytest
 
-from telegram import Update, User
+from telegram import Update, User, constants
 from telegram.utils.helpers import escape_markdown
 
 
@@ -127,6 +127,16 @@ def test_link(self, user):
         user.username = None
         assert user.link is None
 
+    def test_anonymous_admin(self, user):
+        assert user.is_anonymous_admin is False
+        user.id = constants.ANONYMOUS_ADMIN_ID
+        assert user.is_anonymous_admin
+
+    def test_service_chat(self, user):
+        assert user.is_service_chat is False
+        user.id = constants.SERVICE_CHAT_ID
+        assert user.is_service_chat
+
     def test_get_profile_photos(self, monkeypatch, user):
         def test(*args, **kwargs):
             return args[0] == user.id