diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 49d4b20a42..52a03abae1 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -11,13 +11,13 @@ jobs: name: build-doc runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: "3.10" - name: Install dependencies AND Build Documentation env: diff --git a/docs/source/api/errors/index.rst b/docs/source/api/errors/index.rst index c3557b77e2..d197b41c2f 100644 --- a/docs/source/api/errors/index.rst +++ b/docs/source/api/errors/index.rst @@ -39,14 +39,14 @@ follow the usual *PascalCase* convention. .. admonition :: RPC Errors :class: tip - There isn't any official list of all possible RPC errors, so the list of known errors is provided on a best-effort basis. When new methods are available, the list may be lacking since we simply don't know what errors can raise from them. Pyrogram creates an `unknown_errors.txt` file in the root directory from where the `Client` is run. + There isn't any official list of all possible RPC errors, so the list of known errors is provided on a best-effort basis. When new methods are available, the list may be lacking since we simply don't know what errors can raise from them. Pyrogram creates an ``unknown_errors.txt`` file in the root directory from where the `Client` is run. .. admonition :: PLEASE DO NOT DO THIS .. tip:: - If you do not want this file to be created, set the `PYROGRAM_DONOT_LOG_UNKNOWN_ERRORS` environment variable before running the Pyrogram `Client`. + If you want the file to be created in a different location, set the ``PYROGRAM_LOG_UNKNOWN_ERRORS_FILENAME`` environment variable to an absolute file path of your choice. .. tip:: - If you want the file to be created in a different location, set the `PYROGRAM_LOG_UNKNOWN_ERRORS_FILENAME` to a file path of your choice. + If you do not want this file to be created, set the ``PYROGRAM_DONOT_LOG_UNKNOWN_ERRORS`` environment variable before running the Pyrogram `Client`. diff --git a/docs/source/conf.py b/docs/source/conf.py index 87a8d2faf7..ab952dcbd8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import inspect import os import subprocess import sys @@ -32,8 +33,11 @@ "HEAD", ]).decode("UTF-8").strip() +project_url = "https://github.com/TelegramPlayGround/Pyrogram" +# --- SETUP: Define your repository root --- +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) project = "pyrotgfork" -copyright = "2017-present, Dan" +copyright = "2017-2024, Dan" author = "Dan" version = f"{__version__} Layer {layer}" @@ -45,6 +49,7 @@ # "sphinx.ext.viewcode", "sphinx_copybutton", # "sphinx.ext.coverage", + "sphinx.ext.linkcode", "sphinx_llms_txt", ] @@ -158,3 +163,57 @@ "genindex", "modindex", ] + +def linkcode_resolve(domain, info): + """ + Determine the URL corresponding to Python object + """ + if domain != "py": + return None + if not info["module"]: + return None + + # Attempt to find the exact line numbers using the inspect module + module = sys.modules.get(info["module"]) + if module is None: + return None + + # Traverse the object tree to find the specific class/function + obj = module + for part in info["fullname"].split("."): + try: + obj = getattr(obj, part) + except AttributeError: + return None + + # --- Unwrap decorators to bypass sync.py wrappers --- + try: + obj = inspect.unwrap(obj) + except: + pass # If it can't be unwrapped, just proceed with the original object + + try: + # 1. Get the absolute path to the file locally + filepath = inspect.getsourcefile(obj) + if filepath is None: + return None + + # 2. Calculate the path relative to the root of your git repository + rel_filepath = os.path.relpath(filepath, start=REPO_ROOT) + + # Ensure forward slashes for the GitHub URL (important for Windows users) + rel_filepath = rel_filepath.replace(os.sep, "/") + + # 3. Get the line numbers + source, lineno = inspect.getsourcelines(obj) + + # Return the perfectly mapped GitHub URL + # Returns a link like: https://github.com/user/repo/blob/main/module.py#L10-L25 + return f"{project_url}/blob/{commit_id}/{rel_filepath}#L{lineno}-L{lineno + len(source) - 1}" + + except (TypeError, OSError, ValueError): + # Fails safely if source cannot be inspected or path calculation fails + return None + + # Fallback to just linking to the file if line numbers can't be resolved + return f"{project_url}/blob/{commit_id}/{filename}.py" diff --git a/docs/source/releases/changes-in-this-fork.rst b/docs/source/releases/changes-in-this-fork.rst index 94fe41b541..257b568820 100644 --- a/docs/source/releases/changes-in-this-fork.rst +++ b/docs/source/releases/changes-in-this-fork.rst @@ -1,7 +1,4 @@ -Changes in this Fork -===================== - .. admonition :: A Word of Warning :class: tip @@ -17,15 +14,15 @@ it can take advantage of new goodies! If you found any issue or have any suggestions, feel free to make `an issue `__ on github. -Breaking Changes in this Fork -============================== +.. admonition :: Breaking Changes in this Fork + :class: tip -- In :meth:`~pyrogram.Client.copy_message`, ``ValueError`` is raised instead of ``logging`` it. -- In :meth:`~pyrogram.Client.download_media`, if the message is a :obj:`~pyrogram.types.PaidMediaInfo` with more than one ``paid_media`` **and** ``idx`` was not specified, then a list of paths or binary file-like objects is returned. -- PR `#115 `_ This `change `_ breaks some usages with offset-naive and offset-aware datetimes. -- PR from upstream: `#1411 `_ without attribution. -- If you relied on internal types like ``import pyrogram.file_id`` OR ``import pyrogram.utils``, Then read this full document to know where `else `_ your code will break. -- :obj:`~pyrogram.types.InlineKeyboardButton` only accepts keyword arguments instead of positional arguments. + - In :meth:`~pyrogram.Client.copy_message`, ``ValueError`` is raised instead of ``logging`` it. + - In :meth:`~pyrogram.Client.download_media`, if the message is a :obj:`~pyrogram.types.PaidMediaInfo` with more than one ``paid_media`` **and** ``idx`` was not specified, then a list of paths or binary file-like objects is returned. + - PR `#115 `_ This `change `_ breaks some usages with offset-naive and offset-aware datetimes. + - PR from upstream: `#1411 `_ without attribution. + - If you relied on internal types like ``import pyrogram.file_id`` OR ``import pyrogram.utils``, Then read this full document to know where `else `_ your code will break. + - :obj:`~pyrogram.types.InlineKeyboardButton` only accepts keyword arguments instead of positional arguments. Changes in this Fork @@ -35,6 +32,7 @@ Changes in this Fork | Scheme layer used: 223 | +------------------------+ +- Added the method :meth:`~pyrogram.Client.send_message_draft`, allowing bots to stream partial messages to a user while being generated. (contributed by @sudo-py-dev in `#231 `__). - Added the :obj:`~pyrogram.types.MessageEntity` type :obj:`~pyrogram.enums.MessageEntityType.DATE_TIME`, allowing to show a formatted date and time to the user. - Added the fields ``chat_owner_left``, ``chat_owner_changed``, ``chat_has_protected_content_toggled`` and ``chat_has_protected_content_disable_requested`` to the class :obj:`~pyrogram.types.Message`. - Added the field ``can_edit_tag`` to the class :obj:`~pyrogram.types.ChatPermissions`. diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index f6346fd1b8..f96e91fe02 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -80,11 +80,11 @@ whole category of errors and be sure to also handle these unknown errors. .. admonition :: RPC Errors :class: tip - There isn't any official list of all possible RPC errors, so the list of known errors is provided on a best-effort basis. When new methods are available, the list may be lacking since we simply don't know what errors can raise from them. Pyrogram creates an `unknown_errors.txt` file in the root directory from where the `Client` is run. + There isn't any official list of all possible RPC errors, so the list of known errors is provided on a best-effort basis. When new methods are available, the list may be lacking since we simply don't know what errors can raise from them. Pyrogram creates an ``unknown_errors.txt`` file in the root directory from where the `Client` is run. .. admonition :: `... `__ - If you want the file to be created in a different location, set the ``PYROGRAM_LOG_UNKNOWN_ERRORS_FILENAME`` to an absolute file path of your choice. + If you want the file to be created in a different location, set the ``PYROGRAM_LOG_UNKNOWN_ERRORS_FILENAME`` environment variable to an absolute file path of your choice. Errors with Values diff --git a/docs/source/topics/message-identifiers.rst b/docs/source/topics/message-identifiers.rst index 5a66070803..e58e6c6497 100644 --- a/docs/source/topics/message-identifiers.rst +++ b/docs/source/topics/message-identifiers.rst @@ -40,11 +40,9 @@ Let's look at a concrete example. Every account and channel has just been created. This means everyone has a message counter of one. -First, User-A will sent a welcome message to both User-B and User-C:: +.. First, User-A will sent a welcome message to both User-B and User-C:: -.. User-A → User-B: Hey, welcome! - User-A → User-C: ¡Bienvenido! * For User-A, "Hey, welcome!" will have the message identifier 1. The message with "¡Bienvenido!" will have an ID of 2. @@ -57,11 +55,10 @@ First, User-A will sent a welcome message to both User-B and User-C:: "Hey, welcome!", 1, 1, "", "", "" "¡Bienvenido!", 2, "", 1, "", "" -Next, User-B and User-C will respond to User-A:: -.. - User-B → User-A: Thanks! +.. Next, User-B and User-C will respond to User-A:: + User-B → User-A: Thanks! User-C → User-A: Gracias :) .. csv-table:: Message identifiers @@ -74,9 +71,8 @@ Next, User-B and User-C will respond to User-A:: Notice how for each message, the counter goes up by one, and they are independent. -Let's see what happens when User-B sends a message to Group-S:: +.. Let's see what happens when User-B sends a message to Group-S:: -.. User-B → Group-S: Nice group .. csv-table:: Message identifiers @@ -92,9 +88,7 @@ While the message was sent to a different chat, the group itself doesn't have a The message identifiers are still unique for each account. The chat where the message was sent can be completely ignored. -Megagroups behave differently:: - -.. +.. Megagroups behave differently:: User-C → Group-M: Buen grupo .. csv-table:: Message identifiers diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index 80948aebc0..7b472385d2 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -160,13 +160,12 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the >Block quotation continued >The last line of the block quotation - **> - The expandable block quotation started right after the previous block quotation - It is separated from the previous block quotation by expandable syntax - Expandable block quotation continued - Hidden by default part of the expandable block quotation started - Expandable block quotation continued - The last line of the expandable block quotation with the expandability mark<** + **>The expandable block quotation started right after the previous block quotation + >It is separated from the previous block quotation by expandable syntax + >Expandable block quotation continued + >Hidden by default part of the expandable block quotation started + >Expandable block quotation continued + >The last line of the expandable block quotation with the expandability mark|| `inline fixed-width code` @@ -215,13 +214,12 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the ">Block quotation continued\n" ">The last line of the block quotation" - "**>\n" - "The expandable block quotation started right after the previous block quotation\n" - "It is separated from the previous block quotation by expandable syntax\n" - "Expandable block quotation continued\n" - "Hidden by default part of the expandable block quotation started\n" - "Expandable block quotation continued\n" - "The last line of the expandable block quotation with the expandability mark<**" + "**>The expandable block quotation started right after the previous block quotation\n" + ">It is separated from the previous block quotation by expandable syntax\n" + ">Expandable block quotation continued\n" + ">Hidden by default part of the expandable block quotation started\n" + ">Expandable block quotation continued\n" + ">The last line of the expandable block quotation with the expandability mark||" ), parse_mode=ParseMode.MARKDOWN diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index 070b65d4a8..cb7fdecaec 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -307,25 +307,25 @@ class FileIdCached: _encoded: typing.Optional[str] = None def __init__( - self, *, - major: int = MAJOR, - minor: int = MINOR, - file_type: FileType, - dc_id: int, - file_reference: bytes = b"", - url: str = None, - media_id: int = None, - access_hash: int = None, - volume_id: int = None, - thumbnail_source: ThumbnailSource = None, - thumbnail_file_type: FileType = None, - thumbnail_size: str = "", - secret: int = None, - local_id: int = None, - chat_id: int = None, - chat_access_hash: int = None, - sticker_set_id: int = None, - sticker_set_access_hash: int = None + self, *, + major: int = MAJOR, + minor: int = MINOR, + file_type: FileType, + dc_id: int, + file_reference: bytes = b"", + url: str = None, + media_id: int = None, + access_hash: int = None, + volume_id: int = None, + thumbnail_source: ThumbnailSource = None, + thumbnail_file_type: FileType = None, + thumbnail_size: str = "", + secret: int = None, + local_id: int = None, + chat_id: int = None, + chat_access_hash: int = None, + sticker_set_id: int = None, + sticker_set_access_hash: int = None ): self.major = major self.minor = minor @@ -416,7 +416,7 @@ def encode(self, *, major: int = None, minor: int = None): return self._encoded def __str__(self): - return str({k: v for k, v in self.__dict__.items() if v is not None}) + return str({k: v for k, v in self.__dict__.items() if k != "_encoded" and v is not None}) class FileUniqueType(IntEnum): @@ -477,12 +477,12 @@ class FileUniqueIdCached: _encoded: typing.Optional[str] = None def __init__( - self, *, - file_unique_type: FileUniqueType, - url: str = None, - media_id: int = None, - volume_id: int = None, - local_id: int = None + self, *, + file_unique_type: FileUniqueType, + url: str = None, + media_id: int = None, + volume_id: int = None, + local_id: int = None ): self.file_unique_type = file_unique_type self.url = url @@ -513,4 +513,4 @@ def encode(self): return self._encoded def __str__(self): - return str({k: v for k, v in self.__dict__.items() if v is not None}) + return str({k: v for k, v in self.__dict__.items() if k != "_encoded" and v is not None}) diff --git a/pyrogram/methods/advanced/invoke.py b/pyrogram/methods/advanced/invoke.py index 0d35bdb752..de06a0bfb3 100644 --- a/pyrogram/methods/advanced/invoke.py +++ b/pyrogram/methods/advanced/invoke.py @@ -68,7 +68,8 @@ async def invoke( ``RawType``: The raw type response generated by the query. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ if not self.is_connected: raise ConnectionError("Client has not been started yet") diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py index 6cb5da6697..c1a04f6738 100644 --- a/pyrogram/methods/advanced/resolve_peer.py +++ b/pyrogram/methods/advanced/resolve_peer.py @@ -53,6 +53,7 @@ async def resolve_peer( Raises: KeyError: In case the peer doesn't exist in the internal database. + """ if not self.is_connected: raise ConnectionError("Client has not been started yet") diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index f2bd4196f5..2b83b294de 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -90,7 +90,8 @@ async def save_file( ``InputFile``: On success, the uploaded file is returned in form of an InputFile object. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ if path is None: return None diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py index cc1fcf5453..19daf97682 100644 --- a/pyrogram/methods/auth/accept_terms_of_service.py +++ b/pyrogram/methods/auth/accept_terms_of_service.py @@ -32,6 +32,10 @@ async def accept_terms_of_service( Parameters: terms_of_service_id (``str``): The terms of service identifier. + + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ r = await self.invoke( raw.functions.help.AcceptTermsOfService( diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py index 39aa82fcc0..e5f0f5efbb 100644 --- a/pyrogram/methods/auth/check_password.py +++ b/pyrogram/methods/auth/check_password.py @@ -44,6 +44,8 @@ async def check_password( Raises: BadRequest: In case the password is invalid. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ r = await self.invoke( raw.functions.auth.CheckPassword( diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py index 612e064bb4..ae7d94f530 100644 --- a/pyrogram/methods/auth/connect.py +++ b/pyrogram/methods/auth/connect.py @@ -33,6 +33,7 @@ async def connect( Raises: ConnectionError: In case you try to connect an already connected client. + """ if self.is_connected: raise ConnectionError("Client is already connected") diff --git a/pyrogram/methods/auth/disconnect.py b/pyrogram/methods/auth/disconnect.py index daa07b8353..ee8893b832 100644 --- a/pyrogram/methods/auth/disconnect.py +++ b/pyrogram/methods/auth/disconnect.py @@ -26,8 +26,8 @@ async def disconnect( """Disconnect the client from Telegram servers. Raises: - ConnectionError: In case you try to disconnect an already disconnected client or in case you try to - disconnect a client that needs to be terminated first. + ConnectionError: In case you try to disconnect an already disconnected client or in case you try to disconnect a client that needs to be terminated first. + """ if not self.is_connected: raise ConnectionError("Client is already disconnected") diff --git a/pyrogram/methods/auth/get_active_sessions.py b/pyrogram/methods/auth/get_active_sessions.py index f09f50e7d5..53f0f10611 100644 --- a/pyrogram/methods/auth/get_active_sessions.py +++ b/pyrogram/methods/auth/get_active_sessions.py @@ -31,6 +31,9 @@ async def get_active_sessions( Returns: :obj:`~pyrogram.types.ActiveSessions`: On success, all the active sessions of the current user is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ r = await self.invoke( raw.functions.account.GetAuthorizations() diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py index a57f7c8075..1a717a5c90 100644 --- a/pyrogram/methods/auth/get_password_hint.py +++ b/pyrogram/methods/auth/get_password_hint.py @@ -34,5 +34,9 @@ async def get_password_hint( Returns: ``str``: On success, the password hint as string is returned. + + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ return (await self.invoke(raw.functions.account.GetPassword())).hint diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 7188b66817..ee7d49e04b 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -34,8 +34,8 @@ async def initialize( It will also load plugins and start the internal dispatcher. Raises: - ConnectionError: In case you try to initialize a disconnected client or in case you try to initialize an - already initialized client. + ConnectionError: In case you try to initialize a disconnected client or in case you try to initialize an already initialized client. + """ if not self.is_connected: raise ConnectionError("Can't initialize a disconnected client") diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py index 84b7db64cd..d28b3c6533 100644 --- a/pyrogram/methods/auth/log_out.py +++ b/pyrogram/methods/auth/log_out.py @@ -38,6 +38,9 @@ async def log_out( Returns: ``bool``: On success, True is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py index 0a34750752..d571da8514 100644 --- a/pyrogram/methods/auth/recover_password.py +++ b/pyrogram/methods/auth/recover_password.py @@ -44,6 +44,8 @@ async def recover_password( Raises: BadRequest: In case the recovery code is invalid. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ r = await self.invoke( raw.functions.auth.RecoverPassword( diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py index 9644ac4f54..6cc78c2a15 100644 --- a/pyrogram/methods/auth/resend_code.py +++ b/pyrogram/methods/auth/resend_code.py @@ -51,6 +51,8 @@ async def resend_code( Raises: BadRequest: In case the arguments are invalid. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ phone_number = phone_number.strip(" +") diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py index 92ffc99996..72673b028c 100644 --- a/pyrogram/methods/auth/send_code.py +++ b/pyrogram/methods/auth/send_code.py @@ -46,6 +46,8 @@ async def send_code( Raises: BadRequest: In case the phone number is invalid. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ phone_number = phone_number.strip(" +") diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py index 9d77f1cd37..a946d32f78 100644 --- a/pyrogram/methods/auth/sign_in.py +++ b/pyrogram/methods/auth/sign_in.py @@ -57,6 +57,8 @@ async def sign_in( Raises: BadRequest: In case the arguments are invalid. SessionPasswordNeeded: In case a password is needed to sign in. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ phone_number = phone_number.strip(" +") diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py index 09a2f28379..2123eaf239 100644 --- a/pyrogram/methods/auth/sign_in_bot.py +++ b/pyrogram/methods/auth/sign_in_bot.py @@ -45,6 +45,8 @@ async def sign_in_bot( Raises: BadRequest: In case the bot token is invalid. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ while True: try: diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py index eee64fdd3f..d8a551f9f2 100644 --- a/pyrogram/methods/auth/sign_up.py +++ b/pyrogram/methods/auth/sign_up.py @@ -55,6 +55,8 @@ async def sign_up( Raises: BadRequest: In case the arguments are invalid. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ phone_number = phone_number.strip(" +") diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index d5fd949cba..ec5b891944 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -35,6 +35,7 @@ async def terminate( Raises: ConnectionError: In case you try to terminate a client that is already terminated. + """ if not self.is_initialized: raise ConnectionError("Client is already terminated") diff --git a/pyrogram/methods/auth/terminate_all_other_sessions.py b/pyrogram/methods/auth/terminate_all_other_sessions.py index 027ea3cc3b..0aa4a2d043 100644 --- a/pyrogram/methods/auth/terminate_all_other_sessions.py +++ b/pyrogram/methods/auth/terminate_all_other_sessions.py @@ -32,7 +32,7 @@ async def terminate_all_other_sessions( ``bool``: On success, in case the session is destroyed, True is returned. Otherwise, False is returned. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ return await self.invoke( diff --git a/pyrogram/methods/auth/terminate_session.py b/pyrogram/methods/auth/terminate_session.py index 90ddaf7a70..50364e57eb 100644 --- a/pyrogram/methods/auth/terminate_session.py +++ b/pyrogram/methods/auth/terminate_session.py @@ -37,7 +37,7 @@ async def terminate_session( ``bool``: On success, in case the session is destroyed, True is returned. Otherwise, False is returned. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ return await self.invoke( diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py index a6d8747cd5..a476488e37 100644 --- a/pyrogram/methods/bots/answer_callback_query.py +++ b/pyrogram/methods/bots/answer_callback_query.py @@ -58,6 +58,9 @@ async def answer_callback_query( Returns: ``bool``: True, on success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py index c3a450a015..c0bb4a96db 100644 --- a/pyrogram/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -83,6 +83,9 @@ async def answer_inline_query( Returns: ``bool``: True, on success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/answer_web_app_query.py b/pyrogram/methods/bots/answer_web_app_query.py index 74f56079bb..c9e2eea6f4 100644 --- a/pyrogram/methods/bots/answer_web_app_query.py +++ b/pyrogram/methods/bots/answer_web_app_query.py @@ -41,6 +41,10 @@ async def answer_web_app_query( Returns: :obj:`~pyrogram.types.SentWebAppMessage`: On success the sent web app message is returned. + + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ r = await self.invoke( diff --git a/pyrogram/methods/bots/delete_bot_commands.py b/pyrogram/methods/bots/delete_bot_commands.py index e8173d32d9..562b2d7272 100644 --- a/pyrogram/methods/bots/delete_bot_commands.py +++ b/pyrogram/methods/bots/delete_bot_commands.py @@ -47,6 +47,9 @@ async def delete_bot_commands( Returns: ``bool``: On success, True is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_bot_commands.py b/pyrogram/methods/bots/get_bot_commands.py index ed0c2848ae..9249c06e82 100644 --- a/pyrogram/methods/bots/get_bot_commands.py +++ b/pyrogram/methods/bots/get_bot_commands.py @@ -47,6 +47,9 @@ async def get_bot_commands( Returns: List of :obj:`~pyrogram.types.BotCommand`: On success, the list of bot commands is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_bot_default_privileges.py b/pyrogram/methods/bots/get_bot_default_privileges.py index 217d9b4384..5d794442b6 100644 --- a/pyrogram/methods/bots/get_bot_default_privileges.py +++ b/pyrogram/methods/bots/get_bot_default_privileges.py @@ -40,6 +40,9 @@ async def get_bot_default_privileges( Returns: ``bool``: On success, True is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_bot_info_description.py b/pyrogram/methods/bots/get_bot_info_description.py index 739e6d60dc..f22b4cd948 100644 --- a/pyrogram/methods/bots/get_bot_info_description.py +++ b/pyrogram/methods/bots/get_bot_info_description.py @@ -47,6 +47,9 @@ async def get_bot_info_description( Returns: ``str``: On success, returns the text shown in the chat with a bot if the chat is empty in the given language. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_bot_info_short_description.py b/pyrogram/methods/bots/get_bot_info_short_description.py index c78eb186c5..7b709e3d33 100644 --- a/pyrogram/methods/bots/get_bot_info_short_description.py +++ b/pyrogram/methods/bots/get_bot_info_short_description.py @@ -47,6 +47,9 @@ async def get_bot_info_short_description( Returns: ``str``: On success, returns the text shown on a bot's profile page and sent together with the link when users share the bot in the given language. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_bot_name.py b/pyrogram/methods/bots/get_bot_name.py index e2775b89fc..6408ecf63a 100644 --- a/pyrogram/methods/bots/get_bot_name.py +++ b/pyrogram/methods/bots/get_bot_name.py @@ -44,6 +44,9 @@ async def get_bot_name( Unique identifier (int) or username (str) of the bot for which profile photo has to be updated instead of the current user. The bot should have ``can_be_edited`` property set to True. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Returns: ``str``: On success, returns the name of a bot in the given language. diff --git a/pyrogram/methods/bots/get_chat_menu_button.py b/pyrogram/methods/bots/get_chat_menu_button.py index 9d143d5819..815b0428b3 100644 --- a/pyrogram/methods/bots/get_chat_menu_button.py +++ b/pyrogram/methods/bots/get_chat_menu_button.py @@ -36,6 +36,10 @@ async def get_chat_menu_button( chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. If not specified, default bot's menu button will be returned. + + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ if chat_id: diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py index 17445edd09..b581334786 100644 --- a/pyrogram/methods/bots/get_game_high_scores.py +++ b/pyrogram/methods/bots/get_game_high_scores.py @@ -53,6 +53,9 @@ async def get_game_high_scores( Returns: List of :obj:`~pyrogram.types.GameHighScore`: On success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py index bbde018e2d..cae9f1bf12 100644 --- a/pyrogram/methods/bots/get_inline_bot_results.py +++ b/pyrogram/methods/bots/get_inline_bot_results.py @@ -69,6 +69,7 @@ async def get_inline_bot_results( Raises: TimeoutError: In case the bot fails to answer within 10 seconds. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_owned_bots.py b/pyrogram/methods/bots/get_owned_bots.py index 884ec248cb..4a2a57518f 100644 --- a/pyrogram/methods/bots/get_owned_bots.py +++ b/pyrogram/methods/bots/get_owned_bots.py @@ -31,6 +31,9 @@ async def get_owned_bots( Returns: List of :obj:`~pyrogram.types.User`: On success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/get_similar_bots.py b/pyrogram/methods/bots/get_similar_bots.py index 461fb5c4a2..a8dde77764 100644 --- a/pyrogram/methods/bots/get_similar_bots.py +++ b/pyrogram/methods/bots/get_similar_bots.py @@ -38,6 +38,9 @@ async def get_similar_bots( Returns: List of :obj:`~pyrogram.types.User`: On success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py index 8dd3b8905c..bddba1639e 100644 --- a/pyrogram/methods/bots/request_callback_answer.py +++ b/pyrogram/methods/bots/request_callback_answer.py @@ -62,7 +62,7 @@ async def request_callback_answer( Raises: TimeoutError: In case the bot fails to answer within 10 seconds. ValueError: In case of invalid arguments. - RPCError: In case of Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py index f659bb0cd3..0633ab0ea8 100644 --- a/pyrogram/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -99,6 +99,9 @@ async def send_game( Returns: :obj:`~pyrogram.types.Message`: On success, the sent game message is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index 06fda3bd8b..1e00bf6f11 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -84,7 +84,7 @@ async def send_inline_bot_result( :obj:`~pyrogram.types.Message`: On success, the sent message is returned or False if no message was sent. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/bots/send_web_app_custom_request.py b/pyrogram/methods/bots/send_web_app_custom_request.py index 8bf1a950b5..68b8998478 100644 --- a/pyrogram/methods/bots/send_web_app_custom_request.py +++ b/pyrogram/methods/bots/send_web_app_custom_request.py @@ -46,6 +46,10 @@ async def send_web_app_custom_request( Returns: ``str``: On success, a JSON-serialized result is returned. + + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ r = await self.invoke( diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py index 29cacf1c86..d5f083f403 100644 --- a/pyrogram/methods/bots/set_bot_commands.py +++ b/pyrogram/methods/bots/set_bot_commands.py @@ -51,6 +51,9 @@ async def set_bot_commands( Returns: ``bool``: On success, True is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/set_bot_default_privileges.py b/pyrogram/methods/bots/set_bot_default_privileges.py index 2890ee1ae1..1a3548bdfe 100644 --- a/pyrogram/methods/bots/set_bot_default_privileges.py +++ b/pyrogram/methods/bots/set_bot_default_privileges.py @@ -45,6 +45,9 @@ async def set_bot_default_privileges( Returns: ``bool``: On success, True is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python @@ -77,5 +80,6 @@ async def set_bot_default_privileges( manage_call=privileges.can_manage_video_chats, other=privileges.can_manage_chat ) if privileges else raw.types.ChatAdminRights() + # TODO return await self.invoke(function(admin_rights=admin_rights)) diff --git a/pyrogram/methods/bots/set_bot_info_description.py b/pyrogram/methods/bots/set_bot_info_description.py index 6be130aca4..a59b4090e9 100644 --- a/pyrogram/methods/bots/set_bot_info_description.py +++ b/pyrogram/methods/bots/set_bot_info_description.py @@ -51,6 +51,9 @@ async def set_bot_info_description( Returns: ``bool``: True on success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/set_bot_info_short_description.py b/pyrogram/methods/bots/set_bot_info_short_description.py index 5a3903d55a..5661a43354 100644 --- a/pyrogram/methods/bots/set_bot_info_short_description.py +++ b/pyrogram/methods/bots/set_bot_info_short_description.py @@ -51,6 +51,9 @@ async def set_bot_info_short_description( Returns: ``bool``: True on success. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/bots/set_bot_name.py b/pyrogram/methods/bots/set_bot_name.py index 34fd2fffeb..a6195788a6 100644 --- a/pyrogram/methods/bots/set_bot_name.py +++ b/pyrogram/methods/bots/set_bot_name.py @@ -48,6 +48,9 @@ async def set_bot_name( Unique identifier (int) or username (str) of the bot for which profile photo has to be updated instead of the current user. The bot should have ``can_be_edited`` property set to True. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Returns: ``bool``: True on success. diff --git a/pyrogram/methods/bots/set_chat_menu_button.py b/pyrogram/methods/bots/set_chat_menu_button.py index fa5af85ceb..67bbfe2b7a 100644 --- a/pyrogram/methods/bots/set_chat_menu_button.py +++ b/pyrogram/methods/bots/set_chat_menu_button.py @@ -41,6 +41,10 @@ async def set_chat_menu_button( menu_button (:obj:`~pyrogram.types.MenuButton`, *optional*): The new bot's menu button. Defaults to :obj:`~pyrogram.types.MenuButtonDefault`. + + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + """ await self.invoke( diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py index 020e17293d..4dfbfde392 100644 --- a/pyrogram/methods/bots/set_game_score.py +++ b/pyrogram/methods/bots/set_game_score.py @@ -68,6 +68,9 @@ async def set_game_score( :obj:`~pyrogram.types.Message` | ``bool``: On success, if the message was sent by the bot, the edited message is returned, True otherwise. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/chat_topics/toggle_forum_topic_is_pinned.py b/pyrogram/methods/chat_topics/toggle_forum_topic_is_pinned.py index f07558fae3..91ab6a077e 100644 --- a/pyrogram/methods/chat_topics/toggle_forum_topic_is_pinned.py +++ b/pyrogram/methods/chat_topics/toggle_forum_topic_is_pinned.py @@ -46,7 +46,7 @@ async def toggle_forum_topic_is_pinned( ``bool``: On success, True is returned. Raises: - RPCError: In case of invalid arguments. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/chats/add_profile_audio.py b/pyrogram/methods/chats/add_profile_audio.py index 8b7ebc9e55..dcff9d7fb3 100644 --- a/pyrogram/methods/chats/add_profile_audio.py +++ b/pyrogram/methods/chats/add_profile_audio.py @@ -40,7 +40,7 @@ async def add_profile_audio( ``bool``: On success, True is returned. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/chats/set_chat_direct_messages_group.py b/pyrogram/methods/chats/set_chat_direct_messages_group.py index 311a555c7e..18f250c57e 100644 --- a/pyrogram/methods/chats/set_chat_direct_messages_group.py +++ b/pyrogram/methods/chats/set_chat_direct_messages_group.py @@ -50,7 +50,7 @@ async def set_chat_direct_messages_group( otherwise, in case a message object couldn't be returned, True is returned. Raises: - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/chats/set_chat_protected_content.py b/pyrogram/methods/chats/set_chat_protected_content.py index e2e43a2663..1ac8c7b21a 100644 --- a/pyrogram/methods/chats/set_chat_protected_content.py +++ b/pyrogram/methods/chats/set_chat_protected_content.py @@ -49,7 +49,7 @@ async def set_chat_protected_content( otherwise, in case a message object couldn't be returned, True is returned. Raises: - RPCError: In case of a Telegram RPCError. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ r = await self.invoke( @@ -95,7 +95,7 @@ async def process_chat_protected_content_disable_request( otherwise, in case a message object couldn't be returned, True is returned. Raises: - RPCError: In case of a Telegram RPCError. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ r = await self.invoke( diff --git a/pyrogram/methods/chats/transfer_chat_ownership.py b/pyrogram/methods/chats/transfer_chat_ownership.py index e61406c59e..be50067f16 100644 --- a/pyrogram/methods/chats/transfer_chat_ownership.py +++ b/pyrogram/methods/chats/transfer_chat_ownership.py @@ -52,7 +52,7 @@ async def transfer_chat_ownership( Raises: ValueError: In case of invalid parameters. - RPCError: In case of a Telegram RPC error. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 4458002967..90b802ebf2 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -131,8 +131,8 @@ async def copy_message( :obj:`~pyrogram.types.Message`: On success, the copied message is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case if an invalid message_id was provided. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: .. code-block:: python diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index 7e1157c764..ad9f51ef0d 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -117,9 +117,9 @@ async def download_media( If the message is a :obj:`~pyrogram.types.PaidMediaInfo` with more than one ``paid_media`` containing ``minithumbnail`` and ``idx`` is not specified, then a list of paths or binary file-like objects is returned. Raises: - RPCError: In case of a Telegram RPC error. IndexError: In case of wrong value of ``idx``. ValueError: If the message doesn't contain any downloadable media. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. Example: Download media to file diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index db86abd207..fcf2c0790d 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -105,6 +105,9 @@ async def forward_messages( :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was not a list, a single message is returned, otherwise a list of messages is returned. + Raises: + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. + Example: .. code-block:: python diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py index 9968a92347..89931c1f2b 100644 --- a/pyrogram/methods/messages/get_custom_emoji_stickers.py +++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py @@ -23,14 +23,14 @@ class GetCustomEmojiStickers: async def get_custom_emoji_stickers( self: "pyrogram.Client", - custom_emoji_ids: list[int], + custom_emoji_ids: list[str], ) -> list["types.Sticker"]: """Get information about custom emoji stickers by their identifiers. .. include:: /_includes/usable-by/users-bots.rst Parameters: - custom_emoji_ids (List of ``int``): + custom_emoji_ids (List of ``str``): List of custom emoji identifiers. At most 200 custom emoji identifiers can be specified. @@ -39,7 +39,7 @@ async def get_custom_emoji_stickers( """ result = await self.invoke( raw.functions.messages.GetCustomEmojiDocuments( - document_id=custom_emoji_ids + document_id=[int(document_id) for document_id in custom_emoji_ids] ) ) diff --git a/pyrogram/methods/stories/edit_story.py b/pyrogram/methods/stories/edit_story.py index 437822efc2..7947eacc62 100644 --- a/pyrogram/methods/stories/edit_story.py +++ b/pyrogram/methods/stories/edit_story.py @@ -95,7 +95,7 @@ async def edit_story( Raises: ValueError: In case of invalid arguments. - RPCError: In case of Telegram RPCError. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ @@ -256,7 +256,7 @@ async def edit_business_story( Raises: ValueError: In case of invalid arguments. - RPCError: In case of Telegram RPCError. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ if not business_connection_id: diff --git a/pyrogram/methods/stories/hide_my_story_view.py b/pyrogram/methods/stories/hide_my_story_view.py index 8d21b85cb7..69f1b16d9c 100644 --- a/pyrogram/methods/stories/hide_my_story_view.py +++ b/pyrogram/methods/stories/hide_my_story_view.py @@ -49,7 +49,7 @@ async def hide_my_story_view( await app.hide_my_story_view() Raises: - RPCError: In case of Telegram RPCError. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ diff --git a/pyrogram/methods/stories/post_story.py b/pyrogram/methods/stories/post_story.py index bb6e66bdc3..a7e7161357 100644 --- a/pyrogram/methods/stories/post_story.py +++ b/pyrogram/methods/stories/post_story.py @@ -124,7 +124,7 @@ async def post_story( Raises: ValueError: In case of invalid arguments. - RPCError: In case of Telegram RPCError. + :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error. """ if business_connection_id: diff --git a/pyrogram/methods/users/set_emoji_status.py b/pyrogram/methods/users/set_emoji_status.py index 2d8c77cc02..ffa852af71 100644 --- a/pyrogram/methods/users/set_emoji_status.py +++ b/pyrogram/methods/users/set_emoji_status.py @@ -43,7 +43,8 @@ async def set_emoji_status( from pyrogram import types - await app.set_emoji_status(types.EmojiStatus(custom_emoji_id=1234567890987654321)) + await app.set_emoji_status(types.EmojiStatus(custom_emoji_id="1234567890987654321")) + """ await self.invoke( raw.functions.account.UpdateEmojiStatus( diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 168c3524e2..773775bf9f 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -210,11 +210,13 @@ def parse_one(entity): elif entity_type in ( MessageEntityType.CODE, MessageEntityType.BLOCKQUOTE, - MessageEntityType.SPOILER, ): name = entity_type.name.lower() start_tag = f"<{name}>" end_tag = f"" + elif entity_type == MessageEntityType.SPOILER: + start_tag = "" + end_tag = "" elif entity_type == MessageEntityType.TEXT_LINK: url = entity.url start_tag = f'' diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index 15c60c1b57..9b90a4d669 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -39,7 +39,7 @@ BLOCKQUOTE_DELIM = ">" BLOCKQUOTE_ESCAPE_DELIM = "|>" BLOCKQUOTE_EXPANDABLE_DELIM = "**>" -BLOCKQUOTE_EXPANDABLE_OPTIONAL_END_DELIM = "<**" +BLOCKQUOTE_EXPANDABLE_OPTIONAL_END_DELIM = "<**" # Kept for backwards compatibility if imported elsewhere MARKDOWN_RE = re.compile( r"({d})|(!?)\[(.+?)\]\((.+?)\)".format( @@ -81,134 +81,156 @@ def __init__(self, client: Optional["pyrogram.Client"]): @staticmethod def escape_and_create_quotes(text: str, strict: bool): text_lines: list[Union[str, None]] = text.splitlines() - - # Indexes of Already escaped lines html_escaped_list: list[int] = [] - # Temporary Queue to hold lines to be quoted - # Index and Line - to_quote_list: list[tuple[int, str]] = [] - - def create_blockquote(quote_type: str = "") -> None: - """ - Merges all lines in quote_queue into first line of queue - Encloses that line in html quote - Replaces rest of the lines with None placeholders to preserve indexes - """ - if len(to_quote_list) == 0: - return - - # Create quoted text block - joined_lines = "\n".join([text for _, text in to_quote_list]) - - first_line_index, _ = to_quote_list[0] - - # Enclose the block in html quote - # and add to starting index of quoted line - text_lines[first_line_index] = f"{joined_lines}" - - # Set None Placeholders for preserving indexes - for idx, line_to_remove in to_quote_list[1:]: - text_lines[idx] = None - - # clear queue - to_quote_list.clear() - - def process_text(start_delimiter, end_delimiter: str = "", quote_type: str = ""): - for index, line in enumerate(text_lines): - # Ignore None placeholders from previous runs - if line is None: - continue - - # Ignore Escaped > - if line.startswith(BLOCKQUOTE_ESCAPE_DELIM): - text_lines[index] = line[1:] - create_blockquote(quote_type=quote_type) - continue - - # Parse lines starting with delimiter - if line.startswith(start_delimiter): - endswith_delimiter = end_delimiter and line.endswith(end_delimiter) - - # Indexes to skip in line - start_index = len(start_delimiter) - end_index = end_index = len(line) - len(end_delimiter) if endswith_delimiter else len(line) - - # Strip delimiters - delimiter_stripped_line = line[start_index:end_index] - - # Escape if strict - parsed_line = html.escape(delimiter_stripped_line) if strict else delimiter_stripped_line - - # add to queue - to_quote_list.append((index, parsed_line)) + i = 0 + while i < len(text_lines): + line = text_lines[i] - # save line index - html_escaped_list.append(index) - - # if line doesn't end with delimiter continue loop - if not endswith_delimiter: - continue + if line is None: + i += 1 + continue - # If line doesn't start with a delimiter - # or has ended with delimiter - # it means the block quote has ended - # create pending quotes if any - create_blockquote(quote_type=quote_type) + # Ignore Escaped > + if line.startswith(BLOCKQUOTE_ESCAPE_DELIM): + text_lines[i] = html.escape(line[1:]) if strict else line[1:] + html_escaped_list.append(i) + i += 1 + continue + # Check if line starts a blockquote + is_bq = False + started_as_expandable = False + + if line.startswith(BLOCKQUOTE_EXPANDABLE_DELIM): + is_bq = True + started_as_expandable = True + elif line.startswith(BLOCKQUOTE_DELIM): + is_bq = True + + if is_bq: + start_index = i + bq_lines = [] + + # Collect all consecutive blockquote lines + while i < len(text_lines): + curr_line = text_lines[i] + + # Detect boundaries between consecutive blockquotes + if i > start_index: + # Boundary 1: `**>` explicitly starts a NEW expandable blockquote + if curr_line.startswith(BLOCKQUOTE_EXPANDABLE_DELIM): + break + + curr_prefix_len = 0 + if curr_line.startswith(BLOCKQUOTE_EXPANDABLE_DELIM): + curr_prefix_len = len(BLOCKQUOTE_EXPANDABLE_DELIM) + elif curr_line.startswith(BLOCKQUOTE_DELIM): + curr_prefix_len = len(BLOCKQUOTE_DELIM) + else: + break # No longer in a blockquote + + # Strip the delimiter + bq_lines.append(curr_line[curr_prefix_len:]) + i += 1 + + # Check if it properly closes as an expandable blockquote + is_expandable = False + # Strict Bot API requirement: Must have started with **> AND end with || + if started_as_expandable and bq_lines and bq_lines[-1].endswith(SPOILER_DELIM): + is_expandable = True + # Strip the || from the final line + bq_lines[-1] = bq_lines[-1][:-len(SPOILER_DELIM)] + + # Escape if strict + if strict: + bq_lines = [html.escape(l) for l in bq_lines] + + # Create the merged blockquote entity + joined_lines = "\n".join(bq_lines) + quote_type = " expandable" if is_expandable else "" + + text_lines[start_index] = f"{joined_lines}" + html_escaped_list.append(start_index) + + # Clear out the consumed lines + for j in range(start_index + 1, i): + text_lines[j] = None else: - # is triggered when there's only one line of text - # the line above won't be triggered - # because loop will exit after first iteration - # so try to create quote if any in queue - create_blockquote(quote_type=quote_type) - - process_text( - start_delimiter=BLOCKQUOTE_EXPANDABLE_DELIM, - end_delimiter=BLOCKQUOTE_EXPANDABLE_OPTIONAL_END_DELIM, - quote_type=" expandable", - ) - process_text(start_delimiter=BLOCKQUOTE_DELIM) + i += 1 + # Escape remaining text lines if strict if strict: for idx, line in enumerate(text_lines): - if idx not in html_escaped_list: + if line is not None and idx not in html_escaped_list: text_lines[idx] = html.escape(line) return "\n".join(filter(lambda x: x is not None, text_lines)) async def parse(self, text: str, strict: bool = False): text = self.escape_and_create_quotes(text, strict=strict) + + matches = list(re.finditer(MARKDOWN_RE, text)) + valid_delims = set() + opened = {} + active_fixed_width = None + + # --- Pass 1: Identify paired delimiters --- + for i, match in enumerate(matches): + delim, is_emoji_or_date, text_url, url = match.groups() + + if not delim: + continue + + # If we are inside a code block, ignore all other formatting + if active_fixed_width: + if delim == active_fixed_width: + # Closing the code block + valid_delims.add(opened[delim]) + valid_delims.add(i) + del opened[delim] + active_fixed_width = None + continue + + # Opening a new code block + if delim in FIXED_WIDTH_DELIMS: + active_fixed_width = delim + opened[delim] = i + continue + + # Standard formatting delimiters + if delim in [BOLD_DELIM, ITALIC_DELIM, UNDERLINE_DELIM, STRIKE_DELIM, SPOILER_DELIM]: + if delim not in opened: + opened[delim] = i + else: + # Valid pair found! + valid_delims.add(opened[delim]) + valid_delims.add(i) + del opened[delim] + + # --- Pass 2: Apply replacements --- delims = set() - is_fixed_width = False - - for i, match in enumerate(re.finditer(MARKDOWN_RE, text)): + + for i, match in enumerate(matches): start, _ = match.span() delim, is_emoji_or_date, text_url, url = match.groups() full = match.group(0) - if delim in FIXED_WIDTH_DELIMS: - is_fixed_width = not is_fixed_width - - if is_fixed_width and delim not in FIXED_WIDTH_DELIMS: - continue - + # 1. Handle Links if not is_emoji_or_date and text_url: text = utils.replace_once(text, full, URL_MARKUP.format(url, text_url), start) continue + # 2. Handle Emojis and Dates if is_emoji_or_date: emoji = text_url - parsed_url = urllib.parse.urlparse(url) # Parse the query parameters into a dictionary-like object query_params = urllib.parse.parse_qs(parsed_url.query) - # Branch 1: Custom Emoji if parsed_url.netloc == "emoji": emoji_id = query_params.get("id", ["0"])[0] text = utils.replace_once(text, full, EMOJI_MARKUP.format(emoji_id, emoji), start) - # Branch 2: Custom Time elif parsed_url.netloc == "time": unix_time = query_params.get("unix", ["0"])[0] @@ -219,39 +241,46 @@ async def parse(self, text: str, strict: bool = False): text = utils.replace_once(text, full, DATE_TIME_MARKUP.format(unix_time, emoji), start) continue - if delim == BOLD_DELIM: - tag = "b" - elif delim == ITALIC_DELIM: - tag = "i" - elif delim == UNDERLINE_DELIM: - tag = "u" - elif delim == STRIKE_DELIM: - tag = "s" - elif delim == CODE_DELIM: - tag = "code" - elif delim == PRE_DELIM: - tag = "pre" - elif delim == SPOILER_DELIM: - tag = "spoiler" - else: - continue + # 3. Handle Formatting Delimiters + if delim: + # If this delimiter is unclosed (or suppressed by a code block), leave it as literal text! + if i not in valid_delims: + continue + + if delim == BOLD_DELIM: + tag = "b" + elif delim == ITALIC_DELIM: + tag = "i" + elif delim == UNDERLINE_DELIM: + tag = "u" + elif delim == STRIKE_DELIM: + tag = "s" + elif delim == CODE_DELIM: + tag = "code" + elif delim == PRE_DELIM: + tag = "pre" + elif delim == SPOILER_DELIM: + tag = "spoiler" + else: + continue - if delim not in delims: - delims.add(delim) - tag = OPENING_TAG.format(tag) - else: - delims.remove(delim) - tag = CLOSING_TAG.format(tag) - - if delim == PRE_DELIM and delim in delims: - delim_and_language = text[text.find(PRE_DELIM) :].split("\n")[0] - language = delim_and_language[len(PRE_DELIM) :] - text = utils.replace_once( - text, delim_and_language, f'
', start
-                )
-                continue
+                if delim not in delims:
+                    delims.add(delim)
+                    tag = OPENING_TAG.format(tag)
+                else:
+                    delims.remove(delim)
+                    tag = CLOSING_TAG.format(tag)
+
+                # Special handling for PRE language definition
+                if delim == PRE_DELIM and delim in delims:
+                    delim_and_language = text[text.find(PRE_DELIM) :].split("\n")[0]
+                    language = delim_and_language[len(PRE_DELIM) :]
+                    text = utils.replace_once(
+                        text, delim_and_language, f'
', start
+                    )
+                    continue
 
