diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index 266253d6..a1fee7f6 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -31,7 +31,8 @@ class SetChatPhoto(Scaffold): chat_id: Union[int, str], *, photo: Union[str, BinaryIO] = None, - video: Union[str, BinaryIO] = None + video: Union[str, BinaryIO] = None, + video_start_ts: float = None, ) -> bool: """Set a new chat photo or video (H.264/MPEG-4 AVC video, max 5 seconds). @@ -54,6 +55,9 @@ class SetChatPhoto(Scaffold): from your local machine or a binary file-like object with its attribute ".name" set for in-memory uploads. + video_start_ts (``float``, *optional*): + The timestamp in seconds of the video frame to use as photo profile preview. + Returns: ``bool``: True on success. @@ -82,7 +86,8 @@ class SetChatPhoto(Scaffold): if os.path.isfile(photo): photo = raw.types.InputChatUploadedPhoto( file=await self.save_file(photo), - video=await self.save_file(video) + video=await self.save_file(video), + video_start_ts=video_start_ts, ) else: photo = utils.get_input_media_from_file_id(photo, FileType.PHOTO) @@ -90,14 +95,15 @@ class SetChatPhoto(Scaffold): else: photo = raw.types.InputChatUploadedPhoto( file=await self.save_file(photo), - video=await self.save_file(video) + video=await self.save_file(video), + video_start_ts=video_start_ts, ) if isinstance(peer, raw.types.InputPeerChat): await self.send( raw.functions.messages.EditChatPhoto( chat_id=peer.chat_id, - photo=photo + photo=photo, ) ) elif isinstance(peer, raw.types.InputPeerChannel): diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index b70a189f..81c761ac 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -175,10 +175,7 @@ class HTML: entities_offsets.append((start_tag, start,)) entities_offsets.append((end_tag, end,)) - # sorting by offset (desc) - entities_offsets.sort(key=lambda x: -x[1]) - - for entity, offset in entities_offsets: + for entity, offset in reversed(entities_offsets): text = text[:offset] + entity + text[offset:] return utils.remove_surrogates(text) diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py index 2ea9e4f2..d294e04d 100644 --- a/pyrogram/parser/parser.py +++ b/pyrogram/parser/parser.py @@ -31,7 +31,7 @@ class Parser: self.markdown = Markdown(client) async def parse(self, text: str, mode: Optional[str] = object): - text = str(text).strip() + text = str(text if text else "").strip() if mode == object: if self.client: diff --git a/pyrogram/py.typed b/pyrogram/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pyrogram/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py index 025176f2..4cb137d8 100644 --- a/pyrogram/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/types/bots_and_keyboards/force_reply.py @@ -36,24 +36,31 @@ class ForceReply(Object): Use this parameter if you want to force reply from specific users only. Targets: 1) users that are @mentioned in the text of the Message object; 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. + + placeholder (``str``, *optional*): + The placeholder to be shown in the input field when the reply is active; 1-64 characters. """ def __init__( self, - selective: bool = None + selective: bool = None, + placeholder: str = None ): super().__init__() self.selective = selective + self.placeholder = placeholder @staticmethod def read(b): return ForceReply( - selective=b.selective + selective=b.selective, + placeholder=b.placeholder ) async def write(self, _: "pyrogram.Client"): return raw.types.ReplyKeyboardForceReply( single_use=True, - selective=self.selective or None + selective=self.selective or None, + placeholder=self.placeholder or None ) diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py index b619216a..b62f6dcf 100644 --- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py @@ -47,6 +47,9 @@ class ReplyKeyboardMarkup(Object): 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. Example: A user requests to change the bot's language, bot replies to the request with a keyboard to select the new language. Other users in the group don't see the keyboard. + + placeholder (``str``, *optional*): + The placeholder to be shown in the input field when the keyboard is active; 1-64 characters. """ def __init__( @@ -54,7 +57,8 @@ class ReplyKeyboardMarkup(Object): keyboard: List[List[Union["types.KeyboardButton", str]]], resize_keyboard: bool = None, one_time_keyboard: bool = None, - selective: bool = None + selective: bool = None, + placeholder: str = None ): super().__init__() @@ -62,6 +66,7 @@ class ReplyKeyboardMarkup(Object): self.resize_keyboard = resize_keyboard self.one_time_keyboard = one_time_keyboard self.selective = selective + self.placeholder = placeholder @staticmethod def read(kb): @@ -79,7 +84,8 @@ class ReplyKeyboardMarkup(Object): keyboard=keyboard, resize_keyboard=kb.resize, one_time_keyboard=kb.single_use, - selective=kb.selective + selective=kb.selective, + placeholder=kb.placeholder ) async def write(self, _: "pyrogram.Client"): @@ -93,5 +99,6 @@ class ReplyKeyboardMarkup(Object): ) for i in self.keyboard], resize=self.resize_keyboard or None, single_use=self.one_time_keyboard or None, - selective=self.selective or None + selective=self.selective or None, + placeholder=self.placeholder or None ) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index a9d396eb..6dc3fb82 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -247,7 +247,8 @@ class Chat(Object): is_fake=getattr(channel, "fake", None), title=channel.title, username=getattr(channel, "username", None), - photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash), + photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, + getattr(channel, "access_hash", 0)), restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None, permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), members_count=getattr(channel, "participants_count", None), diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py index a7001da5..fe051de4 100644 --- a/pyrogram/types/user_and_chats/chat_join_request.py +++ b/pyrogram/types/user_and_chats/chat_join_request.py @@ -80,3 +80,59 @@ class ChatJoinRequest(Object, Update): invite_link=types.ChatInviteLink._parse(client, update.invite, users), client=client ) + + async def approve(self) -> bool: + """Bound method *approve* of :obj:`~pyrogram.types.ChatJoinRequest`. + + Use as a shortcut for: + + .. code-block:: python + + client.approve_chat_join_request( + chat_id=request.chat.id, + user_id=request.from_user.id + ) + + Example: + .. code-block:: python + + request.approve() + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + return await self._client.approve_chat_join_request( + chat_id=self.chat.id, + user_id=self.from_user.id + ) + + async def decline(self) -> bool: + """Bound method *decline* of :obj:`~pyrogram.types.ChatJoinRequest`. + + Use as a shortcut for: + + .. code-block:: python + + client.decline_chat_join_request( + chat_id=request.chat.id, + user_id=request.from_user.id + ) + + Example: + .. code-block:: python + + request.decline() + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + return await self._client.decline_chat_join_request( + chat_id=self.chat.id, + user_id=self.from_user.id + ) diff --git a/setup.py b/setup.py index 95181a65..5d8c14e1 100644 --- a/setup.py +++ b/setup.py @@ -172,6 +172,9 @@ setup( "Documentation": "https://docs.pyrogram.org", }, python_requires="~=3.6", + package_data = { + "pyrogram": ["py.typed"], + }, packages=find_packages(exclude=["compiler*", "tests*"]), zip_safe=False, install_requires=requires,