From 55ae37f65e2d03ca39e75431058796f80a403b25 Mon Sep 17 00:00:00 2001 From: z03h <7235242+z03h@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:30:03 -0700 Subject: [PATCH] Add automod types to audit log --- discord/audit_logs.py | 52 +++++++++++++++++++++++++++++++++++++++---- discord/automod.py | 4 ++-- discord/enums.py | 4 +++- discord/guild.py | 27 +++++++++++++++------- docs/api.rst | 31 ++++++++++++++------------ 5 files changed, 89 insertions(+), 29 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 1d43b6843..f45f0bf35 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -33,6 +33,7 @@ from .invite import Invite from .mixins import Hashable from .object import Object from .permissions import PermissionOverwrite, Permissions +from .automod import AutoModTrigger, AutoModRuleAction, AutoModPresets __all__ = ( 'AuditLogDiff', @@ -61,10 +62,12 @@ if TYPE_CHECKING: from .types.invite import Invite as InvitePayload from .types.role import Role as RolePayload from .types.snowflake import Snowflake + from .types.automod import AutoModerationTriggerMetadata, AutoModerationAction from .user import User from .stage_instance import StageInstance from .sticker import GuildSticker from .threads import Thread + from .automod import AutoModRule, AutoModTrigger TargetType = Union[ Guild, @@ -78,7 +81,7 @@ if TYPE_CHECKING: GuildSticker, Thread, Object, - PartialIntegration, + AutoModRule, None, ] @@ -101,6 +104,12 @@ def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optio return entry.guild.get_channel(int(data)) or Object(id=data) +def _transform_channels_or_threads( + entry: AuditLogEntry, data: List[Snowflake] +) -> List[Union[abc.GuildChannel, Thread, Object]]: + return [entry.guild.get_channel_or_thread(int(data)) or Object(id=data) for data in data] + + def _transform_member_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Union[Member, User, None]: if data is None: return None @@ -113,6 +122,10 @@ def _transform_guild_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Opti return entry._state._get_guild(int(data)) +def _transform_roles(entry: AuditLogEntry, data: List[Snowflake]) -> List[Union[Role, Object]]: + return [entry.guild.get_role(int(role_id)) or Object(role_id) for role_id in data] + + def _transform_overwrites( entry: AuditLogEntry, data: List[PermissionOverwritePayload] ) -> List[Tuple[Object, PermissionOverwrite]]: @@ -168,6 +181,24 @@ def _guild_hash_transformer(path: str) -> Callable[[AuditLogEntry, Optional[str] return _transform +def _transform_automod_trigger_metadata( + entry: AuditLogEntry, data: AutoModerationTriggerMetadata +) -> Optional[AutoModTrigger]: + if data is None: + return None + + # discord doesn't provide the type of the trigger + # have to infer from the data and present keys + if 'presets' in data: + return AutoModTrigger(presets=AutoModPresets._from_value(data['presets'])) # type: ignore + + return AutoModTrigger(**data) + + +def _transform_automod_actions(entry: AuditLogEntry, data: List[AutoModerationAction]) -> List[AutoModRuleAction]: + return [AutoModRuleAction.from_data(action) for action in data] + + E = TypeVar('E', bound=enums.Enum) @@ -259,6 +290,11 @@ class AuditLogChanges: 'preferred_locale': (None, _enum_transformer(enums.Locale)), 'image_hash': ('cover_image', _transform_cover_image), 'trigger_type': (None, _enum_transformer(enums.AutoModRuleTriggerType)), + 'event_type': (None, _enum_transformer(enums.AutoModRuleEventType)), + 'trigger_metadata': ('trigger', _transform_automod_trigger_metadata), + 'actions': (None, _transform_automod_actions), + 'exempt_channels': (None, _transform_channels_or_threads), + 'exempt_roles': (None, _transform_roles), } # fmt: on @@ -420,10 +456,18 @@ class AuditLogEntry(Hashable): which actions have this field filled out. """ - def __init__(self, *, users: Dict[int, User], data: AuditLogEntryPayload, guild: Guild): + def __init__( + self, + *, + users: Dict[int, User], + automod_rules: Dict[int, AutoModRule], + data: AuditLogEntryPayload, + guild: Guild, + ): self._state: ConnectionState = guild._state self.guild: Guild = guild self._users: Dict[int, User] = users + self._automod_rules: Dict[int, AutoModRule] = automod_rules self._from_data(data) def _from_data(self, data: AuditLogEntryPayload) -> None: @@ -614,5 +658,5 @@ class AuditLogEntry(Hashable): def _convert_target_guild_scheduled_event(self, target_id: int) -> Union[ScheduledEvent, Object]: return self.guild.get_scheduled_event(target_id) or Object(id=target_id) - def _convert_target_auto_moderation(self, target_id: int) -> Union[Member, Object]: - return self.guild.get_member(target_id) or Object(target_id) + def _convert_target_auto_moderation(self, target_id: int) -> Union[AutoModRule, Object]: + return self._automod_rules.get(target_id) or Object(target_id) diff --git a/discord/automod.py b/discord/automod.py index b17ebc100..28c508aa0 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -66,7 +66,7 @@ class AutoModRuleAction: type: :class:`AutoModRuleActionType` The type of action to take. channel_id: Optional[:class:`int`] - The ID of the channel to send the alert message to, if any. + The ID of the channel or thread to send the alert message to, if any. duration: Optional[:class:`datetime.timedelta`] The duration of the timeout to apply, if any. Has a maximum of 28 days. @@ -463,7 +463,7 @@ class AutoModAction: def channel(self) -> Optional[Union[GuildChannel, Thread]]: """Optional[Union[:class:`abc.GuildChannel`, :class:`Thread`]]: The channel this action was taken in.""" if self.channel_id: - return self.guild.get_channel(self.channel_id) + return self.guild.get_channel_or_thread(self.channel_id) return None @property diff --git a/discord/enums.py b/discord/enums.py index 85f154e9c..c41d27f1a 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -607,8 +607,10 @@ class AuditLogAction(Enum): return 'thread' elif v < 122: return 'integration_or_app_command' - elif v < 144: + elif v < 143: return 'auto_moderation' + elif v == 143: + return 'user' class UserFlags(Enum): diff --git a/discord/guild.py b/discord/guild.py index 5dac2b899..283ec376d 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3535,7 +3535,7 @@ class Guild(Hashable): after = Object(id=int(entries[0]['id'])) - return data.get('users', []), entries, after, limit + return data, entries, after, limit if user is not MISSING: user_id = user.id @@ -3571,26 +3571,37 @@ class Guild(Hashable): if retrieve < 1: return - raw_users, data, state, limit = await strategy(retrieve, state, limit) + data, raw_entries, state, limit = await strategy(retrieve, state, limit) # Terminate loop on next iteration; there's no data left after this - if len(data) < 100: + if len(raw_entries) < 100: limit = 0 if reverse: - data = reversed(data) + raw_entries = reversed(raw_entries) if predicate: - data = filter(predicate, data) + raw_entries = filter(predicate, raw_entries) - users = (User(data=raw_user, state=self._state) for raw_user in raw_users) + users = (User(data=raw_user, state=self._state) for raw_user in data.get('users', [])) user_map = {user.id: user for user in users} - for raw_entry in data: + automod_rules = ( + AutoModRule(data=raw_rule, guild=self, state=self._state) + for raw_rule in data.get('auto_moderation_rules', []) + ) + automod_rule_map = {rule.id: rule for rule in automod_rules} + + for raw_entry in raw_entries: # Weird Discord quirk if raw_entry['action_type'] is None: continue - yield AuditLogEntry(data=raw_entry, users=user_map, guild=self) + yield AuditLogEntry( + data=raw_entry, + users=user_map, + automod_rules=automod_rule_map, + guild=self, + ) async def ack(self) -> None: """|coro| diff --git a/docs/api.rst b/docs/api.rst index baab0bd67..8ec1f0e24 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2759,7 +2759,8 @@ of :class:`enum.Enum`. An automod rule was created. When this is the action, the type of :attr:`~AuditLogEntry.target` is - a :class:`Object` with the ID of the automod rule that was created. + a :class:`AutoModRule` or :class:`Object` with the ID of the automod + rule that was created. Possible attributes for :class:`AuditLogDiff`: @@ -2767,7 +2768,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.enabled` - :attr:`~AuditLogDiff.event_type` - :attr:`~AuditLogDiff.trigger_type` - - :attr:`~AuditLogDiff.trigger_metadata` + - :attr:`~AuditLogDiff.trigger` - :attr:`~AuditLogDiff.actions` - :attr:`~AuditLogDiff.exempt_roles` - :attr:`~AuditLogDiff.exempt_channels` @@ -2779,7 +2780,8 @@ of :class:`enum.Enum`. An automod rule was updated. When this is the action, the type of :attr:`~AuditLogEntry.target` is - a :class:`Object` with the ID of the automod rule that was updated. + a :class:`AutoModRule` or :class:`Object` with the ID of the automod + rule that was created. Possible attributes for :class:`AuditLogDiff`: @@ -2787,7 +2789,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.enabled` - :attr:`~AuditLogDiff.event_type` - :attr:`~AuditLogDiff.trigger_type` - - :attr:`~AuditLogDiff.trigger_metadata` + - :attr:`~AuditLogDiff.trigger` - :attr:`~AuditLogDiff.actions` - :attr:`~AuditLogDiff.exempt_roles` - :attr:`~AuditLogDiff.exempt_channels` @@ -2799,7 +2801,8 @@ of :class:`enum.Enum`. An automod rule was deleted. When this is the action, the type of :attr:`~AuditLogEntry.target` is - a :class:`Object` with the ID of the automod rule that was deleted. + a :class:`AutoModRule` or :class:`Object` with the ID of the automod + rule that was created. Possible attributes for :class:`AuditLogDiff`: @@ -2807,7 +2810,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.enabled` - :attr:`~AuditLogDiff.event_type` - :attr:`~AuditLogDiff.trigger_type` - - :attr:`~AuditLogDiff.trigger_metadata` + - :attr:`~AuditLogDiff.trigger` - :attr:`~AuditLogDiff.actions` - :attr:`~AuditLogDiff.exempt_roles` - :attr:`~AuditLogDiff.exempt_channels` @@ -5910,7 +5913,7 @@ AuditLogDiff The event type for triggering the automod rule. - :type: :class:`str` + :type: :class:`AutoModRuleEventType` .. attribute:: trigger_type @@ -5918,29 +5921,29 @@ AuditLogDiff :type: :class:`AutoModRuleTriggerType` - .. attribute:: trigger_metadata + .. attribute:: trigger - The trigger metadata for the automod rule. + The trigger for the automod rule. - :type: Dict[:class:`str`, Any] + :type: :class:`AutoModTrigger` .. attribute:: actions The actions to take when an automod rule is triggered. - :type: List[Dict[:class:`str`, Any]] + :type: List[AutoModRuleAction] .. attribute:: exempt_roles The list of roles that are exempt from the automod rule. - :type: List[:class:`str`] + :type: List[Union[:class:`Role`, :class:`Object`]] .. attribute:: exempt_channels - The list of channels that are exempt from the automod rule. + The list of channels or threads that are exempt from the automod rule. - :type: List[:class:`str`] + :type: List[:class:`abc.GuildChannel`, :class:`Thread`, :class:`Object`] .. this is currently missing the following keys: reason and application_id I'm not sure how to port these