2019-04-13 11:37:23 +03:00
|
|
|
import asyncio
|
2019-09-08 11:56:35 +03:00
|
|
|
import inspect
|
2019-01-03 15:09:59 +03:00
|
|
|
import itertools
|
2019-02-27 14:51:09 +03:00
|
|
|
import string
|
2019-05-30 17:51:19 +03:00
|
|
|
import typing
|
2018-06-10 22:50:28 +03:00
|
|
|
|
2021-09-12 14:27:13 +03:00
|
|
|
from .. import hints, errors, _tl
|
2021-09-12 17:58:06 +03:00
|
|
|
from .._misc import helpers, utils, requestiter, tlobject
|
|
|
|
from ..types import _custom
|
2018-06-09 23:13:00 +03:00
|
|
|
|
2019-05-03 22:37:27 +03:00
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
from .telegramclient import TelegramClient
|
|
|
|
|
2019-05-30 17:51:19 +03:00
|
|
|
_MAX_PARTICIPANTS_CHUNK_SIZE = 200
|
|
|
|
_MAX_ADMIN_LOG_CHUNK_SIZE = 100
|
|
|
|
_MAX_PROFILE_PHOTO_CHUNK_SIZE = 100
|
|
|
|
|
2018-06-09 23:13:00 +03:00
|
|
|
|
2019-04-13 11:37:23 +03:00
|
|
|
class _ChatAction:
|
|
|
|
_str_mapping = {
|
2021-09-12 13:16:02 +03:00
|
|
|
'typing': _tl.SendMessageTypingAction(),
|
|
|
|
'contact': _tl.SendMessageChooseContactAction(),
|
|
|
|
'game': _tl.SendMessageGamePlayAction(),
|
|
|
|
'location': _tl.SendMessageGeoLocationAction(),
|
|
|
|
'sticker': _tl.SendMessageChooseStickerAction(),
|
|
|
|
|
|
|
|
'record-audio': _tl.SendMessageRecordAudioAction(),
|
|
|
|
'record-voice': _tl.SendMessageRecordAudioAction(), # alias
|
|
|
|
'record-round': _tl.SendMessageRecordRoundAction(),
|
|
|
|
'record-video': _tl.SendMessageRecordVideoAction(),
|
|
|
|
|
|
|
|
'audio': _tl.SendMessageUploadAudioAction(1),
|
|
|
|
'voice': _tl.SendMessageUploadAudioAction(1), # alias
|
|
|
|
'song': _tl.SendMessageUploadAudioAction(1), # alias
|
|
|
|
'round': _tl.SendMessageUploadRoundAction(1),
|
|
|
|
'video': _tl.SendMessageUploadVideoAction(1),
|
|
|
|
|
|
|
|
'photo': _tl.SendMessageUploadPhotoAction(1),
|
|
|
|
'document': _tl.SendMessageUploadDocumentAction(1),
|
|
|
|
'file': _tl.SendMessageUploadDocumentAction(1), # alias
|
|
|
|
|
|
|
|
'cancel': _tl.SendMessageCancelAction()
|
2019-04-13 11:37:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, client, chat, action, *, delay, auto_cancel):
|
|
|
|
self._client = client
|
|
|
|
self._chat = chat
|
|
|
|
self._action = action
|
|
|
|
self._delay = delay
|
|
|
|
self._auto_cancel = auto_cancel
|
|
|
|
self._request = None
|
|
|
|
self._task = None
|
|
|
|
self._running = False
|
|
|
|
|
|
|
|
async def __aenter__(self):
|
|
|
|
self._chat = await self._client.get_input_entity(self._chat)
|
|
|
|
|
|
|
|
# Since `self._action` is passed by reference we can avoid
|
|
|
|
# recreating the request all the time and still modify
|
|
|
|
# `self._action.progress` directly in `progress`.
|
2021-09-12 13:16:02 +03:00
|
|
|
self._request = _tl.fn.messages.SetTyping(
|
2019-04-13 11:37:23 +03:00
|
|
|
self._chat, self._action)
|
|
|
|
|
|
|
|
self._running = True
|
|
|
|
self._task = self._client.loop.create_task(self._update())
|
2019-07-13 22:20:51 +03:00
|
|
|
return self
|
2019-04-13 11:37:23 +03:00
|
|
|
|
|
|
|
async def __aexit__(self, *args):
|
|
|
|
self._running = False
|
|
|
|
if self._task:
|
|
|
|
self._task.cancel()
|
|
|
|
try:
|
|
|
|
await self._task
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
self._task = None
|
|
|
|
|
|
|
|
async def _update(self):
|
|
|
|
try:
|
|
|
|
while self._running:
|
|
|
|
await self._client(self._request)
|
|
|
|
await asyncio.sleep(self._delay)
|
|
|
|
except ConnectionError:
|
|
|
|
pass
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
if self._auto_cancel:
|
2021-09-12 13:16:02 +03:00
|
|
|
await self._client(_tl.fn.messages.SetTyping(
|
|
|
|
self._chat, _tl.SendMessageCancelAction()))
|
2019-04-13 11:37:23 +03:00
|
|
|
|
|
|
|
def progress(self, current, total):
|
|
|
|
if hasattr(self._action, 'progress'):
|
|
|
|
self._action.progress = 100 * round(current / total)
|
|
|
|
|
|
|
|
|
2021-09-12 14:27:13 +03:00
|
|
|
class _ParticipantsIter(requestiter.RequestIter):
|
2019-02-27 13:12:05 +03:00
|
|
|
async def _init(self, entity, filter, search, aggressive):
|
|
|
|
if isinstance(filter, type):
|
2021-09-12 13:16:02 +03:00
|
|
|
if filter in (_tl.ChannelParticipantsBanned,
|
|
|
|
_tl.ChannelParticipantsKicked,
|
|
|
|
_tl.ChannelParticipantsSearch,
|
|
|
|
_tl.ChannelParticipantsContacts):
|
2019-02-27 13:12:05 +03:00
|
|
|
# These require a `q` parameter (support types for convenience)
|
|
|
|
filter = filter('')
|
|
|
|
else:
|
|
|
|
filter = filter()
|
|
|
|
|
|
|
|
entity = await self.client.get_input_entity(entity)
|
2019-12-23 15:52:07 +03:00
|
|
|
ty = helpers._entity_type(entity)
|
|
|
|
if search and (filter or ty != helpers._EntityType.CHANNEL):
|
2019-02-27 13:12:05 +03:00
|
|
|
# We need to 'search' ourselves unless we have a PeerChannel
|
2019-05-20 15:02:51 +03:00
|
|
|
search = search.casefold()
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
self.filter_entity = lambda ent: (
|
2019-05-20 15:02:51 +03:00
|
|
|
search in utils.get_display_name(ent).casefold() or
|
|
|
|
search in (getattr(ent, 'username', None) or '').casefold()
|
2019-02-27 13:12:05 +03:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.filter_entity = lambda ent: True
|
|
|
|
|
2019-03-02 23:17:36 +03:00
|
|
|
# Only used for channels, but we should always set the attribute
|
|
|
|
self.requests = []
|
|
|
|
|
2019-12-23 15:52:07 +03:00
|
|
|
if ty == helpers._EntityType.CHANNEL:
|
2019-02-27 15:01:04 +03:00
|
|
|
if self.limit <= 0:
|
2021-07-09 21:08:04 +03:00
|
|
|
# May not have access to the channel, but getFull can get the .total.
|
|
|
|
self.total = (await self.client(
|
2021-09-12 13:16:02 +03:00
|
|
|
_tl.fn.channels.GetFullChannel(entity)
|
2021-07-09 21:08:04 +03:00
|
|
|
)).full_chat.participants_count
|
2019-02-27 13:12:05 +03:00
|
|
|
raise StopAsyncIteration
|
|
|
|
|
|
|
|
self.seen = set()
|
|
|
|
if aggressive and not filter:
|
2021-09-12 13:16:02 +03:00
|
|
|
self.requests.extend(_tl.fn.channels.GetParticipants(
|
2019-02-27 13:12:05 +03:00
|
|
|
channel=entity,
|
2021-09-12 13:16:02 +03:00
|
|
|
filter=_tl.ChannelParticipantsSearch(x),
|
2019-02-27 13:12:05 +03:00
|
|
|
offset=0,
|
2019-02-27 15:07:25 +03:00
|
|
|
limit=_MAX_PARTICIPANTS_CHUNK_SIZE,
|
2019-02-27 13:12:05 +03:00
|
|
|
hash=0
|
2019-03-02 23:17:36 +03:00
|
|
|
) for x in (search or string.ascii_lowercase))
|
2019-02-27 13:12:05 +03:00
|
|
|
else:
|
2021-09-12 13:16:02 +03:00
|
|
|
self.requests.append(_tl.fn.channels.GetParticipants(
|
2019-02-27 13:12:05 +03:00
|
|
|
channel=entity,
|
2021-09-12 13:16:02 +03:00
|
|
|
filter=filter or _tl.ChannelParticipantsSearch(search),
|
2019-02-27 13:12:05 +03:00
|
|
|
offset=0,
|
2019-02-27 15:07:25 +03:00
|
|
|
limit=_MAX_PARTICIPANTS_CHUNK_SIZE,
|
2019-02-27 13:12:05 +03:00
|
|
|
hash=0
|
2019-03-02 23:17:36 +03:00
|
|
|
))
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2019-12-23 15:52:07 +03:00
|
|
|
elif ty == helpers._EntityType.CHAT:
|
2019-02-27 13:12:05 +03:00
|
|
|
full = await self.client(
|
2021-09-12 13:16:02 +03:00
|
|
|
_tl.fn.messages.GetFullChat(entity.chat_id))
|
2019-02-27 13:12:05 +03:00
|
|
|
if not isinstance(
|
2021-09-12 13:16:02 +03:00
|
|
|
full.full_chat.participants, _tl.ChatParticipants):
|
2019-02-27 13:12:05 +03:00
|
|
|
# ChatParticipantsForbidden won't have ``.participants``
|
|
|
|
self.total = 0
|
|
|
|
raise StopAsyncIteration
|
|
|
|
|
|
|
|
self.total = len(full.full_chat.participants.participants)
|
|
|
|
|
|
|
|
users = {user.id: user for user in full.users}
|
|
|
|
for participant in full.full_chat.participants.participants:
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(participant, _tl.ChannelParticipantBanned):
|
2021-06-15 23:57:32 +03:00
|
|
|
user_id = participant.peer.user_id
|
|
|
|
else:
|
|
|
|
user_id = participant.user_id
|
|
|
|
user = users[user_id]
|
2019-02-27 13:12:05 +03:00
|
|
|
if not self.filter_entity(user):
|
|
|
|
continue
|
|
|
|
|
2021-06-15 23:57:32 +03:00
|
|
|
user = users[user_id]
|
2019-02-27 13:12:05 +03:00
|
|
|
user.participant = participant
|
2019-02-27 13:24:47 +03:00
|
|
|
self.buffer.append(user)
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2019-02-27 13:24:47 +03:00
|
|
|
return True
|
2019-02-27 13:12:05 +03:00
|
|
|
else:
|
|
|
|
self.total = 1
|
|
|
|
if self.limit != 0:
|
|
|
|
user = await self.client.get_entity(entity)
|
|
|
|
if self.filter_entity(user):
|
|
|
|
user.participant = None
|
2019-02-27 13:24:47 +03:00
|
|
|
self.buffer.append(user)
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2019-02-27 13:24:47 +03:00
|
|
|
return True
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
async def _load_next_chunk(self):
|
|
|
|
if not self.requests:
|
2019-02-27 13:24:47 +03:00
|
|
|
return True
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
# 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.
|
2019-02-27 15:07:25 +03:00
|
|
|
self.requests[0].limit = min(
|
|
|
|
self.limit - self.requests[0].offset, _MAX_PARTICIPANTS_CHUNK_SIZE)
|
|
|
|
|
2019-02-27 13:12:05 +03:00
|
|
|
if self.requests[0].offset > self.limit:
|
2019-02-27 13:24:47 +03:00
|
|
|
return True
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2021-07-09 21:08:04 +03:00
|
|
|
if self.total is None:
|
|
|
|
f = self.requests[0].filter
|
|
|
|
if len(self.requests) > 1 or (
|
2021-09-12 13:16:02 +03:00
|
|
|
not isinstance(f, _tl.ChannelParticipantsRecent)
|
|
|
|
and (not isinstance(f, _tl.ChannelParticipantsSearch) or f.q)
|
2021-07-09 21:08:04 +03:00
|
|
|
):
|
|
|
|
# Only do an additional getParticipants here to get the total
|
|
|
|
# if there's a filter which would reduce the real total number.
|
|
|
|
# getParticipants is cheaper than getFull.
|
2021-09-12 13:16:02 +03:00
|
|
|
self.total = (await self.client(_tl.fn.channels.GetParticipants(
|
2021-07-09 21:08:04 +03:00
|
|
|
channel=self.requests[0].channel,
|
2021-09-12 13:16:02 +03:00
|
|
|
filter=_tl.ChannelParticipantsRecent(),
|
2021-07-09 21:08:04 +03:00
|
|
|
offset=0,
|
|
|
|
limit=1,
|
|
|
|
hash=0
|
|
|
|
))).count
|
|
|
|
|
2019-02-27 13:12:05 +03:00
|
|
|
results = await self.client(self.requests)
|
|
|
|
for i in reversed(range(len(self.requests))):
|
|
|
|
participants = results[i]
|
2021-07-09 21:08:04 +03:00
|
|
|
if self.total is None:
|
|
|
|
# Will only get here if there was one request with a filter that matched all users.
|
|
|
|
self.total = participants.count
|
2019-02-27 13:12:05 +03:00
|
|
|
if not participants.users:
|
|
|
|
self.requests.pop(i)
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.requests[i].offset += len(participants.participants)
|
|
|
|
users = {user.id: user for user in participants.users}
|
|
|
|
for participant in participants.participants:
|
2021-06-15 23:57:32 +03:00
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(participant, _tl.ChannelParticipantBanned):
|
|
|
|
if not isinstance(participant.peer, _tl.PeerUser):
|
2021-08-22 14:24:02 +03:00
|
|
|
# May have the entire channel banned. See #3105.
|
|
|
|
continue
|
2021-06-15 23:57:32 +03:00
|
|
|
user_id = participant.peer.user_id
|
|
|
|
else:
|
|
|
|
user_id = participant.user_id
|
|
|
|
|
|
|
|
user = users[user_id]
|
2019-02-27 13:12:05 +03:00
|
|
|
if not self.filter_entity(user) or user.id in self.seen:
|
|
|
|
continue
|
2021-06-15 23:57:32 +03:00
|
|
|
self.seen.add(user_id)
|
|
|
|
user = users[user_id]
|
2019-02-27 13:12:05 +03:00
|
|
|
user.participant = participant
|
2019-02-27 13:24:47 +03:00
|
|
|
self.buffer.append(user)
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
|
2021-09-12 14:27:13 +03:00
|
|
|
class _AdminLogIter(requestiter.RequestIter):
|
2019-02-27 13:12:05 +03:00
|
|
|
async def _init(
|
|
|
|
self, entity, admins, search, min_id, max_id,
|
|
|
|
join, leave, invite, restrict, unrestrict, ban, unban,
|
2020-12-11 18:55:49 +03:00
|
|
|
promote, demote, info, settings, pinned, edit, delete,
|
|
|
|
group_call
|
2019-02-27 13:12:05 +03:00
|
|
|
):
|
|
|
|
if any((join, leave, invite, restrict, unrestrict, ban, unban,
|
2020-12-11 18:55:49 +03:00
|
|
|
promote, demote, info, settings, pinned, edit, delete,
|
|
|
|
group_call)):
|
2021-09-12 13:16:02 +03:00
|
|
|
events_filter = _tl.ChannelAdminLogEventsFilter(
|
2019-02-27 13:12:05 +03:00
|
|
|
join=join, leave=leave, invite=invite, ban=restrict,
|
|
|
|
unban=unrestrict, kick=ban, unkick=unban, promote=promote,
|
|
|
|
demote=demote, info=info, settings=settings, pinned=pinned,
|
2020-12-11 18:55:49 +03:00
|
|
|
edit=edit, delete=delete, group_call=group_call
|
2019-02-27 13:12:05 +03:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
events_filter = None
|
|
|
|
|
|
|
|
self.entity = await self.client.get_input_entity(entity)
|
|
|
|
|
|
|
|
admin_list = []
|
|
|
|
if admins:
|
|
|
|
if not utils.is_list_like(admins):
|
|
|
|
admins = (admins,)
|
|
|
|
|
|
|
|
for admin in admins:
|
|
|
|
admin_list.append(await self.client.get_input_entity(admin))
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
self.request = _tl.fn.channels.GetAdminLog(
|
2019-02-27 13:12:05 +03:00
|
|
|
self.entity, q=search or '', min_id=min_id, max_id=max_id,
|
|
|
|
limit=0, events_filter=events_filter, admins=admin_list or None
|
|
|
|
)
|
|
|
|
|
|
|
|
async def _load_next_chunk(self):
|
2019-02-27 15:07:25 +03:00
|
|
|
self.request.limit = min(self.left, _MAX_ADMIN_LOG_CHUNK_SIZE)
|
2019-02-27 13:12:05 +03:00
|
|
|
r = await self.client(self.request)
|
|
|
|
entities = {utils.get_peer_id(x): x
|
|
|
|
for x in itertools.chain(r.users, r.chats)}
|
|
|
|
|
|
|
|
self.request.max_id = min((e.id for e in r.events), default=0)
|
|
|
|
for ev in r.events:
|
|
|
|
if isinstance(ev.action,
|
2021-09-12 13:16:02 +03:00
|
|
|
_tl.ChannelAdminLogEventActionEditMessage):
|
2021-09-13 21:37:29 +03:00
|
|
|
ev.action.prev_message = _custom.Message._new(
|
|
|
|
self.client, ev.action.prev_message, entities, self.entity)
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2021-09-13 21:37:29 +03:00
|
|
|
ev.action.new_message = _custom.Message._new(
|
|
|
|
self.client, ev.action.new_message, entities, self.entity)
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
elif isinstance(ev.action,
|
2021-09-12 13:16:02 +03:00
|
|
|
_tl.ChannelAdminLogEventActionDeleteMessage):
|
2021-09-13 21:37:29 +03:00
|
|
|
ev.action.message = _custom.Message._new(
|
|
|
|
self.client, ev.action.message, entities, self.entity)
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2021-09-12 17:58:06 +03:00
|
|
|
self.buffer.append(_custom.AdminLogEvent(ev, entities))
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
if len(r.events) < self.request.limit:
|
2019-02-27 13:24:47 +03:00
|
|
|
return True
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
|
2021-09-12 14:27:13 +03:00
|
|
|
class _ProfilePhotoIter(requestiter.RequestIter):
|
2019-05-30 17:51:19 +03:00
|
|
|
async def _init(
|
|
|
|
self, entity, offset, max_id
|
|
|
|
):
|
|
|
|
entity = await self.client.get_input_entity(entity)
|
2019-12-23 15:52:07 +03:00
|
|
|
ty = helpers._entity_type(entity)
|
|
|
|
if ty == helpers._EntityType.USER:
|
2021-09-12 13:16:02 +03:00
|
|
|
self.request = _tl.fn.photos.GetUserPhotos(
|
2019-05-30 17:51:19 +03:00
|
|
|
entity,
|
|
|
|
offset=offset,
|
|
|
|
max_id=max_id,
|
|
|
|
limit=1
|
|
|
|
)
|
|
|
|
else:
|
2021-09-12 13:16:02 +03:00
|
|
|
self.request = _tl.fn.messages.Search(
|
2019-05-30 17:51:19 +03:00
|
|
|
peer=entity,
|
|
|
|
q='',
|
2021-09-12 13:16:02 +03:00
|
|
|
filter=_tl.InputMessagesFilterChatPhotos(),
|
2019-05-30 17:51:19 +03:00
|
|
|
min_date=None,
|
|
|
|
max_date=None,
|
|
|
|
offset_id=0,
|
|
|
|
add_offset=offset,
|
|
|
|
limit=1,
|
|
|
|
max_id=max_id,
|
|
|
|
min_id=0,
|
|
|
|
hash=0
|
|
|
|
)
|
|
|
|
|
|
|
|
if self.limit == 0:
|
|
|
|
self.request.limit = 1
|
|
|
|
result = await self.client(self.request)
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(result, _tl.photos.Photos):
|
2019-05-30 17:51:19 +03:00
|
|
|
self.total = len(result.photos)
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(result, _tl.messages.Messages):
|
2019-05-30 17:51:19 +03:00
|
|
|
self.total = len(result.messages)
|
|
|
|
else:
|
|
|
|
# Luckily both photosSlice and messages have a count for total
|
|
|
|
self.total = getattr(result, 'count', None)
|
|
|
|
|
|
|
|
async def _load_next_chunk(self):
|
|
|
|
self.request.limit = min(self.left, _MAX_PROFILE_PHOTO_CHUNK_SIZE)
|
|
|
|
result = await self.client(self.request)
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(result, _tl.photos.Photos):
|
2019-05-30 17:51:19 +03:00
|
|
|
self.buffer = result.photos
|
|
|
|
self.left = len(self.buffer)
|
|
|
|
self.total = len(self.buffer)
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(result, _tl.messages.Messages):
|
2019-05-30 17:51:19 +03:00
|
|
|
self.buffer = [x.action.photo for x in result.messages
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(x.action, _tl.MessageActionChatEditPhoto)]
|
2019-05-30 17:51:19 +03:00
|
|
|
|
|
|
|
self.left = len(self.buffer)
|
|
|
|
self.total = len(self.buffer)
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(result, _tl.photos.PhotosSlice):
|
2019-05-30 17:51:19 +03:00
|
|
|
self.buffer = result.photos
|
|
|
|
self.total = result.count
|
|
|
|
if len(self.buffer) < self.request.limit:
|
|
|
|
self.left = len(self.buffer)
|
|
|
|
else:
|
|
|
|
self.request.offset += len(result.photos)
|
|
|
|
else:
|
2020-09-22 12:08:17 +03:00
|
|
|
# Some broadcast channels have a photo that this request doesn't
|
|
|
|
# retrieve for whatever random reason the Telegram server feels.
|
|
|
|
#
|
|
|
|
# This means the `total` count may be wrong but there's not much
|
|
|
|
# that can be done around it (perhaps there are too many photos
|
|
|
|
# and this is only a partial result so it's not possible to just
|
|
|
|
# use the len of the result).
|
2020-08-13 16:13:29 +03:00
|
|
|
self.total = getattr(result, 'count', None)
|
2020-09-22 12:08:17 +03:00
|
|
|
|
|
|
|
# Unconditionally fetch the full channel to obtain this photo and
|
|
|
|
# yield it with the rest (unless it's a duplicate).
|
|
|
|
seen_id = None
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(result, _tl.messages.ChannelMessages):
|
|
|
|
channel = await self.client(_tl.fn.channels.GetFullChannel(self.request.peer))
|
2020-08-13 16:13:29 +03:00
|
|
|
photo = channel.full_chat.chat_photo
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(photo, _tl.Photo):
|
2020-09-22 12:08:17 +03:00
|
|
|
self.buffer.append(photo)
|
|
|
|
seen_id = photo.id
|
2020-08-13 16:13:29 +03:00
|
|
|
|
2020-09-22 12:08:17 +03:00
|
|
|
self.buffer.extend(
|
|
|
|
x.action.photo for x in result.messages
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(x.action, _tl.MessageActionChatEditPhoto)
|
2020-09-22 12:08:17 +03:00
|
|
|
and x.action.photo.id != seen_id
|
|
|
|
)
|
2020-08-13 16:13:29 +03:00
|
|
|
|
2019-05-30 17:51:19 +03:00
|
|
|
if len(result.messages) < self.request.limit:
|
|
|
|
self.left = len(self.buffer)
|
|
|
|
elif result.messages:
|
|
|
|
self.request.add_offset = 0
|
|
|
|
self.request.offset_id = result.messages[-1].id
|
|
|
|
|
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
def iter_participants(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
limit: float = None,
|
|
|
|
*,
|
|
|
|
search: str = '',
|
2021-09-12 13:16:02 +03:00
|
|
|
filter: '_tl.TypeChannelParticipantsFilter' = None,
|
2021-09-11 14:33:27 +03:00
|
|
|
aggressive: bool = False) -> _ParticipantsIter:
|
|
|
|
return _ParticipantsIter(
|
|
|
|
self,
|
|
|
|
limit,
|
|
|
|
entity=entity,
|
|
|
|
filter=filter,
|
|
|
|
search=search,
|
|
|
|
aggressive=aggressive
|
|
|
|
)
|
|
|
|
|
|
|
|
async def get_participants(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
*args,
|
|
|
|
**kwargs) -> 'hints.TotalList':
|
|
|
|
return await self.iter_participants(*args, **kwargs).collect()
|
|
|
|
|
|
|
|
|
|
|
|
def iter_admin_log(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
limit: float = None,
|
|
|
|
*,
|
|
|
|
max_id: int = 0,
|
|
|
|
min_id: int = 0,
|
|
|
|
search: str = None,
|
|
|
|
admins: 'hints.EntitiesLike' = None,
|
|
|
|
join: bool = None,
|
|
|
|
leave: bool = None,
|
|
|
|
invite: bool = None,
|
|
|
|
restrict: bool = None,
|
|
|
|
unrestrict: bool = None,
|
|
|
|
ban: bool = None,
|
|
|
|
unban: bool = None,
|
|
|
|
promote: bool = None,
|
|
|
|
demote: bool = None,
|
|
|
|
info: bool = None,
|
|
|
|
settings: bool = None,
|
|
|
|
pinned: bool = None,
|
|
|
|
edit: bool = None,
|
|
|
|
delete: bool = None,
|
|
|
|
group_call: bool = None) -> _AdminLogIter:
|
|
|
|
return _AdminLogIter(
|
|
|
|
self,
|
|
|
|
limit,
|
|
|
|
entity=entity,
|
|
|
|
admins=admins,
|
|
|
|
search=search,
|
|
|
|
min_id=min_id,
|
|
|
|
max_id=max_id,
|
|
|
|
join=join,
|
|
|
|
leave=leave,
|
|
|
|
invite=invite,
|
|
|
|
restrict=restrict,
|
|
|
|
unrestrict=unrestrict,
|
|
|
|
ban=ban,
|
|
|
|
unban=unban,
|
|
|
|
promote=promote,
|
|
|
|
demote=demote,
|
|
|
|
info=info,
|
|
|
|
settings=settings,
|
|
|
|
pinned=pinned,
|
|
|
|
edit=edit,
|
|
|
|
delete=delete,
|
|
|
|
group_call=group_call
|
|
|
|
)
|
|
|
|
|
|
|
|
async def get_admin_log(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
*args,
|
|
|
|
**kwargs) -> 'hints.TotalList':
|
|
|
|
return await self.iter_admin_log(*args, **kwargs).collect()
|
|
|
|
|
|
|
|
|
|
|
|
def iter_profile_photos(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
limit: int = None,
|
|
|
|
*,
|
|
|
|
offset: int = 0,
|
|
|
|
max_id: int = 0) -> _ProfilePhotoIter:
|
|
|
|
return _ProfilePhotoIter(
|
|
|
|
self,
|
|
|
|
limit,
|
|
|
|
entity=entity,
|
|
|
|
offset=offset,
|
|
|
|
max_id=max_id
|
|
|
|
)
|
|
|
|
|
|
|
|
async def get_profile_photos(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
*args,
|
|
|
|
**kwargs) -> 'hints.TotalList':
|
|
|
|
return await self.iter_profile_photos(*args, **kwargs).collect()
|
|
|
|
|
|
|
|
|
|
|
|
def action(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
2021-09-12 13:16:02 +03:00
|
|
|
action: 'typing.Union[str, _tl.TypeSendMessageAction]',
|
2021-09-11 14:33:27 +03:00
|
|
|
*,
|
|
|
|
delay: float = 4,
|
|
|
|
auto_cancel: bool = True) -> 'typing.Union[_ChatAction, typing.Coroutine]':
|
|
|
|
if isinstance(action, str):
|
|
|
|
try:
|
|
|
|
action = _ChatAction._str_mapping[action.lower()]
|
|
|
|
except KeyError:
|
2021-06-15 23:57:32 +03:00
|
|
|
raise ValueError(
|
2021-09-11 14:33:27 +03:00
|
|
|
'No such action "{}"'.format(action)) from None
|
2021-09-12 17:58:06 +03:00
|
|
|
elif not isinstance(action, tlobject.TLObject) or action.SUBCLASS_OF_ID != 0x20b2cc21:
|
2021-09-11 14:33:27 +03:00
|
|
|
# 0x20b2cc21 = crc32(b'SendMessageAction')
|
|
|
|
if isinstance(action, type):
|
|
|
|
raise ValueError('You must pass an instance, not the class')
|
|
|
|
else:
|
|
|
|
raise ValueError('Cannot use {} as action'.format(action))
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(action, _tl.SendMessageCancelAction):
|
2021-09-13 22:00:31 +03:00
|
|
|
# ``SetTyping.resolve`` will get input peer of ``entity``.
|
2021-09-12 13:16:02 +03:00
|
|
|
return self(_tl.fn.messages.SetTyping(
|
|
|
|
entity, _tl.SendMessageCancelAction()))
|
2021-09-11 14:33:27 +03:00
|
|
|
|
|
|
|
return _ChatAction(
|
|
|
|
self, entity, action, delay=delay, auto_cancel=auto_cancel)
|
|
|
|
|
|
|
|
async def edit_admin(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
user: 'hints.EntityLike',
|
|
|
|
*,
|
|
|
|
change_info: bool = None,
|
|
|
|
post_messages: bool = None,
|
|
|
|
edit_messages: bool = None,
|
|
|
|
delete_messages: bool = None,
|
|
|
|
ban_users: bool = None,
|
|
|
|
invite_users: bool = None,
|
|
|
|
pin_messages: bool = None,
|
|
|
|
add_admins: bool = None,
|
|
|
|
manage_call: bool = None,
|
|
|
|
anonymous: bool = None,
|
|
|
|
is_admin: bool = None,
|
2021-09-12 13:16:02 +03:00
|
|
|
title: str = None) -> _tl.Updates:
|
2021-09-11 14:33:27 +03:00
|
|
|
entity = await self.get_input_entity(entity)
|
|
|
|
user = await self.get_input_entity(user)
|
|
|
|
ty = helpers._entity_type(user)
|
|
|
|
if ty != helpers._EntityType.USER:
|
|
|
|
raise ValueError('You must pass a user entity')
|
|
|
|
|
|
|
|
perm_names = (
|
|
|
|
'change_info', 'post_messages', 'edit_messages', 'delete_messages',
|
|
|
|
'ban_users', 'invite_users', 'pin_messages', 'add_admins',
|
|
|
|
'anonymous', 'manage_call',
|
|
|
|
)
|
|
|
|
|
|
|
|
ty = helpers._entity_type(entity)
|
|
|
|
if ty == helpers._EntityType.CHANNEL:
|
|
|
|
# If we try to set these permissions in a megagroup, we
|
|
|
|
# would get a RIGHT_FORBIDDEN. However, it makes sense
|
|
|
|
# that an admin can post messages, so we want to avoid the error
|
|
|
|
if post_messages or edit_messages:
|
|
|
|
# TODO get rid of this once sessions cache this information
|
|
|
|
if entity.channel_id not in self._megagroup_cache:
|
|
|
|
full_entity = await self.get_entity(entity)
|
|
|
|
self._megagroup_cache[entity.channel_id] = full_entity.megagroup
|
|
|
|
|
|
|
|
if self._megagroup_cache[entity.channel_id]:
|
|
|
|
post_messages = None
|
|
|
|
edit_messages = None
|
|
|
|
|
|
|
|
perms = locals()
|
2021-09-12 13:16:02 +03:00
|
|
|
return await self(_tl.fn.channels.EditAdmin(entity, user, _tl.ChatAdminRights(**{
|
2021-09-11 14:33:27 +03:00
|
|
|
# A permission is its explicit (not-None) value or `is_admin`.
|
|
|
|
# This essentially makes `is_admin` be the default value.
|
|
|
|
name: perms[name] if perms[name] is not None else is_admin
|
|
|
|
for name in perm_names
|
|
|
|
}), rank=title or ''))
|
|
|
|
|
|
|
|
elif ty == helpers._EntityType.CHAT:
|
|
|
|
# If the user passed any permission in a small
|
|
|
|
# group chat, they must be a full admin to have it.
|
|
|
|
if is_admin is None:
|
|
|
|
is_admin = any(locals()[x] for x in perm_names)
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
return await self(_tl.fn.messages.EditChatAdmin(
|
2021-09-11 14:33:27 +03:00
|
|
|
entity, user, is_admin=is_admin))
|
|
|
|
|
|
|
|
else:
|
|
|
|
raise ValueError(
|
|
|
|
'You can only edit permissions in groups and channels')
|
|
|
|
|
|
|
|
async def edit_permissions(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
user: 'typing.Optional[hints.EntityLike]' = None,
|
|
|
|
until_date: 'hints.DateLike' = None,
|
|
|
|
*,
|
|
|
|
view_messages: bool = True,
|
|
|
|
send_messages: bool = True,
|
|
|
|
send_media: bool = True,
|
|
|
|
send_stickers: bool = True,
|
|
|
|
send_gifs: bool = True,
|
|
|
|
send_games: bool = True,
|
|
|
|
send_inline: bool = True,
|
|
|
|
embed_link_previews: bool = True,
|
|
|
|
send_polls: bool = True,
|
|
|
|
change_info: bool = True,
|
|
|
|
invite_users: bool = True,
|
2021-09-12 13:16:02 +03:00
|
|
|
pin_messages: bool = True) -> _tl.Updates:
|
2021-09-11 14:33:27 +03:00
|
|
|
entity = await self.get_input_entity(entity)
|
|
|
|
ty = helpers._entity_type(entity)
|
|
|
|
if ty != helpers._EntityType.CHANNEL:
|
|
|
|
raise ValueError('You must pass either a channel or a supergroup')
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
rights = _tl.ChatBannedRights(
|
2021-09-11 14:33:27 +03:00
|
|
|
until_date=until_date,
|
|
|
|
view_messages=not view_messages,
|
|
|
|
send_messages=not send_messages,
|
|
|
|
send_media=not send_media,
|
|
|
|
send_stickers=not send_stickers,
|
|
|
|
send_gifs=not send_gifs,
|
|
|
|
send_games=not send_games,
|
|
|
|
send_inline=not send_inline,
|
|
|
|
embed_links=not embed_link_previews,
|
|
|
|
send_polls=not send_polls,
|
|
|
|
change_info=not change_info,
|
|
|
|
invite_users=not invite_users,
|
|
|
|
pin_messages=not pin_messages
|
|
|
|
)
|
|
|
|
|
|
|
|
if user is None:
|
2021-09-12 13:16:02 +03:00
|
|
|
return await self(_tl.fn.messages.EditChatDefaultBannedRights(
|
2021-09-11 14:33:27 +03:00
|
|
|
peer=entity,
|
2019-07-05 11:48:21 +03:00
|
|
|
banned_rights=rights
|
|
|
|
))
|
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
user = await self.get_input_entity(user)
|
|
|
|
ty = helpers._entity_type(user)
|
|
|
|
if ty != helpers._EntityType.USER:
|
|
|
|
raise ValueError('You must pass a user entity')
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(user, _tl.InputPeerSelf):
|
2021-09-11 14:33:27 +03:00
|
|
|
raise ValueError('You cannot restrict yourself')
|
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
return await self(_tl.fn.channels.EditBanned(
|
2021-09-11 14:33:27 +03:00
|
|
|
channel=entity,
|
|
|
|
participant=user,
|
|
|
|
banned_rights=rights
|
|
|
|
))
|
|
|
|
|
|
|
|
async def kick_participant(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
user: 'typing.Optional[hints.EntityLike]'
|
|
|
|
):
|
|
|
|
entity = await self.get_input_entity(entity)
|
|
|
|
user = await self.get_input_entity(user)
|
|
|
|
if helpers._entity_type(user) != helpers._EntityType.USER:
|
|
|
|
raise ValueError('You must pass a user entity')
|
|
|
|
|
|
|
|
ty = helpers._entity_type(entity)
|
|
|
|
if ty == helpers._EntityType.CHAT:
|
2021-09-12 13:16:02 +03:00
|
|
|
resp = await self(_tl.fn.messages.DeleteChatUser(entity.chat_id, user))
|
2021-09-11 14:33:27 +03:00
|
|
|
elif ty == helpers._EntityType.CHANNEL:
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(user, _tl.InputPeerSelf):
|
2021-09-11 14:33:27 +03:00
|
|
|
# Despite no longer being in the channel, the account still
|
|
|
|
# seems to get the service message.
|
2021-09-12 13:16:02 +03:00
|
|
|
resp = await self(_tl.fn.channels.LeaveChannel(entity))
|
2019-07-31 23:40:41 +03:00
|
|
|
else:
|
2021-09-12 13:16:02 +03:00
|
|
|
resp = await self(_tl.fn.channels.EditBanned(
|
2021-09-11 14:33:27 +03:00
|
|
|
channel=entity,
|
|
|
|
participant=user,
|
2021-09-12 13:16:02 +03:00
|
|
|
banned_rights=_tl.ChatBannedRights(
|
2021-09-11 14:33:27 +03:00
|
|
|
until_date=None, view_messages=True)
|
2020-10-03 17:59:54 +03:00
|
|
|
))
|
2021-09-11 14:33:27 +03:00
|
|
|
await asyncio.sleep(0.5)
|
2021-09-12 13:16:02 +03:00
|
|
|
await self(_tl.fn.channels.EditBanned(
|
2021-09-11 14:33:27 +03:00
|
|
|
channel=entity,
|
|
|
|
participant=user,
|
2021-09-12 13:16:02 +03:00
|
|
|
banned_rights=_tl.ChatBannedRights(until_date=None)
|
2020-10-03 17:59:54 +03:00
|
|
|
))
|
2021-09-11 14:33:27 +03:00
|
|
|
else:
|
2020-10-03 17:59:54 +03:00
|
|
|
raise ValueError('You must pass either a channel or a chat')
|
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
return self._get_response_message(None, resp, entity)
|
|
|
|
|
|
|
|
async def get_permissions(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
|
|
|
user: 'hints.EntityLike' = None
|
2021-09-12 14:27:13 +03:00
|
|
|
) -> 'typing.Optional[custom.ParticipantPermissions]':
|
2021-09-11 14:33:27 +03:00
|
|
|
entity = await self.get_entity(entity)
|
|
|
|
|
|
|
|
if not user:
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(entity, _tl.Channel):
|
|
|
|
FullChat = await self(_tl.fn.channels.GetFullChannel(entity))
|
|
|
|
elif isinstance(entity, _tl.Chat):
|
|
|
|
FullChat = await self(_tl.fn.messages.GetFullChat(entity))
|
2021-03-20 22:20:36 +03:00
|
|
|
else:
|
2021-09-11 14:33:27 +03:00
|
|
|
return
|
|
|
|
return FullChat.chats[0].default_banned_rights
|
|
|
|
|
|
|
|
entity = await self.get_input_entity(entity)
|
|
|
|
user = await self.get_input_entity(user)
|
|
|
|
if helpers._entity_type(user) != helpers._EntityType.USER:
|
|
|
|
raise ValueError('You must pass a user entity')
|
|
|
|
if helpers._entity_type(entity) == helpers._EntityType.CHANNEL:
|
2021-09-12 13:16:02 +03:00
|
|
|
participant = await self(_tl.fn.channels.GetParticipant(
|
2021-09-11 14:33:27 +03:00
|
|
|
entity,
|
|
|
|
user
|
|
|
|
))
|
2021-09-12 17:58:06 +03:00
|
|
|
return _custom.ParticipantPermissions(participant.participant, False)
|
2021-09-11 14:33:27 +03:00
|
|
|
elif helpers._entity_type(entity) == helpers._EntityType.CHAT:
|
2021-09-12 13:16:02 +03:00
|
|
|
chat = await self(_tl.fn.messages.GetFullChat(
|
2021-09-11 14:33:27 +03:00
|
|
|
entity
|
|
|
|
))
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(user, _tl.InputPeerSelf):
|
2021-09-11 14:33:27 +03:00
|
|
|
user = await self.get_me(input_peer=True)
|
|
|
|
for participant in chat.full_chat.participants.participants:
|
|
|
|
if participant.user_id == user.user_id:
|
2021-09-12 17:58:06 +03:00
|
|
|
return _custom.ParticipantPermissions(participant, True)
|
2021-09-11 14:33:27 +03:00
|
|
|
raise errors.UserNotParticipantError(None)
|
|
|
|
|
|
|
|
raise ValueError('You must pass either a channel or a chat')
|
|
|
|
|
|
|
|
async def get_stats(
|
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
2021-09-12 13:16:02 +03:00
|
|
|
message: 'typing.Union[int, _tl.Message]' = None,
|
2021-09-11 14:33:27 +03:00
|
|
|
):
|
|
|
|
entity = await self.get_input_entity(entity)
|
|
|
|
if helpers._entity_type(entity) != helpers._EntityType.CHANNEL:
|
|
|
|
raise TypeError('You must pass a channel entity')
|
|
|
|
|
|
|
|
message = utils.get_message_id(message)
|
|
|
|
if message is not None:
|
|
|
|
try:
|
2021-09-12 13:16:02 +03:00
|
|
|
req = _tl.fn.stats.GetMessageStats(entity, message)
|
2021-09-11 14:33:27 +03:00
|
|
|
return await self(req)
|
|
|
|
except errors.StatsMigrateError as e:
|
|
|
|
dc = e.dc
|
|
|
|
else:
|
|
|
|
# Don't bother fetching the Channel entity (costs a request), instead
|
|
|
|
# try to guess and if it fails we know it's the other one (best case
|
|
|
|
# no extra request, worst just one).
|
|
|
|
try:
|
2021-09-12 13:16:02 +03:00
|
|
|
req = _tl.fn.stats.GetBroadcastStats(entity)
|
2021-09-11 14:33:27 +03:00
|
|
|
return await self(req)
|
|
|
|
except errors.StatsMigrateError as e:
|
|
|
|
dc = e.dc
|
|
|
|
except errors.BroadcastRequiredError:
|
2021-09-12 13:16:02 +03:00
|
|
|
req = _tl.fn.stats.GetMegagroupStats(entity)
|
2020-07-26 14:45:30 +03:00
|
|
|
try:
|
|
|
|
return await self(req)
|
|
|
|
except errors.StatsMigrateError as e:
|
|
|
|
dc = e.dc
|
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
sender = await self._borrow_exported_sender(dc)
|
|
|
|
try:
|
|
|
|
# req will be resolved to use the right types inside by now
|
|
|
|
return await sender.send(req)
|
|
|
|
finally:
|
|
|
|
await self._return_exported_sender(sender)
|