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
|
2022-02-07 11:28:39 +03:00
|
|
|
import dataclasses
|
2018-06-10 22:50:28 +03:00
|
|
|
|
2021-09-26 20:58:42 +03:00
|
|
|
from .. import errors, _tl
|
|
|
|
from .._misc import helpers, utils, requestiter, tlobject, enums, hints
|
2021-09-12 17:58:06 +03:00
|
|
|
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:
|
|
|
|
def __init__(self, client, chat, action, *, delay, auto_cancel):
|
|
|
|
self._client = client
|
|
|
|
self._delay = delay
|
|
|
|
self._auto_cancel = auto_cancel
|
2022-02-07 11:28:39 +03:00
|
|
|
self._request = _tl.fn.messages.SetTyping(chat, action)
|
2019-04-13 11:37:23 +03:00
|
|
|
self._task = None
|
|
|
|
self._running = False
|
|
|
|
|
2022-01-16 14:40:09 +03:00
|
|
|
def __await__(self):
|
|
|
|
return self._once().__await__()
|
|
|
|
|
2019-04-13 11:37:23 +03:00
|
|
|
async def __aenter__(self):
|
2022-02-07 11:28:39 +03:00
|
|
|
self._request = dataclasses.replace(self._request, peer=await self._client.get_input_entity(self._request.peer))
|
2019-04-13 11:37:23 +03:00
|
|
|
self._running = True
|
2022-01-16 15:51:23 +03:00
|
|
|
self._task = asyncio.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
|
|
|
|
|
2022-01-16 14:40:09 +03:00
|
|
|
async def _once(self):
|
2022-02-07 11:28:39 +03:00
|
|
|
self._request = dataclasses.replace(self._request, peer=await self._client.get_input_entity(self._request.peer))
|
2022-01-16 14:40:09 +03:00
|
|
|
await self._client(_tl.fn.messages.SetTyping(self._chat, self._action))
|
|
|
|
|
2019-04-13 11:37:23 +03:00
|
|
|
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
|
|
|
|
2021-10-16 13:40:25 +03:00
|
|
|
@staticmethod
|
|
|
|
def _parse(action):
|
|
|
|
if isinstance(action, tlobject.TLObject) and action.SUBCLASS_OF_ID != 0x20b2cc21:
|
|
|
|
return action
|
|
|
|
|
|
|
|
return {
|
2022-01-16 14:06:42 +03:00
|
|
|
enums.Action.TYPING: _tl.SendMessageTypingAction(),
|
|
|
|
enums.Action.CONTACT: _tl.SendMessageChooseContactAction(),
|
|
|
|
enums.Action.GAME: _tl.SendMessageGamePlayAction(),
|
|
|
|
enums.Action.LOCATION: _tl.SendMessageGeoLocationAction(),
|
|
|
|
enums.Action.STICKER: _tl.SendMessageChooseStickerAction(),
|
|
|
|
enums.Action.RECORD_AUDIO: _tl.SendMessageRecordAudioAction(),
|
|
|
|
enums.Action.RECORD_ROUND: _tl.SendMessageRecordRoundAction(),
|
|
|
|
enums.Action.RECORD_VIDEO: _tl.SendMessageRecordVideoAction(),
|
|
|
|
enums.Action.AUDIO: _tl.SendMessageUploadAudioAction(1),
|
|
|
|
enums.Action.ROUND: _tl.SendMessageUploadRoundAction(1),
|
|
|
|
enums.Action.VIDEO: _tl.SendMessageUploadVideoAction(1),
|
|
|
|
enums.Action.PHOTO: _tl.SendMessageUploadPhotoAction(1),
|
|
|
|
enums.Action.DOCUMENT: _tl.SendMessageUploadDocumentAction(1),
|
|
|
|
enums.Action.CANCEL: _tl.SendMessageCancelAction(),
|
|
|
|
}[enums.Action(action)]
|
2021-10-16 13:40:25 +03:00
|
|
|
|
2019-04-13 11:37:23 +03:00
|
|
|
def progress(self, current, total):
|
2022-02-07 11:28:39 +03:00
|
|
|
if hasattr(self._request.action, 'progress'):
|
|
|
|
self._request = dataclasses.replace(
|
|
|
|
self._request,
|
|
|
|
action=dataclasses.replace(self._request.action, progress=100 * round(current / total))
|
|
|
|
)
|
2019-04-13 11:37:23 +03:00
|
|
|
|
|
|
|
|
2021-09-12 14:27:13 +03:00
|
|
|
class _ParticipantsIter(requestiter.RequestIter):
|
2021-09-17 21:13:05 +03:00
|
|
|
async def _init(self, entity, filter, search):
|
2021-09-18 15:16:19 +03:00
|
|
|
if not filter:
|
|
|
|
if search:
|
|
|
|
filter = _tl.ChannelParticipantsSearch(search)
|
2019-02-27 13:12:05 +03:00
|
|
|
else:
|
2021-09-18 15:16:19 +03:00
|
|
|
filter = _tl.ChannelParticipantsRecent()
|
|
|
|
else:
|
2022-01-16 14:06:42 +03:00
|
|
|
filter = enums.Participant(filter)
|
2021-09-18 15:16:19 +03:00
|
|
|
if filter == enums.Participant.ADMIN:
|
|
|
|
filter = _tl.ChannelParticipantsAdmins()
|
|
|
|
elif filter == enums.Participant.BOT:
|
|
|
|
filter = _tl.ChannelParticipantsBots()
|
|
|
|
elif filter == enums.Participant.KICKED:
|
|
|
|
filter = _tl.ChannelParticipantsKicked(search)
|
|
|
|
elif filter == enums.Participant.BANNED:
|
|
|
|
filter = _tl.ChannelParticipantsBanned(search)
|
|
|
|
elif filter == enums.Participant.CONTACT:
|
|
|
|
filter = _tl.ChannelParticipantsContacts(search)
|
|
|
|
else:
|
|
|
|
raise RuntimeError('unhandled enum variant')
|
2019-02-27 13:12:05 +03:00
|
|
|
|
|
|
|
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-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()
|
2021-09-17 21:13:05 +03:00
|
|
|
self.request = _tl.fn.channels.GetParticipants(
|
|
|
|
channel=entity,
|
|
|
|
filter=filter or _tl.ChannelParticipantsSearch(search),
|
|
|
|
offset=0,
|
|
|
|
limit=_MAX_PARTICIPANTS_CHUNK_SIZE,
|
|
|
|
hash=0
|
|
|
|
)
|
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: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):
|
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):
|
|
|
|
# Only care about the limit for the first request
|
2021-09-17 21:13:05 +03:00
|
|
|
# (small amount of people).
|
2019-02-27 13:12:05 +03:00
|
|
|
#
|
|
|
|
# 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.
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(self.request, limit=min(
|
|
|
|
self.limit - self.request.offset, _MAX_PARTICIPANTS_CHUNK_SIZE))
|
2019-02-27 15:07:25 +03:00
|
|
|
|
2021-09-17 21:13:05 +03:00
|
|
|
if self.request.offset > self.limit:
|
2019-02-27 13:24:47 +03:00
|
|
|
return True
|
2019-02-27 13:12:05 +03:00
|
|
|
|
2021-09-17 21:13:05 +03:00
|
|
|
participants = await self.client(self.request)
|
2021-09-17 21:16:01 +03:00
|
|
|
self.total = participants.count
|
2021-09-17 21:13:05 +03:00
|
|
|
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(self.request, offset=self.request.offset + len(participants.participants))
|
2021-09-17 21:13:05 +03:00
|
|
|
users = {user.id: user for user in participants.users}
|
|
|
|
for participant in participants.participants:
|
|
|
|
if isinstance(participant, _tl.ChannelParticipantBanned):
|
|
|
|
if not isinstance(participant.peer, _tl.PeerUser):
|
|
|
|
# May have the entire channel banned. See #3105.
|
2019-02-27 13:12:05 +03:00
|
|
|
continue
|
2021-09-17 21:13:05 +03:00
|
|
|
user_id = participant.peer.user_id
|
|
|
|
else:
|
|
|
|
user_id = participant.user_id
|
|
|
|
|
|
|
|
user = users[user_id]
|
|
|
|
if not self.filter_entity(user) or user.id in self.seen:
|
|
|
|
continue
|
|
|
|
self.seen.add(user_id)
|
|
|
|
user = users[user_id]
|
|
|
|
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):
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(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)}
|
|
|
|
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(self.request, max_id=min((e.id for e in r.events), default=0))
|
2019-02-27 13:12:05 +03:00
|
|
|
for ev in r.events:
|
|
|
|
if isinstance(ev.action,
|
2021-09-12 13:16:02 +03:00
|
|
|
_tl.ChannelAdminLogEventActionEditMessage):
|
2022-02-07 11:28:39 +03:00
|
|
|
ev = dataclasses.replace(ev, action=dataclasses.replace(
|
|
|
|
ev.action,
|
|
|
|
prev_message=_custom.Message._new(self.client, ev.action.prev_message, entities, self.entity),
|
|
|
|
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:
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(self.request, limit=1)
|
2019-05-30 17:51:19 +03:00
|
|
|
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):
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(self.request, limit=min(self.left, _MAX_PROFILE_PHOTO_CHUNK_SIZE))
|
2019-05-30 17:51:19 +03:00
|
|
|
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:
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(self.request, offset=self.request.offset + len(result.photos))
|
2019-05-30 17:51:19 +03:00
|
|
|
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:
|
2022-02-07 11:28:39 +03:00
|
|
|
self.request = dataclasses.replace(
|
|
|
|
self.request,
|
|
|
|
add_offset=0,
|
|
|
|
offset_id=result.messages[-1].id
|
|
|
|
)
|
2019-05-30 17:51:19 +03:00
|
|
|
|
|
|
|
|
2021-09-17 20:35:10 +03:00
|
|
|
def get_participants(
|
2021-09-11 14:33:27 +03:00
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
2021-09-26 19:37:09 +03:00
|
|
|
limit: float = (),
|
2021-09-11 14:33:27 +03:00
|
|
|
*,
|
|
|
|
search: str = '',
|
2021-09-17 21:13:05 +03:00
|
|
|
filter: '_tl.TypeChannelParticipantsFilter' = None) -> _ParticipantsIter:
|
2021-09-11 14:33:27 +03:00
|
|
|
return _ParticipantsIter(
|
|
|
|
self,
|
|
|
|
limit,
|
|
|
|
entity=entity,
|
|
|
|
filter=filter,
|
2021-09-17 21:13:05 +03:00
|
|
|
search=search
|
2021-09-11 14:33:27 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-09-17 20:35:10 +03:00
|
|
|
def get_admin_log(
|
2021-09-11 14:33:27 +03:00
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
2021-09-26 19:37:09 +03:00
|
|
|
limit: float = (),
|
2021-09-11 14:33:27 +03:00
|
|
|
*,
|
|
|
|
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
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-09-17 20:35:10 +03:00
|
|
|
def get_profile_photos(
|
2021-09-11 14:33:27 +03:00
|
|
|
self: 'TelegramClient',
|
|
|
|
entity: 'hints.EntityLike',
|
2021-09-26 19:37:09 +03:00
|
|
|
limit: int = (),
|
2021-09-11 14:33:27 +03:00
|
|
|
*,
|
|
|
|
offset: int = 0,
|
|
|
|
max_id: int = 0) -> _ProfilePhotoIter:
|
|
|
|
return _ProfilePhotoIter(
|
|
|
|
self,
|
|
|
|
limit,
|
|
|
|
entity=entity,
|
|
|
|
offset=offset,
|
|
|
|
max_id=max_id
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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]':
|
2021-10-16 13:40:25 +03:00
|
|
|
action = _ChatAction._parse(action)
|
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)
|
|
|
|
|
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
|
2022-01-24 23:09:51 +03:00
|
|
|
) -> 'typing.Optional[_custom.ParticipantPermissions]':
|
2021-09-11 14:33:27 +03:00
|
|
|
entity = await self.get_entity(entity)
|
|
|
|
|
|
|
|
if not user:
|
2022-01-24 15:15:02 +03:00
|
|
|
if helpers._entity_type(entity) != helpers._EntityType.USER:
|
|
|
|
return entity.default_banned_rights
|
2021-09-11 14:33:27 +03:00
|
|
|
|
|
|
|
entity = await self.get_input_entity(entity)
|
|
|
|
user = await self.get_input_entity(user)
|
2022-01-24 23:09:51 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
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):
|
2022-01-26 13:03:50 +03:00
|
|
|
user = _tl.PeerUser(self._session_state.user_id)
|
2021-09-11 14:33:27 +03:00
|
|
|
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-24 21:07:34 +03:00
|
|
|
raise errors.USER_NOT_PARTICIPANT(400, 'USER_NOT_PARTICIPANT')
|
2021-09-11 14:33:27 +03:00
|
|
|
|
|
|
|
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)
|
2021-09-24 21:07:34 +03:00
|
|
|
except errors.STATS_MIGRATE as e:
|
2021-09-11 14:33:27 +03:00
|
|
|
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)
|
2021-09-24 21:07:34 +03:00
|
|
|
except errors.STATS_MIGRATE as e:
|
2021-09-11 14:33:27 +03:00
|
|
|
dc = e.dc
|
2021-09-24 21:07:34 +03:00
|
|
|
except errors.BROADCAST_REQUIRED:
|
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)
|
2021-09-24 21:07:34 +03:00
|
|
|
except errors.STATS_MIGRATE as e:
|
2020-07-26 14:45:30 +03:00
|
|
|
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)
|