diff --git a/telethon/client/chats.py b/telethon/client/chats.py index ac4623f2..07ccfe3d 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -1,8 +1,11 @@ +import itertools +import sys + from async_generator import async_generator, yield_ from .users import UserMethods from .. import utils, helpers -from ..tl import types, functions +from ..tl import types, functions, custom class ChatMethods(UserMethods): @@ -188,4 +191,150 @@ class ChatMethods(UserMethods): participants.total = total[0] return participants + @async_generator + async def iter_admin_log( + self, entity, limit=None, *, max_id=0, min_id=0, search=None, + admins=None, join=None, leave=None, invite=None, restrict=None, + unrestrict=None, ban=None, unban=None, promote=None, demote=None, + info=None, settings=None, pinned=None, edit=None, delete=None): + """ + Iterator over the admin log for the specified channel. + + Note that you must be an administrator of it to use this method. + + If none of the filters are present (i.e. they all are ``None``), + *all* event types will be returned. If at least one of them is + ``True``, only those that are true will be returned. + + Args: + entity (`entity`): + The channel entity from which to get its admin log. + + limit (`int` | `None`, optional): + Number of events to be retrieved. + + The limit may also be ``None``, which would eventually return + the whole history. + + max_id (`int`): + All the events with a higher (newer) ID or equal to this will + be excluded. + + min_id (`int`): + All the events with a lower (older) ID or equal to this will + be excluded. + + search (`str`): + The string to be used as a search query. + + admins (`entity` | `list`): + If present, the events will be filtered by these admins + (or single admin) and only those caused by them will be + returned. + + join (`bool`): + If ``True``, events for when a user joined will be returned. + + leave (`bool`): + If ``True``, events for when a user leaves will be returned. + + invite (`bool`): + If ``True``, events for when a user joins through an invite + link will be returned. + + restrict (`bool`): + If ``True``, events with partial restrictions will be + returned. This is what the API calls "ban". + + unrestrict (`bool`): + If ``True``, events removing restrictions will be returned. + This is what the API calls "unban". + + ban (`bool`): + If ``True``, events applying or removing all restrictions will + be returned. This is what the API calls "kick" (restricting + all permissions removed is a ban, which kicks the user). + + unban (`bool`): + If ``True``, events removing all restrictions will be + returned. This is what the API calls "unkick". + + promote (`bool`): + If ``True``, events with admin promotions will be returned. + + demote (`bool`): + If ``True``, events with admin demotions will be returned. + + info (`bool`): + If ``True``, events changing the group info will be returned. + + settings (`bool`): + If ``True``, events changing the group settings will be + returned. + + pinned (`bool`): + If ``True``, events of new pinned messages will be returned. + + edit (`bool`): + If ``True``, events of message edits will be returned. + + delete (`bool`): + If ``True``, events of message deletions will be returned. + + Yields: + Instances of `telethon.tl.custom.adminlogevent.AdminLogEvent`. + """ + if limit is None: + limit = sys.maxsize + elif limit <= 0: + return + + if any((join, leave, invite, restrict, unrestrict, ban, unban, + promote, demote, info, settings, pinned, edit, delete)): + events_filter = types.ChannelAdminLogEventsFilter( + join=join, leave=leave, invite=invite, ban=restrict, + unban=unrestrict, kick=ban, unkick=unban, promote=promote, + demote=demote, info=info, settings=settings, pinned=pinned, + edit=edit, delete=delete + ) + else: + events_filter = None + + entity = await self.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.get_input_entity(admin)) + + request = functions.channels.GetAdminLogRequest( + entity, q=search or '', min_id=min_id, max_id=max_id, + limit=0, events_filter=events_filter, admins=admin_list or None + ) + while limit > 0: + request.limit = min(limit, 100) + result = await self(request) + limit -= len(result.events) + entities = {utils.get_peer_id(x): x + for x in itertools.chain(result.users, result.chats)} + + request.max_id = min((e.id for e in result.events), default=0) + for event in result.events: + await yield_(custom.AdminLogEvent(event, entities)) + + if len(result.events) < request.limit: + break + + async def get_admin_log(self, *args, **kwargs): + """ + Same as `iter_admin_log`, but returns a ``list`` instead. + """ + admin_log = [] + async for x in self.iter_admin_log(*args, **kwargs): + admin_log.append(x) + return admin_log + # endregion diff --git a/telethon/tl/custom/__init__.py b/telethon/tl/custom/__init__.py index 2a7b7aa8..6578fbf7 100644 --- a/telethon/tl/custom/__init__.py +++ b/telethon/tl/custom/__init__.py @@ -1,3 +1,4 @@ +from .adminlogevent import AdminLogEvent from .draft import Draft from .dialog import Dialog from .inputsizedfile import InputSizedFile diff --git a/telethon/tl/custom/adminlogevent.py b/telethon/tl/custom/adminlogevent.py new file mode 100644 index 00000000..860dbb8c --- /dev/null +++ b/telethon/tl/custom/adminlogevent.py @@ -0,0 +1,294 @@ +from ...tl import types +from ...utils import get_input_peer + + +class AdminLogEvent: + """ + Represents a more friendly interface for admin log events. + + Members: + original (:tl:`ChannelAdminLogEvent`): + The original :tl:`ChannelAdminLogEvent`. + + entities (`dict`): + A dictionary mapping user IDs to :tl:`User`. + + When `old` and `new` are :tl:`ChannelParticipant`, you can + use this dictionary to map the ``user_id``, ``kicked_by``, + ``inviter_id`` and ``promoted_by`` IDs to their :tl:`User`. + + user (:tl:`User`): + The user that caused this action (``entities[original.user_id]``). + + input_user (:tl:`InputPeerUser`): + Input variant of `user`. + """ + def __init__(self, original, entities): + self.original = original + self.entities = entities + self.user = entities[original.user_id] + self.input_user = get_input_peer(self.user) + + @property + def id(self): + """ + The ID of this event. + """ + return self.original.id + + @property + def date(self): + """ + The date when this event occured. + """ + return self.original.date + + @property + def user_id(self): + """ + The ID of the user that triggered this event. + """ + return self.original.user_id + + @property + def action(self): + """ + The original :tl:`ChannelAdminLogEventAction`. + """ + return self.original.action + + @property + def old(self): + """ + The old value from the event. + """ + ori = self.original + if isinstance(ori, ( + types.ChannelAdminLogEventActionChangeAbout, + types.ChannelAdminLogEventActionChangeTitle, + types.ChannelAdminLogEventActionChangeUsername + )): + return ori.prev_value + elif isinstance(ori, types.ChannelAdminLogEventActionChangePhoto): + return ori.prev_photo + elif isinstance(ori, types.ChannelAdminLogEventActionChangeStickerSet): + return ori.prev_stickerset + elif isinstance(ori, types.ChannelAdminLogEventActionEditMessage): + return ori.prev_message + elif isinstance(ori, ( + types.ChannelAdminLogEventActionParticipantToggleAdmin, + types.ChannelAdminLogEventActionParticipantToggleBan + )): + return ori.prev_participant + elif isinstance(ori, ( + types.ChannelAdminLogEventActionToggleInvites, + types.ChannelAdminLogEventActionTogglePreHistoryHidden, + types.ChannelAdminLogEventActionToggleSignatures + )): + return not ori.new_value + elif isinstance(ori, types.ChannelAdminLogEventActionDeleteMessage): + return ori.message + + @property + def new(self): + """ + The new value present in the event. + """ + ori = self.original + if isinstance(ori, ( + types.ChannelAdminLogEventActionChangeAbout, + types.ChannelAdminLogEventActionChangeTitle, + types.ChannelAdminLogEventActionChangeUsername, + types.ChannelAdminLogEventActionToggleInvites, + types.ChannelAdminLogEventActionTogglePreHistoryHidden, + types.ChannelAdminLogEventActionToggleSignatures + )): + return ori.new_value + elif isinstance(ori, types.ChannelAdminLogEventActionChangePhoto): + return ori.new_photo + elif isinstance(ori, types.ChannelAdminLogEventActionChangeStickerSet): + return ori.new_stickerset + elif isinstance(ori, types.ChannelAdminLogEventActionEditMessage): + return ori.new_message + elif isinstance(ori, ( + types.ChannelAdminLogEventActionParticipantToggleAdmin, + types.ChannelAdminLogEventActionParticipantToggleBan + )): + return ori.new_participant + elif isinstance(ori, types.ChannelAdminLogEventActionParticipantInvite): + return ori.participant + + @property + def changed_about(self): + """ + Whether the channel's about was changed in this event or not. + + If ``True``, `old` and `new` will be present as ``str``. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionChangeAbout) + + @property + def changed_title(self): + """ + Whether the channel's title was changed in this event or not. + + If ``True``, `old` and `new` will be present as ``str``. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionChangeTitle) + + @property + def changed_username(self): + """ + Whether the channel's username was changed in this event or not. + + If ``True``, `old` and `new` will be present as ``str``. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionChangeUsername) + + @property + def changed_photo(self): + """ + Whether the channel's photo was changed in this event or not. + + If ``True``, `old` and `new` will be present as :tl:`ChatPhoto`. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionChangePhoto) + + @property + def changed_sticker_set(self): + """ + Whether the channel's sticker set was changed in this event or not. + + If ``True``, `old` and `new` will be present as :tl:`InputStickerSet`. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionChangeStickerSet) + + @property + def changed_message(self): + """ + Whether a message in this channel was edited in this event or not. + + If ``True``, `old` and `new` will be present as + `Message `. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionEditMessage) + + @property + def deleted_message(self): + """ + Whether a message in this channel was deleted in this event or not. + + If ``True``, `old` will be present as + `Message `. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionDeleteMessage) + + @property + def changed_admin(self): + """ + Whether the permissions for an admin in this channel + changed in this event or not. + + If ``True``, `old` and `new` will be present as + :tl:`ChannelParticipant`. + """ + return isinstance( + self.original, + types.ChannelAdminLogEventActionParticipantToggleAdmin) + + @property + def changed_restrictions(self): + """ + Whether a message in this channel was edited in this event or not. + + If ``True``, `old` and `new` will be present as + :tl:`ChannelParticipant`. + """ + return isinstance( + self.original, + types.ChannelAdminLogEventActionParticipantToggleBan) + + @property + def changed_invites(self): + """ + Whether the invites in the channel were toggled in this event or not. + + If ``True``, `old` and `new` will be present as ``bool``. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionToggleInvites) + + @property + def joined(self): + """ + Whether `user` joined through the channel's + public username in this event or not. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionParticipantJoin) + + @property + def joined_invite(self): + """ + Whether a new user joined through an invite + link to the channel in this event or not. + + If ``True``, `new` will be present as + :tl:`ChannelParticipant`. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionParticipantInvite) + + @property + def left(self): + """ + Whether `user` left the channel in this event or not. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionParticipantLeave) + + @property + def changed_hide_history(self): + """ + Whether hiding the previous message history for new members + in the channel were toggled in this event or not. + + If ``True``, `old` and `new` will be present as ``bool``. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionTogglePreHistoryHidden) + + @property + def changed_signatures(self): + """ + Whether the message signatures in the channel were toggled + in this event or not. + + If ``True``, `old` and `new` will be present as ``bool``. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionToggleSignatures) + + @property + def changed_pin(self): + """ + Whether a new message in this channel was pinned in this event or not. + + If ``True``, `new` will be present as + `Message `. + """ + return isinstance(self.original, + types.ChannelAdminLogEventActionUpdatePinned) + + def __str__(self): + return str(self.original) + + def stringify(self): + return self.original.stringify()