'
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
\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
"""
+
+ 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 = ' 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/)
+
+
+
+
+
+`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