Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement audit log events #1932

Merged
merged 16 commits into from
Feb 22, 2023
14 changes: 12 additions & 2 deletions discord/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,12 +717,22 @@ def members(self):
"""
return 1 << 1

@flag_value
@alias_flag_value
def bans(self):
""":class:`bool`: Whether guild ban related events are enabled.
""":class:`bool`: Alias of :attr:`.moderation`.

.. versionchanged:: 2.5
Changed to an alias.
"""
return 1 << 2

@flag_value
def moderation(self):
""":class:`bool`: Whether guild moderation related events are enabled.

This corresponds to the following events:

- :func:`on_audit_log_entry`
- :func:`on_member_ban`
- :func:`on_member_unban`

Expand Down
54 changes: 53 additions & 1 deletion discord/raw_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from typing import TYPE_CHECKING

from .automod import AutoModAction, AutoModTriggerType
from .enums import ChannelType, try_enum
from .enums import AuditLogAction, ChannelType, try_enum
from .types.user import User

if TYPE_CHECKING:
Expand All @@ -40,6 +40,7 @@
from .partial_emoji import PartialEmoji
from .state import ConnectionState
from .threads import Thread
from .types.raw_models import AuditLogEntryEvent
from .types.raw_models import AutoModActionExecutionEvent as AutoModActionExecution
from .types.raw_models import (
BulkMessageDeleteEvent,
Expand Down Expand Up @@ -73,6 +74,7 @@
"RawScheduledEventSubscription",
"AutoModActionExecutionEvent",
"RawThreadMembersUpdateEvent",
"RawAuditLogEntryEvent",
)


Expand Down Expand Up @@ -601,3 +603,53 @@ def __init__(self, data: ThreadMembersUpdateEvent) -> None:
self.guild_id = int(data["guild_id"])
self.member_count = int(data["member_count"])
self.data = data


class RawAuditLogEntryEvent(_RawReprMixin):
"""Represents the payload for an :func:`on_raw_audit_log_entry` event.

.. versionadded:: 2.5

Attributes
----------
action_type: :class:`AuditLogAction`
The action that was done.
id: :class:`int`
The entry ID.
user_id: :class:`int`
The ID of the user who initiated this action
target_id: :class:`int`
The ID of the target that got changed.
reason: Optional[:class:`str`]
The reason this action was done.
changes: Optional[:class:`list`]
The changes that were made to the target.
extra: Any
Extra information that this entry has that might be useful.
For most actions, this is ``None``. However, in some cases it
contains extra information. See :class:`AuditLogAction` for
which actions have this field filled out.
data: :class:`dict`
The raw data given by the `gateway <https://discord.com/developers/docs/topics/gateway-events#guild-audit-log-entry-create>`_.
"""

__slots__ = (
"id",
"user_id",
"target_id",
"action_type",
"reason",
"extra",
"changes",
)

def __init__(self, data: AuditLogEntryEvent) -> None:
self.id = int(data["id"])
self.user_id = int(data["user_id"])
self.guild_id = int(data["guild_id"])
self.target_id = int(data["target_id"])
self.action_type = try_enum(AuditLogAction, int(data["action_type"]))
self.reason = data.get("reason")
self.extra = data.get("options")
self.changes = data.get("changes")
self.data = data
21 changes: 21 additions & 0 deletions discord/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

from . import utils
from .activity import BaseActivity
from .audit_logs import AuditLogEntry
from .automod import AutoModRule
from .channel import *
from .channel import _channel_factory
Expand Down Expand Up @@ -1339,6 +1340,26 @@ def parse_guild_delete(self, data) -> None:
self._remove_guild(guild)
self.dispatch("guild_remove", guild)

def parse_guild_audit_log_entry_create(self, data) -> None:
guild = self._get_guild(int(data["guild_id"]))
if guild is None:
_log.debug(
(
"GUILD_AUDIT_LOG_ENTRY_CREATE referencing an unknown guild ID: %s."
" Discarding."
),
data["guild_id"],
)
return
payload = RawAuditLogEntryEvent(data)
payload.guild = guild
self.dispatch("raw_audit_log_entry", payload)
user = self.get_user(payload.user_id)
if user is not None:
data.pop("guild_id")
entry = AuditLogEntry(users={data["user_id"]: user}, data=data, guild=guild)
self.dispatch("audit_log_entry", entry)

def parse_guild_ban_add(self, data) -> None:
# we make the assumption that GUILD_BAN_ADD is done
# before GUILD_MEMBER_REMOVE is called
Expand Down
11 changes: 11 additions & 0 deletions discord/types/raw_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,14 @@ class ThreadMembersUpdateEvent(TypedDict):
member_count: int
added_members: NotRequired[list[ThreadMember]]
removed_member_ids: NotRequired[list[Snowflake]]


class AuditLogEntryEvent(TypedDict):
id: Snowflake
user_id: Snowflake
guild_id: Snowflake
target_id: Snowflake
action_type: int
changes: NotRequired[list[dict]]
reason: NotRequired[str]
options: NotRequired[dict]
33 changes: 31 additions & 2 deletions docs/api/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ Application Commands
:param interaction: The interaction associated to the unknown command.
:type interaction: :class:`Interaction`

Audit Logs
----------

.. function:: on_audit_log_entry(entry)

Called when an audit log entry is created.

The bot must have :attr:`~Permissions.view_audit_log` to receive this, and
:attr:`Intents.moderation` must be enabled.

.. versionadded:: 2.5

:param entry: The audit log entry that was created.
:type entry: :class:`AuditLogEntry`

.. function:: on_raw_audit_log_entry(payload)

Called when an audit log entry is created. Unlike
:func:`on_audit_log_entry`, this is called regardless of the state of the internal
user cache.

The bot must have :attr:`~Permissions.view_audit_log` to receive this, and
:attr:`Intents.moderation` must be enabled.

.. versionadded:: 2.5

:param payload: The raw event payload data.
:type payload: :class:`RawAuditLogEntryEvent`

AutoMod
-------
.. function:: on_auto_moderation_rule_create(rule)
Expand Down Expand Up @@ -120,7 +149,7 @@ Bans

Called when user gets banned from a :class:`Guild`.

This requires :attr:`Intents.bans` to be enabled.
This requires :attr:`Intents.moderation` to be enabled.

:param guild: The guild the user got banned from.
:type guild: :class:`Guild`
Expand All @@ -133,7 +162,7 @@ Bans

Called when a :class:`User` gets unbanned from a :class:`Guild`.

This requires :attr:`Intents.bans` to be enabled.
This requires :attr:`Intents.moderation` to be enabled.

:param guild: The guild the user got unbanned from.
:type guild: :class:`Guild`
Expand Down
5 changes: 5 additions & 0 deletions docs/api/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ Events
.. autoclass:: RawThreadMembersUpdateEvent()
:members:

.. attributetable:: RawAuditLogEntryEvent

.. autoclass:: RawAuditLogEntryEvent()
:members:



Webhooks
Expand Down
3 changes: 1 addition & 2 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,7 @@ Quick example: ::
Is there an event for audit log entries being created?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Since Discord does not dispatch this information in the gateway, the library cannot provide this information.
This is currently a Discord limitation.
As of version 2.5, you can receive audit log entries with the :func:`on_audit_log_entry` event.

Commands Extension
-------------------
Expand Down