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: