From 83f2111be95e4e52d5b0df7146468bbed7318fb7 Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:30:56 +0300 Subject: [PATCH 01/13] FIX get_dialogs.py Fix infinite loop in get_dialogs() by preventing repeated dialogs - Added tracking of seen dialog IDs to avoid yielding duplicates - Added check for None top_message to safely stop pagination - Preserved support for limit, pinned_only, and chat_list parameters - Ensured correct offset updates for reliable pagination --- pyrogram/methods/chats/get_dialogs.py | 60 +++++++++------------------ 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index cfd8c145a3..660637d5b7 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from asyncio import sleep from typing import AsyncGenerator, Optional -import pyrogram from pyrogram import types, raw, utils +from pyrogram.errors import ChannelPrivate, PeerIdInvalid class GetDialogs: @@ -30,47 +29,24 @@ async def get_dialogs( pinned_only: bool = False, chat_list: int = 0 ) -> Optional[AsyncGenerator["types.Dialog", None]]: - """Get a user's dialogs sequentially. - - .. include:: /_includes/usable-by/users.rst - - Parameters: - limit (``int``, *optional*): - Limits the number of dialogs to be retrieved. - By default, no limit is applied and all dialogs are returned. - - pinned_only (``bool``, *optional*): - Pass True if you want to get only pinned dialogs. - Defaults to False. - - chat_list (``int``, *optional*): - Chat list from which to get the dialogs; Only Main (0) and Archive (1) chat lists are supported. Defaults to (0) Main chat list. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. - - Example: - .. code-block:: python - - # Iterate through all dialogs - async for dialog in app.get_dialogs(): - print(dialog.chat.first_name or dialog.chat.title) - """ + current = 0 total = limit or (1 << 31) - 1 - limit = min(100, total) + request_limit = min(100, total) offset_date = 0 offset_id = 0 offset_peer = raw.types.InputPeerEmpty() + seen_dialog_ids = set() + while True: r = await self.invoke( raw.functions.messages.GetDialogs( offset_date=offset_date, offset_id=offset_id, offset_peer=offset_peer, - limit=limit, + limit=request_limit, hash=0, exclude_pinned=not pinned_only, folder_id=chat_list @@ -88,13 +64,10 @@ async def get_dialogs( continue chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse( - self, - message, - users, - chats, - replies=self.fetch_replies - ) + try: + messages[chat_id] = await types.Message._parse(self, message, users, chats) + except (ChannelPrivate, PeerIdInvalid): + continue dialogs = [] @@ -102,22 +75,27 @@ async def get_dialogs( if not isinstance(dialog, raw.types.Dialog): continue - dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) + parsed = types.Dialog._parse(self, dialog, messages, users, chats) + if parsed.chat.id in seen_dialog_ids: + continue + seen_dialog_ids.add(parsed.chat.id) + + dialogs.append(parsed) if not dialogs: return last = dialogs[-1] + if last.top_message is None: + return + offset_id = last.top_message.id offset_date = utils.datetime_to_timestamp(last.top_message.date) offset_peer = await self.resolve_peer(last.chat.id) for dialog in dialogs: - await sleep(0) yield dialog - current += 1 - if current >= total: return From 6ebe425edf79f955c2b2695c0efff11c85b9e6e6 Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:59:59 +0300 Subject: [PATCH 02/13] FIX get_dialogs.py fix: skip dialogs with no parsed chat to avoid errors --- pyrogram/methods/chats/get_dialogs.py | 35 +++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 660637d5b7..89a1f3b0bb 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -29,7 +29,32 @@ async def get_dialogs( pinned_only: bool = False, chat_list: int = 0 ) -> Optional[AsyncGenerator["types.Dialog", None]]: - + """Get a user's dialogs sequentially. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + limit (``int``, *optional*): + Limits the number of dialogs to be retrieved. + By default, no limit is applied and all dialogs are returned. + + pinned_only (``bool``, *optional*): + Pass True if you want to get only pinned dialogs. + Defaults to False. + + chat_list (``int``, *optional*): + Chat list from which to get the dialogs; Only Main (0) and Archive (1) chat lists are supported. Defaults to (0) Main chat list. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. + + Example: + .. code-block:: python + + # Iterate through all dialogs + async for dialog in app.get_dialogs(): + print(dialog.chat.first_name or dialog.chat.title) + """ current = 0 total = limit or (1 << 31) - 1 request_limit = min(100, total) @@ -76,10 +101,16 @@ async def get_dialogs( continue parsed = types.Dialog._parse(self, dialog, messages, users, chats) + if parsed is None: + continue + + if parsed.chat is None: + continue + if parsed.chat.id in seen_dialog_ids: continue + seen_dialog_ids.add(parsed.chat.id) - dialogs.append(parsed) if not dialogs: From 7c5883360baef2eb462c0d752b7f3bb1d8084375 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Wed, 20 Aug 2025 09:16:54 +0530 Subject: [PATCH 03/13] Update get_dialogs.py --- pyrogram/methods/chats/get_dialogs.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 89a1f3b0bb..40e1e173b9 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from asyncio import sleep from typing import AsyncGenerator, Optional -from pyrogram import types, raw, utils -from pyrogram.errors import ChannelPrivate, PeerIdInvalid +from pyrogram import raw, types, utils class GetDialogs: @@ -89,10 +89,7 @@ async def get_dialogs( continue chat_id = utils.get_peer_id(message.peer_id) - try: - messages[chat_id] = await types.Message._parse(self, message, users, chats) - except (ChannelPrivate, PeerIdInvalid): - continue + messages[chat_id] = await types.Message._parse(self, message, users, chats, replies=self.fetch_replies) dialogs = [] @@ -126,6 +123,7 @@ async def get_dialogs( offset_peer = await self.resolve_peer(last.chat.id) for dialog in dialogs: + await sleep(0) yield dialog current += 1 if current >= total: From 1414e4ceb19587ad3dad07cfd52923d2813bef3b Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Wed, 20 Aug 2025 09:17:21 +0530 Subject: [PATCH 04/13] Update get_dialogs.py --- pyrogram/methods/chats/get_dialogs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 40e1e173b9..f06913a4c9 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -19,6 +19,7 @@ from asyncio import sleep from typing import AsyncGenerator, Optional +import pyrogram from pyrogram import raw, types, utils From d54c5bd5a89028b662ecd2201eadfcab8986c496 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Wed, 20 Aug 2025 09:18:05 +0530 Subject: [PATCH 05/13] Update get_dialogs.py --- pyrogram/methods/chats/get_dialogs.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index f06913a4c9..3318dd2535 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -90,7 +90,13 @@ async def get_dialogs( continue chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse(self, message, users, chats, replies=self.fetch_replies) + messages[chat_id] = await types.Message._parse( + self, + message, + users, + chats, + replies=self.fetch_replies + ) dialogs = [] From 4fab224c38f59462ac3c0433e64afe9c822d4fbc Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:38:22 +0300 Subject: [PATCH 06/13] Update chat_permissions.py add support for can_send_plain permission in chat permissions --- pyrogram/types/user_and_chats/chat_permissions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py index 07b655caf9..3c8473d73d 100644 --- a/pyrogram/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -78,6 +78,7 @@ class ChatPermissions(Object): def __init__( self, *, + can_send_plain: bool = None, can_send_messages: bool = None, # Text, contacts, giveaways, giveaway winners, invoices, locations and venues can_send_audios: bool = None, can_send_documents: bool = None, @@ -96,6 +97,7 @@ def __init__( ): super().__init__(None) + self.can_send_plain = can_send_plain self.can_send_messages = can_send_messages self.can_send_audios = can_send_audios self.can_send_documents = can_send_documents @@ -114,6 +116,7 @@ def __init__( @staticmethod def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions": + can_send_plain = False can_send_messages = False can_send_audios = False can_send_documents = False @@ -131,6 +134,7 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_send_media_messages = False if isinstance(denied_permissions, raw.types.ChatBannedRights): + can_send_plain = not denied_permissions.send_plain can_send_messages = not denied_permissions.send_messages can_send_polls = not denied_permissions.send_polls can_send_other_messages = any([ @@ -171,6 +175,7 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_send_voice_notes = can_send_media_messages return ChatPermissions( + can_send_plain=can_send_plain, can_send_messages=can_send_messages, can_send_audios=can_send_audios, can_send_documents=can_send_documents, @@ -217,5 +222,6 @@ def write( send_videos=not permissions.can_send_videos,# TODO send_roundvideos=not permissions.can_send_video_notes,# TODO send_voices=not permissions.can_send_voice_notes,# TODO + send_plain=not permissions.can_send_plain# TODO # send_plain=# TODO ) From 34847aba54e1bed9e13b4914cd3416fbcbe22dd0 Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:09:19 +0300 Subject: [PATCH 07/13] Update chat_permissions.py docstring Updated the class docstring to document this new permission, ensuring clarity and consistency with the actual class parameters. --- pyrogram/types/user_and_chats/chat_permissions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py index 3c8473d73d..778f1ef470 100644 --- a/pyrogram/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -28,6 +28,9 @@ class ChatPermissions(Object): can_send_messages (``bool``, *optional*): True, if the user is allowed to send text messages, contacts, giveaways, giveaway winners, invoices, locations and venues + can_send_plain (``bool``, *optional*): + True, if the user is allowed to send plain text messages + can_send_audios (``bool``, *optional*): True, if the user is allowed to send audios @@ -223,5 +226,4 @@ def write( send_roundvideos=not permissions.can_send_video_notes,# TODO send_voices=not permissions.can_send_voice_notes,# TODO send_plain=not permissions.can_send_plain# TODO - # send_plain=# TODO ) From e3171d51ac1f9d1060a3a3d9029f05dd1548a93d Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Thu, 28 Aug 2025 10:18:33 +0530 Subject: [PATCH 08/13] Update set_chat_permissions.py --- .../methods/chats/set_chat_permissions.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py index d8ec0cf02b..bdc2f0216b 100644 --- a/pyrogram/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -54,12 +54,12 @@ async def set_chat_permissions( # Completely restrict chat await app.set_chat_permissions(chat_id, ChatPermissions()) - # Chat members can only send text messages and media messages + # Chat members can only send text messages and documents await app.set_chat_permissions( chat_id, ChatPermissions( can_send_messages=True, - can_send_media_messages=True + can_send_documents=True ) ) """ @@ -67,20 +67,7 @@ async def set_chat_permissions( r = await self.invoke( raw.functions.messages.EditChatDefaultBannedRights( peer=await self.resolve_peer(chat_id), - banned_rights=raw.types.ChatBannedRights( - until_date=0, - send_messages=not permissions.can_send_messages, - send_media=not permissions.can_send_media_messages, - send_stickers=not permissions.can_send_other_messages, - send_gifs=not permissions.can_send_other_messages, - send_games=not permissions.can_send_other_messages, - send_inline=not permissions.can_send_other_messages, - embed_links=not permissions.can_add_web_page_previews, - send_polls=not permissions.can_send_polls, - change_info=not permissions.can_change_info, - invite_users=not permissions.can_invite_users, - pin_messages=not permissions.can_pin_messages, - ) + banned_rights=permissions.write(False) ) ) From acb1ddf80931a3a2d90737138fa3ea26ddc4ebb2 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Thu, 28 Aug 2025 10:18:52 +0530 Subject: [PATCH 09/13] Update chat_permissions.py --- .../types/user_and_chats/chat_permissions.py | 146 ++++++++++++------ 1 file changed, 99 insertions(+), 47 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py index 778f1ef470..bc0f779c0b 100644 --- a/pyrogram/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -16,10 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import logging from datetime import datetime from pyrogram import raw, utils from ..object import Object +log = logging.getLogger(__name__) + class ChatPermissions(Object): """Describes actions that a non-administrator user is allowed to take in a chat. @@ -28,9 +31,6 @@ class ChatPermissions(Object): can_send_messages (``bool``, *optional*): True, if the user is allowed to send text messages, contacts, giveaways, giveaway winners, invoices, locations and venues - can_send_plain (``bool``, *optional*): - True, if the user is allowed to send plain text messages - can_send_audios (``bool``, *optional*): True, if the user is allowed to send audios @@ -73,15 +73,11 @@ class ChatPermissions(Object): True, if the user is allowed to create forum topics If omitted defaults to the value of can_pin_messages - can_send_media_messages (``bool``, *optional*): - True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes. - Implies *can_send_messages*. """ def __init__( self, *, - can_send_plain: bool = None, can_send_messages: bool = None, # Text, contacts, giveaways, giveaway winners, invoices, locations and venues can_send_audios: bool = None, can_send_documents: bool = None, @@ -96,11 +92,9 @@ def __init__( can_invite_users: bool = None, can_pin_messages: bool = None, can_manage_topics: bool = None, - can_send_media_messages: bool = None, # Audio files, documents, photos, videos, video notes and voice notes ): super().__init__(None) - self.can_send_plain = can_send_plain self.can_send_messages = can_send_messages self.can_send_audios = can_send_audios self.can_send_documents = can_send_documents @@ -115,11 +109,9 @@ def __init__( self.can_invite_users = can_invite_users self.can_pin_messages = can_pin_messages self.can_manage_topics = can_manage_topics - self.can_send_media_messages = can_send_media_messages @staticmethod def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions": - can_send_plain = False can_send_messages = False can_send_audios = False can_send_documents = False @@ -134,10 +126,8 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_invite_users = False can_pin_messages = False can_manage_topics = False - can_send_media_messages = False if isinstance(denied_permissions, raw.types.ChatBannedRights): - can_send_plain = not denied_permissions.send_plain can_send_messages = not denied_permissions.send_messages can_send_polls = not denied_permissions.send_polls can_send_other_messages = any([ @@ -168,17 +158,29 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_send_videos = not denied_permissions.send_videos can_send_video_notes = not denied_permissions.send_roundvideos can_send_voice_notes = not denied_permissions.send_voices + send_aidem = any([ + can_send_audios, + can_send_documents, + can_send_photos, + can_send_videos, + can_send_video_notes, + can_send_voice_notes, + ]) + if not send_aidem: + can_send_messages = not any([ + denied_permissions.send_messages, + denied_permissions.send_plain + ]) else: - can_send_media_messages = not denied_permissions.send_media - can_send_audios = can_send_media_messages - can_send_documents = can_send_media_messages - can_send_photos = can_send_media_messages - can_send_videos = can_send_media_messages - can_send_video_notes = can_send_media_messages - can_send_voice_notes = can_send_media_messages + send_media = not denied_permissions.send_media + can_send_audios = send_media + can_send_documents = send_media + can_send_photos = send_media + can_send_videos = send_media + can_send_video_notes = send_media + can_send_voice_notes = send_media return ChatPermissions( - can_send_plain=can_send_plain, can_send_messages=can_send_messages, can_send_audios=can_send_audios, can_send_documents=can_send_documents, @@ -193,7 +195,6 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_invite_users=can_invite_users, can_pin_messages=can_pin_messages, can_manage_topics=can_manage_topics, - can_send_media_messages=can_send_media_messages ) def write( @@ -201,29 +202,80 @@ def write( use_independent_chat_permissions: bool, until_date: datetime = utils.zero_datetime() ) -> "raw.base.ChatBannedRights": - return raw.types.ChatBannedRights( - until_date=utils.datetime_to_timestamp(until_date), - send_messages=not permissions.can_send_messages, - send_media=not permissions.can_send_media_messages, - send_stickers=not permissions.can_send_other_messages, - send_gifs=not permissions.can_send_other_messages, - send_games=not permissions.can_send_other_messages, - send_inline=not permissions.can_send_other_messages, - embed_links=not permissions.can_add_web_page_previews, - send_polls=not permissions.can_send_polls, - change_info=not permissions.can_change_info, - invite_users=not permissions.can_invite_users, - pin_messages=not permissions.can_pin_messages, - manage_topics=( - permissions.can_manage_topics and - not permissions.can_manage_topics - ) or not permissions.can_pin_messages, - # view_messages=# TODO - send_audios=not permissions.can_send_audios,# TODO - send_docs=not permissions.can_send_documents,# TODO - send_photos=not permissions.can_send_photos,# TODO - send_videos=not permissions.can_send_videos,# TODO - send_roundvideos=not permissions.can_send_video_notes,# TODO - send_voices=not permissions.can_send_voice_notes,# TODO - send_plain=not permissions.can_send_plain# TODO + if use_independent_chat_permissions: + return raw.types.ChatBannedRights( + until_date=utils.datetime_to_timestamp(until_date), + send_messages=not permissions.can_send_messages, + send_media=False, + send_stickers=not permissions.can_send_other_messages, + send_gifs=not permissions.can_send_other_messages, + send_games=not permissions.can_send_other_messages, + send_inline=not permissions.can_send_other_messages, + embed_links=not permissions.can_add_web_page_previews, + send_polls=not permissions.can_send_polls, + change_info=not permissions.can_change_info, + invite_users=not permissions.can_invite_users, + pin_messages=not permissions.can_pin_messages, + manage_topics=( + permissions.can_manage_topics and + not permissions.can_manage_topics + ) or not permissions.can_pin_messages, + view_messages=False, + send_audios=not permissions.can_send_audios, + send_docs=not permissions.can_send_documents, + send_photos=not permissions.can_send_photos, + send_videos=not permissions.can_send_videos, + send_roundvideos=not permissions.can_send_video_notes, + send_voices=not permissions.can_send_voice_notes, + send_plain=not permissions.can_send_messages, + ) + else: + send_aidem = any([ + permissions.can_send_audios, + permissions.can_send_documents, + permissions.can_send_photos, + permissions.can_send_videos, + permissions.can_send_video_notes, + permissions.can_send_voice_notes, + ]) + return raw.types.ChatBannedRights( + until_date=utils.datetime_to_timestamp(until_date), + send_messages=False, + send_media=not send_aidem, + send_stickers=not permissions.can_send_other_messages, + send_gifs=not permissions.can_send_other_messages, + send_games=not permissions.can_send_other_messages, + send_inline=not permissions.can_send_other_messages, + embed_links=not permissions.can_add_web_page_previews, + send_polls=not permissions.can_send_polls, + change_info=not permissions.can_change_info, + invite_users=not permissions.can_invite_users, + pin_messages=not permissions.can_pin_messages, + manage_topics=( + permissions.can_manage_topics and + not permissions.can_manage_topics + ) or not permissions.can_pin_messages, + view_messages=False, + send_audios=not permissions.can_send_audios, + send_docs=not permissions.can_send_documents, + send_photos=not permissions.can_send_photos, + send_videos=not permissions.can_send_videos, + send_roundvideos=not permissions.can_send_video_notes, + send_voices=not permissions.can_send_voice_notes, + send_plain=not permissions.can_send_messages, + ) + + @property + def can_send_media_messages(permissions: "ChatPermissions"): + log.warning( + "can_send_media_messages property is deprecated. " + "Please use any of can_send_audios, can_send_documents, can_send_photos, can_send_videos, can_send_video_notes or can_send_voice_notes." ) + return any([ + permissions.can_send_audios, + permissions.can_send_documents, + permissions.can_send_photos, + permissions.can_send_videos, + permissions.can_send_video_notes, + permissions.can_send_voice_notes, + ]) From 4e9c7e56687c142584419b4e0204e518337b203b Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:51:31 +0300 Subject: [PATCH 10/13] Update get_chat_photos.py Fix (get_chat_photos): prevent duplicates and handle limit properly --- pyrogram/methods/users/get_chat_photos.py | 118 ++++++++-------------- 1 file changed, 42 insertions(+), 76 deletions(-) diff --git a/pyrogram/methods/users/get_chat_photos.py b/pyrogram/methods/users/get_chat_photos.py index 2ffd0b9bd4..0a13ac3a81 100644 --- a/pyrogram/methods/users/get_chat_photos.py +++ b/pyrogram/methods/users/get_chat_photos.py @@ -17,10 +17,9 @@ # along with Pyrogram. If not, see . from asyncio import sleep -from typing import Union, AsyncGenerator, Optional - +from typing import Union, AsyncGenerator import pyrogram -from pyrogram import types, raw +from pyrogram import types, raw, utils class GetChatPhotos: @@ -28,39 +27,14 @@ async def get_chat_photos( self: "pyrogram.Client", chat_id: Union[int, str], limit: int = 0, - ) -> Optional[ - Union[ - AsyncGenerator["types.Photo", None], - AsyncGenerator["types.Animation", None], - ] - ]: - """Get a chat or a user profile photos sequentially. - - .. include:: /_includes/usable-by/users-bots.rst - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - limit (``int``, *optional*): - Limits the number of profile photos to be retrieved. - By default, no limit is applied and all profile photos are returned. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` | :obj:`~pyrogram.types.Animation` objects. - - Example: - .. code-block:: python - - async for photo in app.get_chat_photos("me"): - print(photo) - """ + ) -> AsyncGenerator[Union["types.Photo", "types.Animation"], None]: + """Get a chat or a user profile photos sequentially, without duplicates.""" + total = limit or (1 << 31) - limit = min(100, total) + chunk_limit = min(100, total) peer_id = await self.resolve_peer(chat_id) + seen: set[str] = set() # зберігаємо file_unique_id if isinstance(peer_id, raw.types.InputPeerChannel): r = await self.invoke(raw.functions.channels.GetFullChannel(channel=peer_id)) @@ -70,52 +44,44 @@ async def get_chat_photos( chat_icons = [_animation or _photo] if not (self.me and self.me.is_bot): - r = await self.invoke( - raw.functions.messages.Search( - peer=peer_id, - q="", - filter=raw.types.InputMessagesFilterChatPhotos(), - min_date=0, - max_date=0, - offset_id=0, - add_offset=0, - limit=limit, - max_id=0, - min_id=0, - hash=0, - ) + r = await utils.parse_messages( + self, + await self.invoke( + raw.functions.messages.Search( + peer=peer_id, + q="", + filter=raw.types.InputMessagesFilterChatPhotos(), + min_date=0, + max_date=0, + offset_id=0, + add_offset=0, + limit=chunk_limit, + max_id=0, + min_id=0, + hash=0, + ) + ), ) - if _icon := chat_icons[0]: - _first_file_id = _icon.file_id if _animation else _icon.sizes[0].file_id - else: - _first_file_id = None - - for m in getattr(r, "messages", []): - if not isinstance(getattr(m, "action", None), raw.types.MessageActionChatEditPhoto): - continue - - _c_animation = types.Animation._parse_chat_animation(self, m.action.photo) - _c_photo = types.Photo._parse(self, m.action.photo) - - _current_file_id = (_c_animation and _c_animation.file_id) or (_c_photo and _c_photo.sizes[0].file_id) - - if (_c_animation or _c_photo) and _first_file_id != _current_file_id: - chat_icons.append(_c_animation or _c_photo) + for m in r: + if isinstance(m.new_chat_photo, (types.Animation, types.Photo)): + chat_icons.append(m.new_chat_photo) current = 0 - for icon in chat_icons: await sleep(0) - if not icon: continue - yield icon + if icon.file_unique_id in seen: + continue + seen.add(icon.file_unique_id) + yield icon current += 1 - if current >= limit: + if current >= total: return + else: current = 0 offset = 0 @@ -123,16 +89,14 @@ async def get_chat_photos( while True: r = await self.invoke( raw.functions.photos.GetUserPhotos( - user_id=peer_id, offset=offset, max_id=0, limit=limit + user_id=peer_id, offset=offset, max_id=0, limit=chunk_limit ) ) - photos = [] - for photo in r.photos: - photos.append( - types.Animation._parse_chat_animation(self, photo) - or types.Photo._parse(self, photo) - ) + photos = [ + types.Animation._parse_chat_animation(self, p) or types.Photo._parse(self, p) + for p in r.photos + ] if not photos: return @@ -141,12 +105,14 @@ async def get_chat_photos( for photo in photos: await sleep(0) - if not photo: continue - yield photo + if photo.file_unique_id in seen: + continue + seen.add(photo.file_unique_id) + yield photo current += 1 if current >= total: From a6c1b1939336fb73de23b003df32e6eb9aa97844 Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:29:46 +0300 Subject: [PATCH 11/13] Update get_chat_photos.py --- pyrogram/methods/users/get_chat_photos.py | 87 ++++++++++++++--------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/pyrogram/methods/users/get_chat_photos.py b/pyrogram/methods/users/get_chat_photos.py index 0a13ac3a81..f063e34023 100644 --- a/pyrogram/methods/users/get_chat_photos.py +++ b/pyrogram/methods/users/get_chat_photos.py @@ -18,67 +18,85 @@ from asyncio import sleep from typing import Union, AsyncGenerator -import pyrogram from pyrogram import types, raw, utils - class GetChatPhotos: async def get_chat_photos( self: "pyrogram.Client", chat_id: Union[int, str], limit: int = 0, ) -> AsyncGenerator[Union["types.Photo", "types.Animation"], None]: - """Get a chat or a user profile photos sequentially, without duplicates.""" + """Get a chat or a user profile photos sequentially. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + limit (``int``, *optional*): + Limits the number of profІile photos to be retrieved. + By default, no limit is applied and all profile photos are returned. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` | :obj:`~pyrogram.types.Animation` objects. + + Example: + .. code-block:: python + + async for photo in app.get_chat_photos("me"): + print(photo) + """ total = limit or (1 << 31) chunk_limit = min(100, total) peer_id = await self.resolve_peer(chat_id) - seen: set[str] = set() # зберігаємо file_unique_id + seen: set[str] = set() if isinstance(peer_id, raw.types.InputPeerChannel): r = await self.invoke(raw.functions.channels.GetFullChannel(channel=peer_id)) + chat_icons = [] _animation = types.Animation._parse_chat_animation(self, r.full_chat.chat_photo) _photo = types.Photo._parse(self, r.full_chat.chat_photo) - chat_icons = [_animation or _photo] + if _animation: + chat_icons.append(_animation) + elif _photo: + chat_icons.append(_photo) if not (self.me and self.me.is_bot): - r = await utils.parse_messages( - self, - await self.invoke( - raw.functions.messages.Search( - peer=peer_id, - q="", - filter=raw.types.InputMessagesFilterChatPhotos(), - min_date=0, - max_date=0, - offset_id=0, - add_offset=0, - limit=chunk_limit, - max_id=0, - min_id=0, - hash=0, - ) - ), + r_messages = await self.invoke( + raw.functions.messages.Search( + peer=peer_id, + q="", + filter=raw.types.InputMessagesFilterChatPhotos(), + min_date=0, + max_date=0, + offset_id=0, + add_offset=0, + limit=chunk_limit, + max_id=0, + min_id=0, + hash=0, + ) ) - for m in r: + messages = await utils.parse_messages(self, r_messages) + for m in messages: if isinstance(m.new_chat_photo, (types.Animation, types.Photo)): chat_icons.append(m.new_chat_photo) current = 0 for icon in chat_icons: await sleep(0) - if not icon: + if not icon or icon.file_unique_id in seen: continue - if icon.file_unique_id in seen: - continue seen.add(icon.file_unique_id) - yield icon current += 1 - if current >= total: return @@ -89,7 +107,10 @@ async def get_chat_photos( while True: r = await self.invoke( raw.functions.photos.GetUserPhotos( - user_id=peer_id, offset=offset, max_id=0, limit=chunk_limit + user_id=peer_id, + offset=offset, + max_id=0, + limit=chunk_limit ) ) @@ -98,6 +119,9 @@ async def get_chat_photos( for p in r.photos ] + # Фільтруємо None + photos = [p for p in photos if p] + if not photos: return @@ -105,15 +129,10 @@ async def get_chat_photos( for photo in photos: await sleep(0) - if not photo: - continue - if photo.file_unique_id in seen: continue seen.add(photo.file_unique_id) - yield photo current += 1 - if current >= total: return From 39267d798661db7f505e650bda4f3c48b5476e8d Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:57:37 +0300 Subject: [PATCH 12/13] Update chat_permissions.py --- .../types/user_and_chats/chat_permissions.py | 144 ++++++------------ 1 file changed, 45 insertions(+), 99 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py index bc0f779c0b..3c8473d73d 100644 --- a/pyrogram/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -16,13 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import logging from datetime import datetime from pyrogram import raw, utils from ..object import Object -log = logging.getLogger(__name__) - class ChatPermissions(Object): """Describes actions that a non-administrator user is allowed to take in a chat. @@ -73,11 +70,15 @@ class ChatPermissions(Object): True, if the user is allowed to create forum topics If omitted defaults to the value of can_pin_messages + can_send_media_messages (``bool``, *optional*): + True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes. + Implies *can_send_messages*. """ def __init__( self, *, + can_send_plain: bool = None, can_send_messages: bool = None, # Text, contacts, giveaways, giveaway winners, invoices, locations and venues can_send_audios: bool = None, can_send_documents: bool = None, @@ -92,9 +93,11 @@ def __init__( can_invite_users: bool = None, can_pin_messages: bool = None, can_manage_topics: bool = None, + can_send_media_messages: bool = None, # Audio files, documents, photos, videos, video notes and voice notes ): super().__init__(None) + self.can_send_plain = can_send_plain self.can_send_messages = can_send_messages self.can_send_audios = can_send_audios self.can_send_documents = can_send_documents @@ -109,9 +112,11 @@ def __init__( self.can_invite_users = can_invite_users self.can_pin_messages = can_pin_messages self.can_manage_topics = can_manage_topics + self.can_send_media_messages = can_send_media_messages @staticmethod def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions": + can_send_plain = False can_send_messages = False can_send_audios = False can_send_documents = False @@ -126,8 +131,10 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_invite_users = False can_pin_messages = False can_manage_topics = False + can_send_media_messages = False if isinstance(denied_permissions, raw.types.ChatBannedRights): + can_send_plain = not denied_permissions.send_plain can_send_messages = not denied_permissions.send_messages can_send_polls = not denied_permissions.send_polls can_send_other_messages = any([ @@ -158,29 +165,17 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_send_videos = not denied_permissions.send_videos can_send_video_notes = not denied_permissions.send_roundvideos can_send_voice_notes = not denied_permissions.send_voices - send_aidem = any([ - can_send_audios, - can_send_documents, - can_send_photos, - can_send_videos, - can_send_video_notes, - can_send_voice_notes, - ]) - if not send_aidem: - can_send_messages = not any([ - denied_permissions.send_messages, - denied_permissions.send_plain - ]) else: - send_media = not denied_permissions.send_media - can_send_audios = send_media - can_send_documents = send_media - can_send_photos = send_media - can_send_videos = send_media - can_send_video_notes = send_media - can_send_voice_notes = send_media + can_send_media_messages = not denied_permissions.send_media + can_send_audios = can_send_media_messages + can_send_documents = can_send_media_messages + can_send_photos = can_send_media_messages + can_send_videos = can_send_media_messages + can_send_video_notes = can_send_media_messages + can_send_voice_notes = can_send_media_messages return ChatPermissions( + can_send_plain=can_send_plain, can_send_messages=can_send_messages, can_send_audios=can_send_audios, can_send_documents=can_send_documents, @@ -195,6 +190,7 @@ def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions" can_invite_users=can_invite_users, can_pin_messages=can_pin_messages, can_manage_topics=can_manage_topics, + can_send_media_messages=can_send_media_messages ) def write( @@ -202,80 +198,30 @@ def write( use_independent_chat_permissions: bool, until_date: datetime = utils.zero_datetime() ) -> "raw.base.ChatBannedRights": - if use_independent_chat_permissions: - return raw.types.ChatBannedRights( - until_date=utils.datetime_to_timestamp(until_date), - send_messages=not permissions.can_send_messages, - send_media=False, - send_stickers=not permissions.can_send_other_messages, - send_gifs=not permissions.can_send_other_messages, - send_games=not permissions.can_send_other_messages, - send_inline=not permissions.can_send_other_messages, - embed_links=not permissions.can_add_web_page_previews, - send_polls=not permissions.can_send_polls, - change_info=not permissions.can_change_info, - invite_users=not permissions.can_invite_users, - pin_messages=not permissions.can_pin_messages, - manage_topics=( - permissions.can_manage_topics and - not permissions.can_manage_topics - ) or not permissions.can_pin_messages, - view_messages=False, - send_audios=not permissions.can_send_audios, - send_docs=not permissions.can_send_documents, - send_photos=not permissions.can_send_photos, - send_videos=not permissions.can_send_videos, - send_roundvideos=not permissions.can_send_video_notes, - send_voices=not permissions.can_send_voice_notes, - send_plain=not permissions.can_send_messages, - ) - else: - send_aidem = any([ - permissions.can_send_audios, - permissions.can_send_documents, - permissions.can_send_photos, - permissions.can_send_videos, - permissions.can_send_video_notes, - permissions.can_send_voice_notes, - ]) - return raw.types.ChatBannedRights( - until_date=utils.datetime_to_timestamp(until_date), - send_messages=False, - send_media=not send_aidem, - send_stickers=not permissions.can_send_other_messages, - send_gifs=not permissions.can_send_other_messages, - send_games=not permissions.can_send_other_messages, - send_inline=not permissions.can_send_other_messages, - embed_links=not permissions.can_add_web_page_previews, - send_polls=not permissions.can_send_polls, - change_info=not permissions.can_change_info, - invite_users=not permissions.can_invite_users, - pin_messages=not permissions.can_pin_messages, - manage_topics=( - permissions.can_manage_topics and - not permissions.can_manage_topics - ) or not permissions.can_pin_messages, - view_messages=False, - send_audios=not permissions.can_send_audios, - send_docs=not permissions.can_send_documents, - send_photos=not permissions.can_send_photos, - send_videos=not permissions.can_send_videos, - send_roundvideos=not permissions.can_send_video_notes, - send_voices=not permissions.can_send_voice_notes, - send_plain=not permissions.can_send_messages, - ) - - @property - def can_send_media_messages(permissions: "ChatPermissions"): - log.warning( - "can_send_media_messages property is deprecated. " - "Please use any of can_send_audios, can_send_documents, can_send_photos, can_send_videos, can_send_video_notes or can_send_voice_notes." + return raw.types.ChatBannedRights( + until_date=utils.datetime_to_timestamp(until_date), + send_messages=not permissions.can_send_messages, + send_media=not permissions.can_send_media_messages, + send_stickers=not permissions.can_send_other_messages, + send_gifs=not permissions.can_send_other_messages, + send_games=not permissions.can_send_other_messages, + send_inline=not permissions.can_send_other_messages, + embed_links=not permissions.can_add_web_page_previews, + send_polls=not permissions.can_send_polls, + change_info=not permissions.can_change_info, + invite_users=not permissions.can_invite_users, + pin_messages=not permissions.can_pin_messages, + manage_topics=( + permissions.can_manage_topics and + not permissions.can_manage_topics + ) or not permissions.can_pin_messages, + # view_messages=# TODO + send_audios=not permissions.can_send_audios,# TODO + send_docs=not permissions.can_send_documents,# TODO + send_photos=not permissions.can_send_photos,# TODO + send_videos=not permissions.can_send_videos,# TODO + send_roundvideos=not permissions.can_send_video_notes,# TODO + send_voices=not permissions.can_send_voice_notes,# TODO + send_plain=not permissions.can_send_plain# TODO + # send_plain=# TODO ) - return any([ - permissions.can_send_audios, - permissions.can_send_documents, - permissions.can_send_photos, - permissions.can_send_videos, - permissions.can_send_video_notes, - permissions.can_send_voice_notes, - ]) From 0b5282e8380b0b477efda511ba224e1fbe6c6a4d Mon Sep 17 00:00:00 2001 From: NickolaiH <48893302+WubbaLubbaDubDubDev@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:24:12 +0300 Subject: [PATCH 13/13] Update get_chat_photos.py --- pyrogram/methods/users/get_chat_photos.py | 44 ++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/pyrogram/methods/users/get_chat_photos.py b/pyrogram/methods/users/get_chat_photos.py index f063e34023..b29f8aa2de 100644 --- a/pyrogram/methods/users/get_chat_photos.py +++ b/pyrogram/methods/users/get_chat_photos.py @@ -37,7 +37,7 @@ async def get_chat_photos( For a contact that exists in your Telegram address book you can use his phone number (str). limit (``int``, *optional*): - Limits the number of profІile photos to be retrieved. + Limits the number of profile photos to be retrieved. By default, no limit is applied and all profile photos are returned. Returns: @@ -50,6 +50,14 @@ async def get_chat_photos( print(photo) """ + def get_unique_file_id(media_obj): + """Отримати унікальний file_id для медіа об'єкта""" + if isinstance(media_obj, types.Animation): + return media_obj.file_unique_id + elif isinstance(media_obj, types.Photo) and media_obj.sizes: + return media_obj.sizes[-1].file_unique_id + return None + total = limit or (1 << 31) chunk_limit = min(100, total) @@ -62,6 +70,7 @@ async def get_chat_photos( chat_icons = [] _animation = types.Animation._parse_chat_animation(self, r.full_chat.chat_photo) _photo = types.Photo._parse(self, r.full_chat.chat_photo) + if _animation: chat_icons.append(_animation) elif _photo: @@ -91,10 +100,15 @@ async def get_chat_photos( current = 0 for icon in chat_icons: await sleep(0) - if not icon or icon.file_unique_id in seen: + if not icon: + continue + + file_unique_id = get_unique_file_id(icon) + + if not file_unique_id or file_unique_id in seen: continue - seen.add(icon.file_unique_id) + seen.add(file_unique_id) yield icon current += 1 if current >= total: @@ -114,13 +128,15 @@ async def get_chat_photos( ) ) - photos = [ - types.Animation._parse_chat_animation(self, p) or types.Photo._parse(self, p) - for p in r.photos - ] - - # Фільтруємо None - photos = [p for p in photos if p] + photos = [] + for p in r.photos: + _animation = types.Animation._parse_chat_animation(self, p) + _photo = types.Photo._parse(self, p) + + if _animation: + photos.append(_animation) + elif _photo: + photos.append(_photo) if not photos: return @@ -129,9 +145,13 @@ async def get_chat_photos( for photo in photos: await sleep(0) - if photo.file_unique_id in seen: + + file_unique_id = get_unique_file_id(photo) + + if not file_unique_id or file_unique_id in seen: continue - seen.add(photo.file_unique_id) + + seen.add(file_unique_id) yield photo current += 1 if current >= total: