mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-02-05 06:01:02 +03:00
Separate chat requests from the TelegramClient
This commit is contained in:
parent
1e91e5a83c
commit
ad29f2f5b7
184
telethon/client/chats.py
Normal file
184
telethon/client/chats.py
Normal file
|
@ -0,0 +1,184 @@
|
|||
from collections import UserList
|
||||
|
||||
from .users import UserMethods
|
||||
from .. import utils
|
||||
from ..tl import types, functions
|
||||
|
||||
|
||||
class ChatMethods(UserMethods):
|
||||
|
||||
# region Public methods
|
||||
|
||||
async def iter_participants(
|
||||
self, entity, limit=None, search='',
|
||||
filter=None, aggressive=False, _total=None):
|
||||
"""
|
||||
Iterator over the participants belonging to the specified chat.
|
||||
|
||||
Args:
|
||||
entity (`entity`):
|
||||
The entity from which to retrieve the participants list.
|
||||
|
||||
limit (`int`):
|
||||
Limits amount of participants fetched.
|
||||
|
||||
search (`str`, optional):
|
||||
Look for participants with this string in name/username.
|
||||
|
||||
filter (:tl:`ChannelParticipantsFilter`, optional):
|
||||
The filter to be used, if you want e.g. only admins
|
||||
Note that you might not have permissions for some filter.
|
||||
This has no effect for normal chats or users.
|
||||
|
||||
aggressive (`bool`, optional):
|
||||
Aggressively looks for all participants in the chat in
|
||||
order to get more than 10,000 members (a hard limit
|
||||
imposed by Telegram). Note that this might take a long
|
||||
time (over 5 minutes), but is able to return over 90,000
|
||||
participants on groups with 100,000 members.
|
||||
|
||||
This has no effect for groups or channels with less than
|
||||
10,000 members, or if a ``filter`` is given.
|
||||
|
||||
_total (`list`, optional):
|
||||
A single-item list to pass the total parameter by reference.
|
||||
|
||||
Yields:
|
||||
The :tl:`User` objects returned by :tl:`GetParticipantsRequest`
|
||||
with an additional ``.participant`` attribute which is the
|
||||
matched :tl:`ChannelParticipant` type for channels/megagroups
|
||||
or :tl:`ChatParticipants` for normal chats.
|
||||
"""
|
||||
if isinstance(filter, type):
|
||||
if filter in (types.ChannelParticipantsBanned,
|
||||
types.ChannelParticipantsKicked,
|
||||
types.ChannelParticipantsSearch):
|
||||
# These require a `q` parameter (support types for convenience)
|
||||
filter = filter('')
|
||||
else:
|
||||
filter = filter()
|
||||
|
||||
entity = await self.get_input_entity(entity)
|
||||
if search and (filter
|
||||
or not isinstance(entity, types.InputPeerChannel)):
|
||||
# We need to 'search' ourselves unless we have a PeerChannel
|
||||
search = search.lower()
|
||||
|
||||
def filter_entity(ent):
|
||||
return search in utils.get_display_name(ent).lower() or\
|
||||
search in (getattr(ent, 'username', '') or None).lower()
|
||||
else:
|
||||
def filter_entity(ent):
|
||||
return True
|
||||
|
||||
limit = float('inf') if limit is None else int(limit)
|
||||
if isinstance(entity, types.InputPeerChannel):
|
||||
if _total or (aggressive and not filter):
|
||||
total = (await self(functions.channels.GetFullChannelRequest(
|
||||
entity
|
||||
))).full_chat.participants_count
|
||||
if _total:
|
||||
_total[0] = total
|
||||
else:
|
||||
total = 0
|
||||
|
||||
if limit == 0:
|
||||
return
|
||||
|
||||
seen = set()
|
||||
if total > 10000 and aggressive and not filter:
|
||||
requests = [functions.channels.GetParticipantsRequest(
|
||||
channel=entity,
|
||||
filter=types.ChannelParticipantsSearch(search + chr(x)),
|
||||
offset=0,
|
||||
limit=200,
|
||||
hash=0
|
||||
) for x in range(ord('a'), ord('z') + 1)]
|
||||
else:
|
||||
requests = [functions.channels.GetParticipantsRequest(
|
||||
channel=entity,
|
||||
filter=filter or types.ChannelParticipantsSearch(search),
|
||||
offset=0,
|
||||
limit=200,
|
||||
hash=0
|
||||
)]
|
||||
|
||||
while requests:
|
||||
# Only care about the limit for the first request
|
||||
# (small amount of people, won't be aggressive).
|
||||
#
|
||||
# Most people won't care about getting exactly 12,345
|
||||
# members so it doesn't really matter not to be 100%
|
||||
# precise with being out of the offset/limit here.
|
||||
requests[0].limit = min(limit - requests[0].offset, 200)
|
||||
if requests[0].offset > limit:
|
||||
break
|
||||
|
||||
results = await self(requests)
|
||||
for i in reversed(range(len(requests))):
|
||||
participants = results[i]
|
||||
if not participants.users:
|
||||
requests.pop(i)
|
||||
else:
|
||||
requests[i].offset += len(participants.participants)
|
||||
users = {user.id: user for user in participants.users}
|
||||
for participant in participants.participants:
|
||||
user = users[participant.user_id]
|
||||
if not filter_entity(user) or user.id in seen:
|
||||
continue
|
||||
|
||||
seen.add(participant.user_id)
|
||||
user = users[participant.user_id]
|
||||
user.participant = participant
|
||||
yield user
|
||||
if len(seen) >= limit:
|
||||
return
|
||||
|
||||
elif isinstance(entity, types.InputPeerChat):
|
||||
# TODO We *could* apply the `filter` here ourselves
|
||||
full = await self(
|
||||
functions.messages.GetFullChatRequest(entity.chat_id))
|
||||
if not isinstance(
|
||||
full.full_chat.participants, types.ChatParticipants):
|
||||
# ChatParticipantsForbidden won't have ``.participants``
|
||||
_total[0] = 0
|
||||
return
|
||||
|
||||
if _total:
|
||||
_total[0] = len(full.full_chat.participants.participants)
|
||||
|
||||
have = 0
|
||||
users = {user.id: user for user in full.users}
|
||||
for participant in full.full_chat.participants.participants:
|
||||
user = users[participant.user_id]
|
||||
if not filter_entity(user):
|
||||
continue
|
||||
have += 1
|
||||
if have > limit:
|
||||
break
|
||||
else:
|
||||
user = users[participant.user_id]
|
||||
user.participant = participant
|
||||
yield user
|
||||
else:
|
||||
if _total:
|
||||
_total[0] = 1
|
||||
if limit != 0:
|
||||
user = await self.get_entity(entity)
|
||||
if filter_entity(user):
|
||||
user.participant = None
|
||||
yield user
|
||||
|
||||
async def get_participants(self, *args, **kwargs):
|
||||
"""
|
||||
Same as :meth:`iter_participants`, but returns a list instead
|
||||
with an additional ``.total`` attribute on the list.
|
||||
"""
|
||||
total = [0]
|
||||
kwargs['_total'] = total
|
||||
participants = UserList(x async for x in
|
||||
self.iter_participants(*args, **kwargs))
|
||||
participants.total = total[0]
|
||||
return participants
|
||||
|
||||
# endregion
|
|
@ -1,22 +1,15 @@
|
|||
import getpass
|
||||
import hashlib
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from collections import UserList
|
||||
from io import BytesIO
|
||||
from mimetypes import guess_type
|
||||
|
||||
from ..crypto import CdnDecrypter
|
||||
from ..tl.custom import InputSizedFile
|
||||
from ..tl.functions.help import AcceptTermsOfServiceRequest
|
||||
from ..tl.functions.updates import GetDifferenceRequest
|
||||
from ..tl.functions.upload import (
|
||||
SaveBigFilePartRequest, SaveFilePartRequest, GetFileRequest
|
||||
GetFileRequest
|
||||
)
|
||||
from ..tl.types.updates import (
|
||||
DifferenceSlice, DifferenceEmpty, Difference, DifferenceTooLong
|
||||
|
@ -43,7 +36,6 @@ from ..errors import (
|
|||
SessionPasswordNeededError, FileMigrateError, PhoneNumberUnoccupiedError,
|
||||
PhoneNumberOccupiedError
|
||||
)
|
||||
from ..tl.custom import Draft, Dialog
|
||||
from ..tl.functions.account import (
|
||||
GetPasswordRequest, UpdatePasswordSettingsRequest
|
||||
)
|
||||
|
@ -51,41 +43,19 @@ from ..tl.functions.auth import (
|
|||
CheckPasswordRequest, LogOutRequest, SendCodeRequest, SignInRequest,
|
||||
SignUpRequest, ResendCodeRequest, ImportBotAuthorizationRequest
|
||||
)
|
||||
from ..tl.functions.messages import (
|
||||
GetDialogsRequest, GetHistoryRequest, SendMediaRequest,
|
||||
SendMessageRequest, GetAllDraftsRequest,
|
||||
ReadMentionsRequest, SendMultiMediaRequest,
|
||||
UploadMediaRequest, EditMessageRequest, GetFullChatRequest,
|
||||
ForwardMessagesRequest, SearchRequest
|
||||
)
|
||||
|
||||
from ..tl.functions import channels
|
||||
from ..tl.functions import messages
|
||||
|
||||
from ..tl.functions.channels import (
|
||||
GetFullChannelRequest, GetParticipantsRequest
|
||||
GetFullChannelRequest
|
||||
)
|
||||
from ..tl.types import (
|
||||
DocumentAttributeAudio, DocumentAttributeFilename,
|
||||
InputMediaUploadedDocument, InputMediaUploadedPhoto, InputPeerEmpty,
|
||||
Message, MessageMediaContact, MessageMediaDocument, MessageMediaPhoto,
|
||||
UserProfilePhoto, ChatPhoto, UpdateMessageID,
|
||||
UpdateNewChannelMessage, UpdateNewMessage, UpdateShortSentMessage,
|
||||
PeerUser, InputPeerChat, InputPeerChannel, MessageEmpty,
|
||||
Photo, InputSingleMedia, InputMediaPhoto, InputPhoto, InputFile, InputFileBig,
|
||||
InputDocument, InputMediaDocument, Document, MessageEntityTextUrl,
|
||||
InputMessageEntityMentionName, DocumentAttributeVideo,
|
||||
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
||||
MessageMediaWebPage, ChannelParticipantsSearch, PhotoSize, PhotoCachedSize,
|
||||
PhotoSizeEmpty, MessageService, ChatParticipants, WebPage,
|
||||
ChannelParticipantsBanned, ChannelParticipantsKicked,
|
||||
InputMessagesFilterEmpty, UpdatesCombined
|
||||
UserProfilePhoto, ChatPhoto, UpdateNewMessage, InputPeerChannel, Photo,
|
||||
Document, Updates,
|
||||
MessageMediaWebPage, PhotoSize, PhotoCachedSize,
|
||||
PhotoSizeEmpty, WebPage
|
||||
)
|
||||
from ..tl.types.messages import DialogsSlice, MessagesNotModified
|
||||
from ..tl.types.account import PasswordInputSettings, NoPassword
|
||||
from ..tl import custom
|
||||
from ..utils import Default
|
||||
from ..extensions import markdown, html
|
||||
|
||||
__log__ = logging.getLogger(__name__)
|
||||
import os
|
||||
|
@ -429,180 +399,6 @@ class TelegramClient(TelegramBaseClient):
|
|||
|
||||
# endregion
|
||||
|
||||
# region Dialogs ("chats") requests
|
||||
|
||||
def iter_participants(self, entity, limit=None, search='',
|
||||
filter=None, aggressive=False, _total=None):
|
||||
"""
|
||||
Iterator over the participants belonging to the specified chat.
|
||||
|
||||
Args:
|
||||
entity (`entity`):
|
||||
The entity from which to retrieve the participants list.
|
||||
|
||||
limit (`int`):
|
||||
Limits amount of participants fetched.
|
||||
|
||||
search (`str`, optional):
|
||||
Look for participants with this string in name/username.
|
||||
|
||||
filter (:tl:`ChannelParticipantsFilter`, optional):
|
||||
The filter to be used, if you want e.g. only admins
|
||||
Note that you might not have permissions for some filter.
|
||||
This has no effect for normal chats or users.
|
||||
|
||||
aggressive (`bool`, optional):
|
||||
Aggressively looks for all participants in the chat in
|
||||
order to get more than 10,000 members (a hard limit
|
||||
imposed by Telegram). Note that this might take a long
|
||||
time (over 5 minutes), but is able to return over 90,000
|
||||
participants on groups with 100,000 members.
|
||||
|
||||
This has no effect for groups or channels with less than
|
||||
10,000 members, or if a ``filter`` is given.
|
||||
|
||||
_total (`list`, optional):
|
||||
A single-item list to pass the total parameter by reference.
|
||||
|
||||
Yields:
|
||||
The :tl:`User` objects returned by :tl:`GetParticipantsRequest`
|
||||
with an additional ``.participant`` attribute which is the
|
||||
matched :tl:`ChannelParticipant` type for channels/megagroups
|
||||
or :tl:`ChatParticipants` for normal chats.
|
||||
"""
|
||||
if isinstance(filter, type):
|
||||
if filter in (ChannelParticipantsBanned, ChannelParticipantsKicked,
|
||||
ChannelParticipantsSearch):
|
||||
# These require a `q` parameter (support types for convenience)
|
||||
filter = filter('')
|
||||
else:
|
||||
filter = filter()
|
||||
|
||||
entity = self.get_input_entity(entity)
|
||||
if search and (filter or not isinstance(entity, InputPeerChannel)):
|
||||
# We need to 'search' ourselves unless we have a PeerChannel
|
||||
search = search.lower()
|
||||
|
||||
def filter_entity(ent):
|
||||
return search in utils.get_display_name(ent).lower() or\
|
||||
search in (getattr(ent, 'username', '') or None).lower()
|
||||
else:
|
||||
def filter_entity(ent):
|
||||
return True
|
||||
|
||||
limit = float('inf') if limit is None else int(limit)
|
||||
if isinstance(entity, InputPeerChannel):
|
||||
if _total or (aggressive and not filter):
|
||||
total = self(GetFullChannelRequest(
|
||||
entity
|
||||
)).full_chat.participants_count
|
||||
if _total:
|
||||
_total[0] = total
|
||||
else:
|
||||
total = 0
|
||||
|
||||
if limit == 0:
|
||||
return
|
||||
|
||||
seen = set()
|
||||
if total > 10000 and aggressive and not filter:
|
||||
requests = [GetParticipantsRequest(
|
||||
channel=entity,
|
||||
filter=ChannelParticipantsSearch(search + chr(x)),
|
||||
offset=0,
|
||||
limit=200,
|
||||
hash=0
|
||||
) for x in range(ord('a'), ord('z') + 1)]
|
||||
else:
|
||||
requests = [GetParticipantsRequest(
|
||||
channel=entity,
|
||||
filter=filter or ChannelParticipantsSearch(search),
|
||||
offset=0,
|
||||
limit=200,
|
||||
hash=0
|
||||
)]
|
||||
|
||||
while requests:
|
||||
# Only care about the limit for the first request
|
||||
# (small amount of people, won't be aggressive).
|
||||
#
|
||||
# Most people won't care about getting exactly 12,345
|
||||
# members so it doesn't really matter not to be 100%
|
||||
# precise with being out of the offset/limit here.
|
||||
requests[0].limit = min(limit - requests[0].offset, 200)
|
||||
if requests[0].offset > limit:
|
||||
break
|
||||
|
||||
results = self(requests)
|
||||
for i in reversed(range(len(requests))):
|
||||
participants = results[i]
|
||||
if not participants.users:
|
||||
requests.pop(i)
|
||||
else:
|
||||
requests[i].offset += len(participants.participants)
|
||||
users = {user.id: user for user in participants.users}
|
||||
for participant in participants.participants:
|
||||
user = users[participant.user_id]
|
||||
if not filter_entity(user) or user.id in seen:
|
||||
continue
|
||||
|
||||
seen.add(participant.user_id)
|
||||
user = users[participant.user_id]
|
||||
user.participant = participant
|
||||
yield user
|
||||
if len(seen) >= limit:
|
||||
return
|
||||
|
||||
elif isinstance(entity, InputPeerChat):
|
||||
# TODO We *could* apply the `filter` here ourselves
|
||||
full = self(GetFullChatRequest(entity.chat_id))
|
||||
if not isinstance(full.full_chat.participants, ChatParticipants):
|
||||
# ChatParticipantsForbidden won't have ``.participants``
|
||||
_total[0] = 0
|
||||
return
|
||||
|
||||
if _total:
|
||||
_total[0] = len(full.full_chat.participants.participants)
|
||||
|
||||
have = 0
|
||||
users = {user.id: user for user in full.users}
|
||||
for participant in full.full_chat.participants.participants:
|
||||
user = users[participant.user_id]
|
||||
if not filter_entity(user):
|
||||
continue
|
||||
have += 1
|
||||
if have > limit:
|
||||
break
|
||||
else:
|
||||
user = users[participant.user_id]
|
||||
user.participant = participant
|
||||
yield user
|
||||
else:
|
||||
if _total:
|
||||
_total[0] = 1
|
||||
if limit != 0:
|
||||
user = self.get_entity(entity)
|
||||
if filter_entity(user):
|
||||
user.participant = None
|
||||
yield user
|
||||
|
||||
def get_participants(self, *args, **kwargs):
|
||||
"""
|
||||
Same as :meth:`iter_participants`, but returns a list instead
|
||||
with an additional ``.total`` attribute on the list.
|
||||
"""
|
||||
total = [0]
|
||||
kwargs['_total'] = total
|
||||
participants = UserList(self.iter_participants(*args, **kwargs))
|
||||
participants.total = total[0]
|
||||
return participants
|
||||
|
||||
# endregion
|
||||
|
||||
# region Uploading files
|
||||
|
||||
# endregion
|
||||
|
||||
# region Downloading media requests
|
||||
|
||||
def download_profile_photo(self, entity, file=None, download_big=True):
|
||||
|
|
Loading…
Reference in New Issue
Block a user