-            text = utils.replace_once(text, delim, tag, start)
+                text = utils.replace_once(text, delim, tag, start)
 
         return await self.html.parse(text)
 
@@ -293,27 +322,16 @@ def unparse(text: str, entities: list):
                         and blk_entity.offset < e <= blk_entity.offset + blk_entity.length
                         for blk_entity in entities
                         if blk_entity.type == MessageEntityType.BLOCKQUOTE
+                        or blk_entity.type == MessageEntityType.EXPANDABLE_BLOCKQUOTE
                     )
-                    is_expandable = any(
-                        blk_entity.offset <= s < blk_entity.offset + blk_entity.length
-                        and blk_entity.offset < e <= blk_entity.offset + blk_entity.length
-                        # and blk_entity.collapsed
-                        for blk_entity in entities
-                        if blk_entity.type == MessageEntityType.EXPANDABLE_BLOCKQUOTE
-                    )
+                    
                     if inside_blockquote:
-                        if is_expandable:
-                            if entity.language:
-                                open_delimiter = f"{delimiter}{entity.language}\n**>"
-                            else:
-                                open_delimiter = f"{delimiter}\n**>"
-                            close_delimiter = f"\n**>{delimiter}"
+                        # Inside any blockquote, inner lines use ">"
+                        if entity.language:
+                            open_delimiter = f"{delimiter}{entity.language}\n>"
                         else:
-                            if entity.language:
-                                open_delimiter = f"{delimiter}{entity.language}\n>"
-                            else:
-                                open_delimiter = f"{delimiter}\n>"
-                            close_delimiter = f"\n>{delimiter}"
+                            open_delimiter = f"{delimiter}\n>"
+                        close_delimiter = f"\n>{delimiter}"
                     else:
                         if entity.language:
                             open_delimiter = f"{delimiter}{entity.language}\n"
@@ -336,11 +354,27 @@ def unparse(text: str, entities: list):
                     lines = text_subset.splitlines()
                     for line_num, line in enumerate(lines):
                         line_start = s + sum(len(l) + 1 for l in lines[:line_num])
+                        
+                        # Blockquote prefixes MUST be placed at the absolute start of the line.
+                        # We use `-10000 + i` to ensure they are popped last and end up on the far left,
+                        # wrapping perfectly around any inline entities sharing the same offset.
+                        prefix_priority = -10000 + i
+                        
                         if entity.type == MessageEntityType.EXPANDABLE_BLOCKQUOTE:
-                            insert_at.append((line_start, i, BLOCKQUOTE_EXPANDABLE_DELIM))
+                            if line_num == 0:
+                                insert_at.append((line_start, prefix_priority, BLOCKQUOTE_EXPANDABLE_DELIM))
+                            else:
+                                insert_at.append((line_start, prefix_priority, BLOCKQUOTE_DELIM))
                         else:
-                            insert_at.append((line_start, i, BLOCKQUOTE_DELIM))
-            # No closing delimiter for blockquotes
+                            insert_at.append((line_start, prefix_priority, BLOCKQUOTE_DELIM))
+
+                    # Append expandability mark for expandable blockquotes
+                    if entity.type == MessageEntityType.EXPANDABLE_BLOCKQUOTE:
+                        # Expandability marks MUST be at the absolute end of the blockquote.
+                        # We use `10000 - i` to ensure it is popped first and ends up on the far right.
+                        insert_at.append((e, 10000 - i, SPOILER_DELIM))
+
+            # No closing delimiter for blockquotes (handled by lines above)
             else:
                 url = None
                 is_emoji_or_date = False
diff --git a/pyrogram/types/authorization/active_session.py b/pyrogram/types/authorization/active_session.py
index 7de247e725..fe9092a092 100644
--- a/pyrogram/types/authorization/active_session.py
+++ b/pyrogram/types/authorization/active_session.py
@@ -174,7 +174,7 @@ async def terminate(self):
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
 
diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py
index 05cb7051a5..46db13c200 100644
--- a/pyrogram/types/bots_and_keyboards/callback_query.py
+++ b/pyrogram/types/bots_and_keyboards/callback_query.py
@@ -239,7 +239,8 @@ async def edit_message_text(
             message is returned, otherwise True is returned (message sent via the bot, as inline query result).
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         if self.inline_message_id is None:
             return await self._client.edit_message_text(
@@ -294,7 +295,8 @@ async def edit_message_caption(
             message is returned, otherwise True is returned (message sent via the bot, as inline query result).
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self.edit_message_text(
             text=caption,
@@ -329,7 +331,8 @@ async def edit_message_media(
             message is returned, otherwise True is returned (message sent via the bot, as inline query result).
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         if self.inline_message_id is None:
             return await self._client.edit_message_media(
@@ -364,7 +367,8 @@ async def edit_message_reply_markup(
             message is returned, otherwise True is returned (message sent via the bot, as inline query result).
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         if self.inline_message_id is None:
             return await self._client.edit_message_reply_markup(
diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
index 6753ae582b..b3319b8b37 100644
--- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
@@ -32,7 +32,7 @@ class InlineKeyboardButton(Object):
         text (``str``):
             Label text on the button.
 
-        icon_custom_emoji_id (``int``, *optional*):
+        icon_custom_emoji_id (``str``, *optional*):
             Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription..
 
         style (:obj:`~pyrogram.enums.ButtonStyle`, *optional*):
@@ -99,7 +99,10 @@ class InlineKeyboardButton(Object):
 
     def __init__(
         self,
-        text: str, *,
+        text: str,
+        icon_custom_emoji_id: Optional[str] = None,
+        style: Optional["enums.ButtonStyle"] = enums.ButtonStyle.DEFAULT,
+        *,
         url: Optional[str] = None,
         user_id: Optional[int] = None,
         callback_data: Optional[Union[str, bytes]] = None,
@@ -112,8 +115,6 @@ def __init__(
         callback_game: Optional["types.CallbackGame"] = None,
         pay: Optional[bool] = None,
         callback_data_with_password: Optional[bytes] = None,
-        icon_custom_emoji_id: Optional[int] = None,
-        style: "enums.ButtonStyle" = enums.ButtonStyle.DEFAULT
     ):
         super().__init__()
 
@@ -147,7 +148,7 @@ def read(b: "raw.base.KeyboardButton"):
             elif raw_style.bg_success:
                 button_style = enums.ButtonStyle.SUCCESS
             if raw_style.icon:
-                icon_custom_emoji_id = raw_style.icon
+                icon_custom_emoji_id = str(raw_style.icon)
 
         if isinstance(b, raw.types.KeyboardButtonCallback):
             # Try decode data to keep it as string, but if fails, fallback to bytes so we don't lose any information,
@@ -267,7 +268,7 @@ async def write(self, client: "pyrogram.Client"):
             bg_primary=self.style == enums.ButtonStyle.PRIMARY,
             bg_danger=self.style == enums.ButtonStyle.DANGER,
             bg_success=self.style == enums.ButtonStyle.SUCCESS,
-            icon=self.icon_custom_emoji_id
+            icon=int(self.icon_custom_emoji_id) if self.icon_custom_emoji_id else None
         )
 
         if self.callback_data_with_password is not None:
diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py
index 787914610e..1a6c4c4638 100644
--- a/pyrogram/types/bots_and_keyboards/keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py
@@ -34,7 +34,7 @@ class KeyboardButton(Object):
             Text of the button. If none of the optional fields are used, it will be sent as a message when
             the button is pressed.
 
-        icon_custom_emoji_id (``int``, *optional*):
+        icon_custom_emoji_id (``str``, *optional*):
             Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription..
 
         style (:obj:`~pyrogram.enums.ButtonStyle`, *optional*):
@@ -70,15 +70,16 @@ class KeyboardButton(Object):
 
     def __init__(
         self,
-        text: str, *,
+        text: str,
+        icon_custom_emoji_id: Optional[str] = None,
+        style: "enums.ButtonStyle" = enums.ButtonStyle.DEFAULT,
+        *,
         request_contact: bool = None,
         request_location: bool = None,
         request_poll: "types.KeyboardButtonPollType" = None,
         web_app: "types.WebAppInfo" = None,
         request_users: "types.KeyboardButtonRequestUsers" = None,
         request_chat: "types.KeyboardButtonRequestChat" = None,
-        icon_custom_emoji_id: Optional[int] = None,
-        style: "enums.ButtonStyle" = enums.ButtonStyle.DEFAULT
     ):
         super().__init__()
 
@@ -106,7 +107,7 @@ def read(b):
             elif raw_style.bg_success:
                 button_style = enums.ButtonStyle.SUCCESS
             if raw_style.icon:
-                icon_custom_emoji_id = raw_style.icon
+                icon_custom_emoji_id = str(raw_style.icon)
 
         if isinstance(b, raw.types.KeyboardButton):
             return KeyboardButton(
@@ -222,7 +223,7 @@ def write(self):
             bg_primary=self.style == enums.ButtonStyle.PRIMARY,
             bg_danger=self.style == enums.ButtonStyle.DANGER,
             bg_success=self.style == enums.ButtonStyle.SUCCESS,
-            icon=self.icon_custom_emoji_id
+            icon=int(self.icon_custom_emoji_id) if self.icon_custom_emoji_id else None
         )
 
         if self.request_contact:
diff --git a/pyrogram/types/inline_mode/chosen_inline_result.py b/pyrogram/types/inline_mode/chosen_inline_result.py
index aa93020eba..b5357e5ade 100644
--- a/pyrogram/types/inline_mode/chosen_inline_result.py
+++ b/pyrogram/types/inline_mode/chosen_inline_result.py
@@ -128,7 +128,8 @@ async def edit_message_text(
             ``bool``: On success, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         if self.inline_message_id is None:
             raise ValueError("Identifier of the inline message is required to edit the message")
@@ -171,7 +172,8 @@ async def edit_message_caption(
             ``bool``: On success, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self.edit_message_text(
             text=caption,
@@ -205,7 +207,8 @@ async def edit_message_media(
             ``bool``: On success, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         if self.inline_message_id is None:
             raise ValueError("Identifier of the inline message is required to edit the message")
@@ -232,7 +235,8 @@ async def edit_message_reply_markup(
             ``bool``: On success, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         if self.inline_message_id is None:
             raise ValueError("Identifier of the inline message is required to edit the message")
diff --git a/pyrogram/types/input_message_content/external_reply_info.py b/pyrogram/types/input_message_content/external_reply_info.py
index cd560d93e2..594867a2ee 100644
--- a/pyrogram/types/input_message_content/external_reply_info.py
+++ b/pyrogram/types/input_message_content/external_reply_info.py
@@ -433,9 +433,9 @@ async def download(
             If the message is a :obj:`~pyrogram.types.PaidMediaInfo` with more than one ``paid_media`` containing ``minithumbnail`` and ``idx`` is not specified, then a list of paths or binary file-like objects is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
             IndexError: In case of wrong value of ``idx``.
             ValueError: If the message doesn't contain any downloadable media.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
         message = getattr(self, self.media.value, None)
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 5a51a7d587..6ced697547 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1605,6 +1605,7 @@ def link(self) -> str:
         if self.chat.is_forum:
             # If it's a forum but not a specific topic message, it defaults to topic 1 (General)
             thread_id = self.message_thread_id if self.is_topic_message else 1
+            # https://t.me/c/1279877202/31475
             return f"https://t.me/{chat_path}/{thread_id}/{self.id}"
 
         # 4. Standard message link
@@ -1740,7 +1741,8 @@ async def reply_text(
             On success, the sent Message is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -1937,7 +1939,8 @@ async def reply_animation(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2124,7 +2127,8 @@ async def reply_audio(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2255,7 +2259,8 @@ async def reply_cached_media(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2337,8 +2342,9 @@ async def reply_chat_action(
             ``bool``: On success, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
             ValueError: In case the provided string is not a valid chat action.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.send_chat_action(
             chat_id=self.chat.id,
@@ -2442,7 +2448,8 @@ async def reply_contact(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2618,7 +2625,8 @@ async def reply_document(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2731,7 +2739,8 @@ async def reply_game(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2816,7 +2825,8 @@ async def reply_inline_bot_result(
             On success, the sent Message is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -2926,7 +2936,8 @@ async def reply_location(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -3025,7 +3036,8 @@ async def reply_media_group(
             List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -3194,7 +3206,8 @@ async def reply_photo(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -3388,7 +3401,8 @@ async def reply_poll(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -3554,7 +3568,8 @@ async def reply_sticker(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -3692,7 +3707,8 @@ async def reply_venue(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -3904,7 +3920,8 @@ async def reply_video(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -4095,7 +4112,8 @@ async def reply_video_note(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -4271,7 +4289,8 @@ async def reply_voice(
             instead.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -4465,7 +4484,8 @@ async def reply_invoice(
             On success, the sent :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         
         reply_to_message_id, reply_parameters = utils._get_reply_to_message_quote_ids(
@@ -4563,7 +4583,8 @@ async def edit_text(
             On success, the edited :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.edit_message_text(
             chat_id=self.chat.id,
@@ -4622,7 +4643,8 @@ async def edit_caption(
             On success, the edited :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.edit_message_caption(
             chat_id=self.chat.id,
@@ -4672,7 +4694,8 @@ async def edit_media(
             On success, the edited :obj:`~pyrogram.types.Message` is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.edit_message_media(
             chat_id=self.chat.id,
@@ -4711,7 +4734,8 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
             :obj:`~pyrogram.types.Message` is returned, otherwise True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.edit_message_reply_markup(
             chat_id=self.chat.id,
@@ -4862,7 +4886,8 @@ async def forward(
             On success, the forwarded Message is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.forward_messages(
             from_chat_id=self.chat.id,
@@ -4993,8 +5018,8 @@ async def copy(
             :obj:`~pyrogram.types.Message`: On success, the copied message is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
             ValueError: In case if an invalid message was provided.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
         if self.service:
@@ -5236,7 +5261,8 @@ async def delete(self, revoke: bool = True):
             ``int``: Amount of affected messages
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.delete_messages(
             chat_id=self.chat.id,
@@ -5324,9 +5350,10 @@ async def click(
             -   A :obj:`~pyrogram.types.User` object in case of a ``KeyboardButtonUserProfile`` button.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
             ValueError: In case the provided index or position is out of range or the button label was not found.
             TimeoutError: In case, after clicking an inline button, the bot fails to answer within the timeout.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         if isinstance(self.reply_markup, types.ReplyKeyboardMarkup):
@@ -5508,7 +5535,8 @@ async def react(
             On success, :obj:`~pyrogram.types.MessageReactions`: is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         sr = None
 
@@ -5571,7 +5599,8 @@ async def retract_vote(
             :obj:`~pyrogram.types.Poll`: On success, the poll with the retracted vote is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.retract_vote(
@@ -5650,9 +5679,9 @@ async def download(
             If the message is a :obj:`~pyrogram.types.PaidMediaInfo` with more than one ``paid_media`` containing ``minithumbnail`` and ``idx`` is not specified, then a list of paths or binary file-like objects is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
             IndexError: In case of wrong value of ``idx``.
             ValueError: If the message doesn't contain any downloadable media.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
         return await self._client.download_media(
@@ -5694,7 +5723,8 @@ async def vote(
             :obj:`~pyrogram.types.Poll`: On success, the poll with the chosen option is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.vote_poll(
@@ -5734,7 +5764,8 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
             otherwise, in case a message object couldn't be returned, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.pin_chat_message(
             chat_id=self.chat.id,
@@ -5764,7 +5795,8 @@ async def unpin(self) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.unpin_chat_message(
             chat_id=self.chat.id,
@@ -5853,7 +5885,7 @@ async def read(self) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
         return await self._client.read_chat_history(
@@ -5886,7 +5918,7 @@ async def view(self, force_read: bool = True) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
         return await self._client.view_messages(
@@ -5925,7 +5957,7 @@ async def translate(
             :obj:`~pyrogram.types.TranslatedText`: The translated result is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
 
         """
         return await self._client.translate_message_text(
@@ -6002,7 +6034,8 @@ async def star(
             On success, :obj:`~pyrogram.types.MessageReactions`: is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.add_paid_message_reaction(
             chat_id=self.chat.id,
diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py
index 120fd8db99..87bb82d0a0 100644
--- a/pyrogram/types/messages_and_media/message_entity.py
+++ b/pyrogram/types/messages_and_media/message_entity.py
@@ -48,7 +48,7 @@ class MessageEntity(Object):
         language (``str``, *optional*):
             For :obj:`~pyrogram.enums.MessageEntityType.PRE` only, the programming language of the entity text.
 
-        custom_emoji_id (``int``, *optional*):
+        custom_emoji_id (``str``, *optional*):
             For :obj:`~pyrogram.enums.MessageEntityType.CUSTOM_EMOJI` only, unique identifier of the custom emoji.
             Use :meth:`~pyrogram.Client.get_custom_emoji_stickers` to get full information about the sticker.
 
@@ -70,7 +70,7 @@ def __init__(
         url: str = None,
         user: "types.User" = None,
         language: str = None,
-        custom_emoji_id: int = None,
+        custom_emoji_id: str = None,
         unix_time: int = None,
         date_time_format: str = None,
     ):
@@ -131,6 +131,8 @@ def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["M
                 entity_type = enums.MessageEntityType.UNKNOWN
             user_id = getattr(entity, "user_id", None)
 
+        custom_emoji_id = getattr(entity, "document_id", None)
+
         return MessageEntity(
             type=entity_type,
             offset=entity.offset,
@@ -138,7 +140,7 @@ def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["M
             url=getattr(entity, "url", None),
             user=types.User._parse(client, users.get(user_id, None)),
             language=getattr(entity, "language", None),
-            custom_emoji_id=getattr(entity, "document_id", None),
+            custom_emoji_id=str(custom_emoji_id) if custom_emoji_id else None,
             unix_time=unix_time,
             date_time_format=date_time_format,
             client=client
@@ -164,7 +166,7 @@ async def write(self):
 
         args.pop("custom_emoji_id")
         if self.custom_emoji_id is not None:
-            args["document_id"] = self.custom_emoji_id
+            args["document_id"] = int(self.custom_emoji_id)
 
         if self.type == enums.MessageEntityType.EXPANDABLE_BLOCKQUOTE:
             args["collapsed"] = True
diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py
index 55958913ac..332f283727 100644
--- a/pyrogram/types/messages_and_media/poll.py
+++ b/pyrogram/types/messages_and_media/poll.py
@@ -271,7 +271,8 @@ async def stop(
             :obj:`~pyrogram.types.Poll`: On success, the stopped poll with the final results is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.stop_poll(
diff --git a/pyrogram/types/messages_and_media/reaction.py b/pyrogram/types/messages_and_media/reaction.py
index b9778981be..06e6ca73a9 100644
--- a/pyrogram/types/messages_and_media/reaction.py
+++ b/pyrogram/types/messages_and_media/reaction.py
@@ -27,11 +27,8 @@ class Reaction(Object):
     """Contains information about a reaction.
 
     Parameters:
-        emoji (``str``, *optional*):
-            Reaction emoji.
-
-        custom_emoji_id (``int``, *optional*):
-            Custom emoji id.
+        type (:obj:`~pyrogram.types.ReactionType`, *optional*):
+            List of chosen reactions.
 
         count (``int``, *optional*):
             Reaction count.
@@ -40,9 +37,6 @@ class Reaction(Object):
             Chosen reaction order.
             Available for chosen reactions.
 
-        is_paid (``bool``, *optional*):
-            True, if this is a paid reaction.
-
     """
 
     def __init__(
@@ -52,7 +46,6 @@ def __init__(
         type: "types.ReactionType" = None,
         count: Optional[int] = None,
         chosen_order: Optional[int] = None,
-        is_paid: Optional[bool] = None
     ):
         super().__init__(client)
 
@@ -77,7 +70,7 @@ def _parse(
             return Reaction(
                 client=client,
                 type=ReactionTypeCustomEmoji(
-                    custom_emoji_id=reaction.document_id
+                    custom_emoji_id=str(reaction.document_id)
                 )
             )
 
@@ -133,7 +126,7 @@ def _parse(
             )
         elif isinstance(update, raw.types.ReactionCustomEmoji):
             return ReactionTypeCustomEmoji(
-                custom_emoji_id=update.document_id
+                custom_emoji_id=str(update.document_id)
             )
         elif isinstance(update, raw.types.ReactionPaid):
             return ReactionTypePaid()
@@ -148,6 +141,7 @@ class ReactionTypeEmoji(ReactionType):
     Parameters:
         emoji (``str``, *optional*):
             Reaction emoji.
+
     """
 
     def __init__(
@@ -172,6 +166,7 @@ class ReactionTypeCustomEmoji(ReactionType):
     Parameters:
         custom_emoji_id (``str``, *optional*):
             Custom emoji id.
+
     """
 
     def __init__(
@@ -185,9 +180,11 @@ def __init__(
         )
     
     def write(self, client: "pyrogram.Client") -> "raw.base.Reaction":
-        return raw.types.ReactionCustomEmoji(
-            document_id=self.custom_emoji_id
-        )
+        if self.custom_emoji_id is not None:
+            return raw.types.ReactionCustomEmoji(
+                document_id=int(self.custom_emoji_id)
+            )
+        return raw.types.ReactionEmpty()
 
 
 class ReactionTypePaid(ReactionType):
diff --git a/pyrogram/types/stories/story.py b/pyrogram/types/stories/story.py
index f75a5acfad..076d72555e 100644
--- a/pyrogram/types/stories/story.py
+++ b/pyrogram/types/stories/story.py
@@ -474,7 +474,8 @@ async def react(
             On success, :obj:`~pyrogram.types.MessageReactions`: is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         sr = None
 
@@ -579,8 +580,9 @@ async def download(
             Otherwise, in case ``in_memory=True``, a binary file-like object with its attribute ".name" set is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
-            ``ValueError``: If the message doesn't contain any downloadable media
+            ValueError: If the message doesn't contain any downloadable media.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.download_media(
             message=self,
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index 37b2314afb..0f06426804 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -864,7 +864,8 @@ async def archive(self):
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.archive_chats(self.id)
@@ -887,7 +888,8 @@ async def unarchive(self):
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.unarchive_chats(self.id)
@@ -922,8 +924,9 @@ async def set_title(self, title: str) -> bool:
             ``bool``: True on success.
 
         Raises:
-            RPCError: In case of Telegram RPC error.
             ValueError: In case a chat_id belongs to user.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.set_chat_title(
@@ -956,8 +959,9 @@ async def set_description(self, description: str) -> bool:
             ``bool``: True on success.
 
         Raises:
-            RPCError: In case of Telegram RPC error.
             ValueError: If a chat_id doesn't belong to a supergroup or a channel.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.set_chat_description(
@@ -1018,8 +1022,9 @@ async def set_photo(
             otherwise, in case a message object couldn't be returned, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
             ValueError: if a chat_id belongs to user.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.set_chat_photo(
@@ -1105,7 +1110,8 @@ async def ban_member(
             case a message object couldn't be returned, True is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.ban_chat_member(
@@ -1144,7 +1150,8 @@ async def unban_member(
             ``bool``: True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.unban_chat_member(
@@ -1196,7 +1203,8 @@ async def restrict_member(
             :obj:`~pyrogram.types.Chat`: On success, a chat object is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.restrict_chat_member(
@@ -1243,7 +1251,8 @@ async def promote_member(
             ``bool``: True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.promote_chat_member(
@@ -1273,7 +1282,8 @@ async def join(self):
             :obj:`~pyrogram.types.Chat`: On success, a chat object is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.join_chat(self.username or self.id)
@@ -1293,7 +1303,8 @@ async def leave(self):
                 await chat.leave()
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.leave_chat(self.id)
diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py
index 7bc995970a..c03b2fa10c 100644
--- a/pyrogram/types/user_and_chats/chat_join_request.py
+++ b/pyrogram/types/user_and_chats/chat_join_request.py
@@ -107,7 +107,8 @@ async def approve(self) -> bool:
             ``bool``: True on success.
         
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.approve_chat_join_request(
             chat_id=self.chat.id,
@@ -135,7 +136,8 @@ async def decline(self) -> bool:
             ``bool``: True on success.
         
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
         return await self._client.decline_chat_join_request(
             chat_id=self.chat.id,
diff --git a/pyrogram/types/user_and_chats/emoji_status.py b/pyrogram/types/user_and_chats/emoji_status.py
index d997471717..3cc44a634b 100644
--- a/pyrogram/types/user_and_chats/emoji_status.py
+++ b/pyrogram/types/user_and_chats/emoji_status.py
@@ -29,18 +29,19 @@ class EmojiStatus(Object):
     """A user emoji status.
 
     Parameters:
-        custom_emoji_id (``int``):
+        custom_emoji_id (``str``):
             Custom emoji id.
 
         until_date (:py:obj:`~datetime.datetime`, *optional*):
             Valid until date.
+
     """
 
     def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        custom_emoji_id: int,
+        custom_emoji_id: str,
         until_date: Optional[datetime] = None,
         _raw: "raw.base.EmojiStatus" = None,
     ):
@@ -55,7 +56,7 @@ def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatu
         if isinstance(emoji_status, raw.types.EmojiStatus):
             return EmojiStatus(
                 client=client,
-                custom_emoji_id=emoji_status.document_id,
+                custom_emoji_id=str(emoji_status.document_id),
                 until_date=utils.timestamp_to_datetime(emoji_status.until),
                 _raw=emoji_status
             )
@@ -63,7 +64,7 @@ def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatu
         if isinstance(emoji_status, raw.types.EmojiStatusCollectible):
             return EmojiStatus(
                 client=client,
-                custom_emoji_id=emoji_status.document_id,
+                custom_emoji_id=str(emoji_status.document_id),
                 until_date=utils.timestamp_to_datetime(emoji_status.until),
                 _raw=emoji_status
             )
@@ -73,10 +74,10 @@ def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatu
     def write(self):
         if self.until_date:
             return raw.types.EmojiStatusUntil(
-                document_id=self.custom_emoji_id,
+                document_id=int(self.custom_emoji_id),
                 until=utils.datetime_to_timestamp(self.until_date)
             )
 
         return raw.types.EmojiStatus(
-            document_id=self.custom_emoji_id
+            document_id=int(self.custom_emoji_id)
         )
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index ce07c70890..97fed3a6ba 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -476,7 +476,8 @@ async def archive(self) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.archive_chats(self.id)
@@ -499,7 +500,8 @@ async def unarchive(self) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.unarchive_chats(self.id)
@@ -522,7 +524,8 @@ async def block(self) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.block_user(self.id)
@@ -545,7 +548,8 @@ async def unblock(self) -> bool:
             True on success.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.unblock_user(self.id)
@@ -568,7 +572,8 @@ async def get_common_chats(self) -> list["types.Chat"]:
             List of :obj:`~pyrogram.types.Chat`: On success, a list of the common chats is returned.
 
         Raises:
-            RPCError: In case of a Telegram RPC error.
+            :obj:`~pyrogram.errors.RPCError`: In case of a Telegram RPC error.
+
         """
 
         return await self._client.get_common_chats(self.id)
diff --git a/tests/parser/test_html.py b/tests/parser/test_html.py
index b9138f3cf1..08dfbe7485 100644
--- a/tests/parser/test_html.py
+++ b/tests/parser/test_html.py
@@ -61,7 +61,7 @@ def test_html_unparse_strike():
 
 
 def test_html_unparse_spoiler():
-    expected = "spoiler"
+    expected = "spoiler"
     text = "spoiler"
     entities = pyrogram.types.List(
         [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=0, length=7)])
@@ -72,8 +72,25 @@ def test_html_unparse_spoiler():
 def test_html_unparse_url():
     expected = 'URL'
     text = "URL"
-    entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.TEXT_LINK,
-                                                                 offset=0, length=3, url='https://pyrogram.org/')])
+    entities = pyrogram.types.List([
+        pyrogram.types.MessageEntity(
+            type=pyrogram.enums.MessageEntityType.TEXT_LINK,
+            offset=0, length=3,
+            url="https://pyrogram.org/"
+        )
+    ])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_emoji():
+    expected = '🥲 im crying'
+    text = "🥲 im crying"
+    entities = pyrogram.types.List([
+        pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CUSTOM_EMOJI,
+        offset=0, length=2,
+        custom_emoji_id=5195264424893488796)
+    ])
 
     assert HTML.unparse(text=text, entities=entities) == expected
 
@@ -88,34 +105,53 @@ def test_html_unparse_code():
 
 
 def test_html_unparse_pre():
-    expected = """
for i in range(10):
-    print(i)
""" + expected = """
for i in range(10):
+    print(i)
""" text = """for i in range(10): print(i)""" - entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.PRE, offset=0, - length=32, language='python')]) + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity( + type=pyrogram.enums.MessageEntityType.PRE, + offset=0, length=32, + language="python" + ) + ]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_blockquote(): + expected = """
Block quotation started\nBlock quotation continued\nThe last line of the block quotation
\n
Expandable block quotation started\nExpandable block quotation continued\nExpandable block quotation continued\nHidden by default part of the block quotation started\nExpandable block quotation continued\nThe last line of the block quotation
""" + + text = """Block quotation started\nBlock quotation continued\nThe last line of the block quotation\nExpandable block quotation started\nExpandable block quotation continued\nExpandable block quotation continued\nHidden by default part of the block quotation started\nExpandable block quotation continued\nThe last line of the block quotation""" + + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BLOCKQUOTE, offset=0, length=86), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.EXPANDABLE_BLOCKQUOTE, offset=87, length=236) + ]) assert HTML.unparse(text=text, entities=entities) == expected def test_html_unparse_mixed(): - expected = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" \ - "eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" + expected = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" \ + "eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" text = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" - entities = pyrogram.types.List( - [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=14), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=7, length=7), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=10, length=4), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=14, length=9), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=14, length=9), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=23, length=10), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=30, length=3), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=33, length=10), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=38, length=5), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=43, length=10), - pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=57, length=10)]) + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=14), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=7, length=7), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=10, length=4), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=14, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=14, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=23, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=30, length=3), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=33, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=38, length=5), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=43, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=57, length=10) + ]) assert HTML.unparse(text=text, entities=entities) == expected diff --git a/tests/parser/test_markdown.py b/tests/parser/test_markdown.py new file mode 100644 index 0000000000..70248b01bd --- /dev/null +++ b/tests/parser/test_markdown.py @@ -0,0 +1,272 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.parser.markdown import Markdown + + +# expected: the expected unparsed Markdown +# text: original text without entities +# entities: message entities coming from the server + +def test_markdown_unparse_bold(): + expected = "**bold**" + text = "bold" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=4)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_italic(): + expected = "__italic__" + text = "italic" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=0, length=6)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_italic_html(): + expected = "__This works, it's ok__ This shouldn't" + text = "This works, it's ok This shouldn't" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=0, length=19)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_underline(): + expected = "--underline--" + text = "underline" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=0, length=9)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_strike(): + expected = "~~strike~~" + text = "strike" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=0, length=6)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_spoiler(): + expected = "||spoiler||" + text = "spoiler" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=0, length=7)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_url(): + expected = '[URL](https://pyrogram.org/)' + text = "URL" + entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.TEXT_LINK, + offset=0, length=3, url='https://pyrogram.org/')]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_emoji(): + expected = '![🥲](tg://emoji?id=5195264424893488796) im crying' + text = "🥲 im crying" + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CUSTOM_EMOJI, + offset=0, length=2, + custom_emoji_id=5195264424893488796) + ]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_code(): + expected = '`code`' + text = "code" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=0, length=4)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_pre(): + expected = """```python +for i in range(10): + print(i)```""" + + text = """for i in range(10): + print(i)""" + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity( + type=pyrogram.enums.MessageEntityType.PRE, + offset=0, length=32, + language="python" + ) + ]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_pre_2(): + expected = """```...```""" + + text = """...""" + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity( + type=pyrogram.enums.MessageEntityType.PRE, + offset=0, length=3, + language="" + ) + ]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_blockquote(): + expected = """>Block quotation started +>Block quotation continued +>The last line of the block quotation +**>Expandable block quotation started +>Expandable block quotation continued +>Expandable block quotation continued +>Hidden by default part of the block quotation started +>Expandable block quotation continued +>The last line of the block quotation||""" + + text = """Block quotation started\nBlock quotation continued\nThe last line of the block quotation\nExpandable block quotation started\nExpandable block quotation continued\nExpandable block quotation continued\nHidden by default part of the block quotation started\nExpandable block quotation continued\nThe last line of the block quotation""" + + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BLOCKQUOTE, offset=0, length=86), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.EXPANDABLE_BLOCKQUOTE, offset=87, length=236) + ]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_mixed(): + expected = "**aaaaaaa__aaabbb__**~~dddddddd||ddeee||~~||eeeeeeefff||ffff`fffggggggg`ggghhhhhhhhhh" + text = "aaaaaaaaaabbbddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" + entities = pyrogram.types.List([ + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=13), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=7, length=6), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=13, length=13), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=21, length=5), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=26, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=40, length=10) + ]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_mixed_2(): + expected = """**bold**, **bold** +__italic__, __italic__ +--underline--, --underline-- +~~strikethrough~~, ~~strikethrough~~, ~~strikethrough~~ +||spoiler||, ||spoiler|| +**bold __italic bold ~~italic bold strikethrough ||italic bold strikethrough spoiler||~~ --underline italic bold--__ bold** +[inline URL](http://www.example.com/) +![👍](tg://emoji?id=5368324170671202286) +![22:45 tomorrow](tg://time?unix=1647531900&format=wDT) +![22:45 tomorrow](tg://time?unix=1647531900&format=t) +![22:45 tomorrow](tg://time?unix=1647531900&format=r) +![22:45 tomorrow](tg://time?unix=1647531900) +`inline fixed-width code` +``` +pre-formatted fixed-width code block``` +```python +pre-formatted fixed-width code block written in the Python programming language``` +>Block quotation started +>Block quotation continued +>The last line of the block quotation +**>Expandable block quotation started +**>Expandable block quotation continued +**>Expandable block quotation continued +**>Hidden by default part of the block quotation started +**>Expandable block quotation continued +**>The last line of the block quotation""" + + text = """bold, bold +italic, italic +underline, underline +strikethrough, strikethrough, strikethrough +spoiler, spoiler +bold italic bold italic bold strikethrough italic bold strikethrough spoiler underline italic bold bold +inline URL +👍 +22:45 tomorrow +22:45 tomorrow +22:45 tomorrow +22:45 tomorrow +inline fixed-width code + +pre-formatted fixed-width code block + +pre-formatted fixed-width code block written in the Python programming language +Block quotation started +Block quotation continued +The last line of the block quotation +Expandable block quotation started +Expandable block quotation continued +Expandable block quotation continued +Hidden by default part of the block quotation started +Expandable block quotation continued +The last line of the block quotation""" + entities = entities = pyrogram.types.List([ + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=4), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=6, length=4), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=11, length=6), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=19, length=6), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=26, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=37, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=47, length=13), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=62, length=13), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=77, length=13), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=91, length=7), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=100, length=7), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=108, length=103), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=113, length=93), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=125, length=59), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=151, length=33), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=185, length=21), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.TEXT_LINK, offset=212, length=10, url="http://www.example.com/"), + # TODO + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.DATE_TIME, offset=251, length=14, unix_time=1647531900, date_time_format="wDT"), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.DATE_TIME, offset=266, length=14, unix_time=1647531900, date_time_format="t"), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.DATE_TIME, offset=281, length=14, unix_time=1647531900, date_time_format="r"), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.DATE_TIME, offset=296, length=14, unix_time=1647531900, date_time_format=""), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=311, length=23), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.PRE, offset=335, length=37, language=""), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.PRE, offset=373, length=80, language="python"), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BLOCKQUOTE, offset=454, length=86), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.EXPANDABLE_BLOCKQUOTE, offset=541, length=236), + ]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + +def test_markdown_unparse_no_entities(): + expected = "text" + text = "text" + entities = [] + + assert Markdown.unparse(text=text, entities=entities) == expected