Browse Source

Implement audit logs.

pull/552/head
Rapptz 8 years ago
parent
commit
c54a6a927d
  1. 1
      discord/__init__.py
  2. 319
      discord/audit_logs.py
  3. 87
      discord/enums.py
  4. 70
      discord/guild.py
  5. 27
      discord/http.py
  6. 109
      discord/iterators.py
  7. 4
      discord/member.py
  8. 6
      discord/permissions.py
  9. 685
      docs/api.rst

1
discord/__init__.py

@ -42,6 +42,7 @@ from .embeds import Embed
from .shard import AutoShardedClient
from .player import *
from .voice_client import VoiceClient
from .audit_logs import AuditLogChanges, AuditLogEntry, AuditLogDiff
import logging

319
discord/audit_logs.py

@ -0,0 +1,319 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from . import utils, enums
from .object import Object
from .permissions import PermissionOverwrite, Permissions
from .colour import Colour
from .invite import Invite
def _transform_verification_level(entry, data):
return enums.try_enum(enums.VerificationLevel, data)
def _transform_explicit_content_filter(entry, data):
return enums.try_enum(enums.ContentFilter, data)
def _transform_permissions(entry, data):
return Permissions(data)
def _transform_color(entry, data):
return Colour(data)
def _transform_snowflake(entry, data):
return int(data)
def _transform_channel(entry, data):
if data is None:
return None
channel = entry.guild.get_channel(int(data)) or Object(id=data)
return channel
def _transform_owner_id(entry, data):
if data is None:
return None
return entry._get_member(int(data))
def _transform_inviter_id(entry, data):
if data is None:
return None
return entry._get_member(int(data))
def _transform_overwrites(entry, data):
overwrites = []
for elem in data:
allow = Permissions(elem['allow'])
deny = Permissions(elem['deny'])
ow = PermissionOverwrite.from_pair(allow, deny)
ow_type = elem['type']
ow_id = int(elem['id'])
if ow_type == 'role':
target = utils.find(lambda r: r.id == ow_id, entry.guild.roles)
else:
target = entry._get_member(ow_id)
if target is None:
target = Object(id=ow_id)
overwrites.append((target, ow))
return overwrites
class AuditLogDiff:
def __len__(self):
return len(self.__dict__)
def __iter__(self):
return self.__dict__.items()
def __repr__(self):
return '<AuditLogDiff attrs={0!r}>'.format(tuple(self.__dict__))
class AuditLogChanges:
TRANSFORMERS = {
'verification_level': (None, _transform_verification_level),
'explicit_content_filter': (None, _transform_explicit_content_filter),
'allow': (None, _transform_permissions),
'deny': (None, _transform_permissions),
'permissions': (None, _transform_permissions),
'id': (None, _transform_snowflake),
'color': ('colour', _transform_color),
'owner_id': ('owner', _transform_owner_id),
'inviter_id': ('inviter', _transform_inviter_id),
'channel_id': ('channel', _transform_channel),
'afk_channel_id': ('afk_channel', _transform_channel),
'widget_channel_id': ('widget_channel', _transform_channel),
'permission_overwrites': ('overwrites', _transform_overwrites),
'splash_hash': ('splash', None),
'icon_hash': ('icon', None),
'avatar_hash': ('avatar', None),
}
def __init__(self, entry, data):
self.before = AuditLogDiff()
self.after = AuditLogDiff()
for elem in data:
attr = elem['key']
# special cases for role add/remove
if attr == '$add':
self._handle_role(self.before, self.after, entry, elem)
continue
elif attr == '$remove':
self._handle_role(self.after, self.before, entry, elem)
continue
transformer = self.TRANSFORMERS.get(attr)
if transformer:
attr, transformer = transformer
try:
before = elem['old_value']
except KeyError:
before = None
else:
if transformer:
before = transformer(entry, before)
setattr(self.before, attr, before)
try:
after = elem['new_value']
except KeyError:
after = None
else:
if transformer:
after = transformer(entry, after)
setattr(self.after, attr, after)
# add an alias
if hasattr(self.after, 'colour'):
self.after.color = self.after.colour
self.before.color = self.before.colour
def _handle_role(self, first, second, entry, elem):
setattr(first, 'role', None)
# TODO: partial data?
role_id = int(elem['id'])
role = utils.find(lambda r: r.id == role_id, entry.guild.roles)
if role is None:
role = discord.Object(id=role_id)
role.name = elem['name']
setattr(second, 'role', role)
class AuditLogEntry:
"""Represents an Audit Log entry.
You retrieve these via :meth:`Guild.audit_log`.
Attributes
-----------
action: :class:`AuditLogAction`
The action that was done.
user: :class:`abc.User`
The user who initiated this action. Usually a :class:`Member`\, unless gone
then it's a :class:`User`.
id: int
The entry ID.
target: Any
The target that got changed. The exact type of this depends on
the action being done.
reason: Optional[str]
The reason this action was done.
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.
"""
def __init__(self, *, users, data, guild):
self._state = guild._state
self.guild = guild
self._users = users
self._from_data(data)
def _from_data(self, data):
self.action = enums.AuditLogAction(data['action_type'])
self.id = int(data['id'])
# this key is technically not usually present
self.reason = data.get('reason')
self.extra = data.get('options')
if self.extra:
if self.action is enums.AuditLogAction.member_prune:
# member prune has two keys with useful information
self.extra = type('_AuditLogProxy', (), {k: int(v) for k, v in self.extra.items()})()
elif self.action.name.startswith('overwrite_'):
# the overwrite_ actions have a dict with some information
instance_id = int(self.extra['id'])
the_type = self.extra.get('type')
if the_type == 'member':
self.extra = self._get_member(instance_id)
else:
role = utils.find(lambda r: r.id == instance_id, self.guild.roles)
if role is None:
role = Object(id=instance_id)
role.name = self.extra.get('role_name')
self.extra = role
# this key is not present when the above is present, typically.
# It's a list of { new_value: a, old_value: b, key: c }
# where new_value and old_value are not guaranteed to be there depending
# on the action type, so let's just fetch it for now and only turn it
# into meaningful data when requested
self._changes = data.get('changes', [])
self.user = self._get_member(int(data['user_id']))
self._target_id = utils._get_as_snowflake(data, 'target_id')
def _get_member(self, user_id):
return self.guild.get_member(user_id) or self._users.get(user_id)
def __repr__(self):
return '<AuditLogEntry id={0.id} action={0.action} user={0.user!r}>'.format(self)
@utils.cached_property
def created_at(self):
"""Returns the entry's creation time in UTC."""
return utils.snowflake_time(self.id)
@utils.cached_property
def target(self):
try:
converter = getattr(self, '_convert_target_' + self.action.target_type)
except AttributeError:
return Object(id=self._target_id)
else:
return converter(self._target_id)
@utils.cached_property
def category(self):
"""Optional[:class:`AuditLogActionCategory`]: The category of the action, if applicable."""
return self.action.category
@utils.cached_property
def changes(self):
""":class:`AuditLogChanges`: The list of changes this entry has."""
obj = AuditLogChanges(self, self._changes)
del self._changes
return obj
@utils.cached_property
def before(self):
""":class:`AuditLogDiff`: The target's prior state."""
return self.changes.before
@utils.cached_property
def after(self):
""":class:`AuditLogDiff`: The target's subsequent state."""
return self.changes.after
def _convert_target_guild(self, target_id):
return self.guild
def _convert_target_channel(self, target_id):
ch = self.guild.get_channel(target_id)
if ch is None:
return Object(id=target_id)
return ch
def _convert_target_user(self, target_id):
return self._get_member(target_id)
def _convert_target_role(self, target_id):
role = utils.find(lambda r: r.id == target_id, self.guild.roles)
if role is None:
return Object(id=target_id)
return role
def _convert_target_invite(self, target_id):
# invites have target_id set to null
# so figure out which change has the full invite data
changeset = self.before if self.action is enums.AuditLogAction.invite_delete else self.after
fake_payload = {
'max_age': changeset.max_age,
'max_uses': changeset.max_uses,
'code': changeset.code,
'temporary': changeset.temporary,
'channel': changeset.channel,
'uses': changeset.uses,
'guild': self.guild,
}
obj = Invite(state=self._state, data=fake_payload)
obj.inviter = changeset.inviter
return obj
def _convert_target_emoji(self, target_id):
return self._state.get_emoji(target_id) or Object(id=target_id)

87
discord/enums.py

@ -27,7 +27,8 @@ DEALINGS IN THE SOFTWARE.
from enum import Enum
__all__ = ['ChannelType', 'MessageType', 'GuildRegion', 'VerificationLevel',
'ContentFilter', 'Status', 'DefaultAvatar', 'RelationshipType' ]
'ContentFilter', 'Status', 'DefaultAvatar', 'RelationshipType',
'AuditLogAction', 'AuditLogActionCategory', ]
class ChannelType(Enum):
text = 0
@ -114,6 +115,90 @@ class RelationshipType(Enum):
incoming_request = 3
outgoing_request = 4
class AuditLogActionCategory(Enum):
create = 1
delete = 2
update = 3
class AuditLogAction(Enum):
guild_update = 1
channel_create = 10
channel_update = 11
channel_delete = 12
overwrite_create = 13
overwrite_update = 14
overwrite_delete = 15
kick = 20
member_prune = 21
ban = 22
unban = 23
member_update = 24
member_role_update = 25
role_create = 30
role_update = 31
role_delete = 32
invite_create = 40
invite_update = 41
invite_delete = 42
webhook_create = 50
webhook_update = 51
webhook_delete = 52
emoji_create = 60
emoji_update = 61
emoji_delete = 62
@property
def category(self):
lookup = {
AuditLogAction.guild_update: AuditLogActionCategory.update,
AuditLogAction.channel_create: AuditLogActionCategory.create,
AuditLogAction.channel_update: AuditLogActionCategory.update,
AuditLogAction.channel_delete: AuditLogActionCategory.delete,
AuditLogAction.overwrite_create: AuditLogActionCategory.create,
AuditLogAction.overwrite_update: AuditLogActionCategory.update,
AuditLogAction.overwrite_delete: AuditLogActionCategory.delete,
AuditLogAction.kick: None,
AuditLogAction.member_prune: None,
AuditLogAction.ban: None,
AuditLogAction.unban: None,
AuditLogAction.member_update: AuditLogActionCategory.update,
AuditLogAction.member_role_update: AuditLogActionCategory.update,
AuditLogAction.role_create: AuditLogActionCategory.create,
AuditLogAction.role_update: AuditLogActionCategory.update,
AuditLogAction.role_delete: AuditLogActionCategory.delete,
AuditLogAction.invite_create: AuditLogActionCategory.create,
AuditLogAction.invite_update: AuditLogActionCategory.update,
AuditLogAction.invite_delete: AuditLogActionCategory.delete,
AuditLogAction.webhook_create: AuditLogActionCategory.create,
AuditLogAction.webhook_update: AuditLogActionCategory.update,
AuditLogAction.webhook_delete: AuditLogActionCategory.delete,
AuditLogAction.emoji_create: AuditLogActionCategory.create,
AuditLogAction.emoji_update: AuditLogActionCategory.update,
AuditLogAction.emoji_delete: AuditLogActionCategory.delete,
}
return lookup[self]
@property
def target_type(self):
v = self.value
if v == -1:
return 'all'
elif v < 10:
return 'guild'
elif v < 20:
return 'channel'
elif v < 30:
return 'user'
elif v < 40:
return 'role'
elif v < 50:
return 'invite'
elif v < 60:
return 'webhook'
elif v < 70:
return 'emoji'
def try_enum(cls, val):
"""A function that tries to turn the value into enum ``cls``.

70
discord/guild.py

@ -41,6 +41,7 @@ from .enums import GuildRegion, Status, ChannelType, try_enum, VerificationLevel
from .mixins import Hashable
from .user import User
from .invite import Invite
from .iterators import AuditLogIterator
BanEntry = namedtuple('BanEntry', 'reason user')
@ -921,7 +922,7 @@ class Guild(Hashable):
return role
@asyncio.coroutine
def kick(self, user):
def kick(self, user, *, reason=None):
"""|coro|
Kicks a user from the guild.
@ -935,6 +936,8 @@ class Guild(Hashable):
-----------
user: :class:`abc.Snowflake`
The user to kick from their guild.
reason: Optional[str]
The reason the user got kicked.
Raises
-------
@ -943,10 +946,10 @@ class Guild(Hashable):
HTTPException
Kicking failed.
"""
yield from self._state.http.kick(user.id, self.id)
yield from self._state.http.kick(user.id, self.id, reason=reason)
@asyncio.coroutine
def ban(self, user, *, delete_message_days=1):
def ban(self, user, *, reason=None, delete_message_days=1):
"""|coro|
Bans a user from the guild.
@ -963,6 +966,8 @@ class Guild(Hashable):
delete_message_days: int
The number of days worth of messages to delete from the user
in the guild. The minimum is 0 and the maximum is 7.
reason: Optional[str]
The reason the user got banned.
Raises
-------
@ -971,7 +976,7 @@ class Guild(Hashable):
HTTPException
Banning failed.
"""
yield from self._state.http.ban(user.id, self.id, delete_message_days)
yield from self._state.http.ban(user.id, self.id, delete_message_days, reason=reason)
@asyncio.coroutine
def unban(self, user):
@ -1017,3 +1022,60 @@ class Guild(Hashable):
if state.is_bot:
raise ClientException('Must not be a bot account to ack messages.')
return state.http.ack_guild(self.id)
def audit_logs(self, *, limit=100, before=None, after=None, reverse=None, user=None, action=None):
"""Return an :class:`AsyncIterator` that enables receiving the guild's audit logs.
You must have :attr:`Permissions.view_audit_log` permission to use this.
Parameters
-----------
limit: Optional[int]
The number of entries to retrieve. If ``None`` retrieve all entries.
before: Union[:class:`abc.Snowflake`, datetime]
Retrieve entries before this date or entry.
If a date is provided it must be a timezone-naive datetime representing UTC time.
after: Union[:class:`abc.Snowflake`, datetime]
Retrieve entries after this date or entry.
If a date is provided it must be a timezone-naive datetime representing UTC time.
reverse: bool
If set to true, return entries in oldest->newest order. If unspecified,
this defaults to ``False`` for most cases. However if passing in a
``after`` parameter then this is set to ``True``. This avoids getting entries
out of order in the ``after`` case.
user: :class:`abc.Snowflake`
The moderator to filter entries from.
action: :class:`AuditLogAction`
The action to filter with.
Yields
--------
:class:`AuditLogEntry`
The audit log entry.
Examples
----------
Getting the first 100 entries: ::
async for entry in guild.audit_logs(limit=100):
print('{0.user} did {0.action} to {0.target}'.format(entry))
Getting entries for a specific action: ::
async for entry in guild.audit_logs(action=discord.AuditLogAction.ban):
print('{0.user} banned {0.target}'.format(entry))
Getting entries made by a specific user: ::
entries = await guild.audit_logs(limit=None, user=guild.me).flatten()
await guild.default_channel.send('I made {} moderation actions.'.format(len(entries)))
"""
if user:
user = user.id
if action:
action = action.value
return AuditLogIterator(self, before=before, after=after, limit=limit,
reverse=reverse, user_id=user, action_type=action)

27
discord/http.py

@ -410,15 +410,20 @@ class HTTPClient:
# Member management
def kick(self, user_id, guild_id):
def kick(self, user_id, guild_id, reason=None):
r = Route('DELETE', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id)
return self.request(r)
if reason:
return self.request(r, params={'reason': reason })
return self.request(r, params=params)
def ban(self, user_id, guild_id, delete_message_days=1):
def ban(self, user_id, guild_id, delete_message_days=1, reason=None):
r = Route('PUT', '/guilds/{guild_id}/bans/{user_id}', guild_id=guild_id, user_id=user_id)
params = {
'delete-message-days': delete_message_days
'delete-message-days': delete_message_days,
}
if reason:
params['reason'] = reason
return self.request(r, params=params)
def unban(self, user_id, guild_id):
@ -557,6 +562,20 @@ class HTTPClient:
r = Route('PATCH', '/guilds/{guild_id}/emojis/{emoji_id}', guild_id=guild_id, emoji_id=emoji_id)
return self.request(r, json=payload)
def get_audit_logs(self, guild_id, limit=100, before=None, after=None, user_id=None, action_type=None):
params = { 'limit': limit }
if before:
params['before'] = before
if after:
params['after'] = after
if user_id:
params['user_id'] = user_id
if action_type:
params['action_type'] = action_type
r = Route('GET', '/guilds/{guild_id}/audit-logs', guild_id=guild_id)
return self.request(r, params=params)
# Invite management
def create_invite(self, channel_id, **options):

109
discord/iterators.py

@ -31,6 +31,7 @@ import datetime
from .errors import NoMoreItems
from .utils import time_snowflake, maybe_coroutine
from .object import Object
from .audit_logs import AuditLogEntry
PY35 = sys.version_info >= (3, 5)
@ -369,3 +370,111 @@ class HistoryIterator(_AsyncIterator):
self.around = None
return data
return []
class AuditLogIterator(_AsyncIterator):
def __init__(self, guild, limit=None, before=None, after=None, reverse=None, user_id=None, action_type=None):
if isinstance(before, datetime.datetime):
before = Object(id=time_snowflake(before, high=False))
if isinstance(after, datetime.datetime):
after = Object(id=time_snowflake(after, high=True))
self.guild = guild
self.loop = guild._state.loop
self.request = guild._state.http.get_audit_logs
self.limit = limit
self.before = before
self.user_id = user_id
self.action_type = action_type
self.after = after
self._users = {}
self._state = guild._state
if reverse is None:
self.reverse = after is not None
else:
self.reverse = reverse
self._filter = None # entry dict -> bool
self.entries = asyncio.Queue(loop=self.loop)
if self.before and self.after:
if self.reverse:
self._strategy = self._after_strategy
self._filter = lambda m: int(m['id']) < self.before.id
else:
self._strategy = self._before_strategy
self._filter = lambda m: int(m['id']) > self.after.id
elif self.after:
self._strategy = self._after_strategy
else:
self._strategy = self._before_strategy
@asyncio.coroutine
def _before_strategy(self, retrieve):
before = self.before.id if self.before else None
data = yield from self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
action_type=self.action_type, before=before)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.before = Object(id=int(data['audit_log_entries'][-1]['id']))
return data
@asyncio.coroutine
def _after_strategy(self, retrieve):
after = self.after.id if self.after else None
data = yield from self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
action_type=self.action_type, after=after)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.after = Object(id=int(data['audit_log_entries'][0]['id']))
return data
@asyncio.coroutine
def get(self):
if self.entries.empty():
yield from self._fill()
try:
return self.entries.get_nowait()
except asyncio.QueueEmpty:
raise NoMoreItems()
def _get_retrieve(self):
l = self.limit
if l is None:
r = 100
elif l <= 100:
r = l
else:
r = 100
self.retrieve = r
return r > 0
@asyncio.coroutine
def _fill(self):
from .user import User
if self._get_retrieve():
data = yield from self._strategy(self.retrieve)
users = data.get('users', [])
data = data.get('audit_log_entries', [])
if self.limit is None and len(data) < 100:
self.limit = 0 # terminate the infinite loop
if self.reverse:
data = reversed(data)
if self._filter:
data = filter(self._filter, data)
for user in users:
u = User(data=user, state=self._state)
self._users[u.id] = u
for element in data:
yield from self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild))

4
discord/member.py

@ -357,12 +357,12 @@ class Member(discord.abc.Messageable):
yield from self.guild.unban(self)
@asyncio.coroutine
def kick(self):
def kick(self, *, reason=None):
"""|coro|
Kicks this member. Equivalent to :meth:`Guild.kick`
"""
yield from self.guild.kick(self)
yield from self.guild.kick(self, reason=reason)
@asyncio.coroutine
def edit(self, **fields):

6
discord/permissions.py

@ -284,12 +284,12 @@ class Permissions:
self._set(6, value)
@property
def view_audit_log(self):
def view_audit_logs(self):
"""Returns True if a user can view the guild's audit log."""
return self._bit(7)
@view_audit_log.setter
def view_audit_log(self, value):
@view_audit_logs.setter
def view_audit_logs(self, value):
self._set(7, value)
# 2 unused

685
docs/api.rst

@ -709,6 +709,400 @@ All enumerations are subclasses of `enum`_.
You have sent a friend request to this user.
.. class:: AuditLogAction
Represents the type of action being done for a :class:`AuditLogEntry`\,
which is retrievable via :meth:`Guild.audit_log`.
.. attribute:: guild_update
The guild has updated. Things that trigger this include:
- Changing the guild vanity URL
- Changing the guild invite splash
- Changing the guild AFK channel or timeout
- Changing the guild voice server region
- Changing the guild icon
- Changing the guild moderation settings
- Changing things related to the guild widget
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Guild`.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.afk_channel`
- :attr:`~AuditLogDiff.afk_timeout`
- :attr:`~AuditLogDiff.default_message_notifications`
- :attr:`~AuditLogDiff.explicit_content_filter`
- :attr:`~AuditLogDiff.mfa_level`
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.owner`
- :attr:`~AuditLogDiff.splash`
- :attr:`~AuditLogDiff.vanity_url_code`
.. attribute:: channel_create
A new channel was created.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
either a :class:`abc.GuildChannel` or :class:`Object` with an ID.
A more filled out object in the :class:`Object` case can be found
by using :attr:`~AuditLogEntry.after`.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.type`
- :attr:`~AuditLogDiff.overwrites`
.. attribute:: channel_update
A channel was updated. Things that trigger this include:
- The channel name or topic was changed
- The channel bitrate was changed
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`abc.GuildChannel` or :class:`Object` with an ID.
A more filled out object in the :class:`Object` case can be found
by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.type`
- :attr:`~AuditLogDiff.position`
- :attr:`~AuditLogDiff.overwrites`
- :attr:`~AuditLogDiff.topic`
- :attr:`~AuditLogDiff.bitrate`
.. attribute:: channel_delete
A channel was deleted.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
an :class:`Object` with an ID.
A more filled out object can be found by using the
:attr:`~AuditLogEntry.before` object.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.type`
- :attr:`~AuditLogDiff.overwrites`
.. attribute:: overwrite_create
A channel permission overwrite was created.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`abc.GuildChannel` or :class:`Object` with an ID.
When this is the action, the type of :attr:`~AuditLogEntry.extra` is
either a :class:`Role` or :class:`Member`. If the object is not found
then it is a :class:`Object` with an ID being filled, a name, and a
``type`` attribute set to either ``'role'`` or ``'member'`` to help
dictate what type of ID it is.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.deny`
- :attr:`~AuditLogDiff.allow`
- :attr:`~AuditLogDiff.id`
- :attr:`~AuditLogDiff.type`
.. attribute:: overwrite_update
A channel permission overwrite was changed, this is typically
when the permission values change.
See :attr:`overwrite_create` for more information on how the
:attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields
are set.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.deny`
- :attr:`~AuditLogDiff.allow`
- :attr:`~AuditLogDiff.id`
- :attr:`~AuditLogDiff.type`
.. attribute:: overwrite_delete
A channel permission overwrite was deleted.
See :attr:`overwrite_create` for more information on how the
:attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields
are set.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.deny`
- :attr:`~AuditLogDiff.allow`
- :attr:`~AuditLogDiff.id`
- :attr:`~AuditLogDiff.type`
.. attribute:: kick
A member was kicked.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`User` who got kicked.
When this is the action, :attr:`~AuditLogEntry.changes` is empty.
.. attribute:: member_prune
A member prune was triggered.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
set to `None`.
When this is the action, the type of :attr:`~AuditLogEntry.extra` is
set to an unspecified proxy object with two attributes:
- ``delete_members_days``: An integer specifying how far the prune was.
- ``members_removed``: An integer specifying how many members were removed.
When this is the action, :attr:`~AuditLogEntry.changes` is empty.
.. attribute:: ban
A member was banned.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`User` who got banned.
When this is the action, :attr:`~AuditLogEntry.changes` is empty.
.. attribute:: unban
A member was unbanned.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`User` who got unbanned.
When this is the action, :attr:`~AuditLogEntry.changes` is empty.
.. attribute:: member_update
A member has updated. This triggers in the following situations:
- A nickname was changed
- They were server muted or deafened (or it was undo'd)
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Member` or :class:`User` who got updated.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.nick`
- :attr:`~AuditLogDiff.mute`
- :attr:`~AuditLogDiff.deaf`
.. attribute:: member_role_update
A member's role has been updated. This triggers when a member
either gains a role or losses a role.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Member` or :class:`User` who got the role.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.role`
.. attribute:: role_create
A new role was created.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Role` or a :class:`Object` with the ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.colour`
- :attr:`~AuditLogDiff.mentionable`
- :attr:`~AuditLogDiff.hoist`
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.permissions`
.. attribute:: role_update
A role was updated. This triggers in the following situations:
- The name has changed
- The permissions have changed
- The colour has changed
- Its hoist/mentionable state has changed
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Role` or a :class:`Object` with the ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.colour`
- :attr:`~AuditLogDiff.mentionable`
- :attr:`~AuditLogDiff.hoist`
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.permissions`
.. attribute:: role_delete
A role was deleted.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Role` or a :class:`Object` with the ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.colour`
- :attr:`~AuditLogDiff.mentionable`
- :attr:`~AuditLogDiff.hoist`
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.permissions`
.. attribute:: invite_create
An invite was created.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Invite` that was created.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.max_age`
- :attr:`~AuditLogDiff.code`
- :attr:`~AuditLogDiff.temporary`
- :attr:`~AuditLogDiff.inviter`
- :attr:`~AuditLogDiff.channel`
- :attr:`~AuditLogDiff.uses`
- :attr:`~AuditLogDiff.max_uses`
.. attribute:: invite_update
An invite was updated.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Invite` that was updated.
.. attribute:: invite_delete
An invite was deleted.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Invite` that was deleted.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.max_age`
- :attr:`~AuditLogDiff.code`
- :attr:`~AuditLogDiff.temporary`
- :attr:`~AuditLogDiff.inviter`
- :attr:`~AuditLogDiff.channel`
- :attr:`~AuditLogDiff.uses`
- :attr:`~AuditLogDiff.max_uses`
.. attribute:: webhook_create
A webhook was created.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Object` with the webhook ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.channel`
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.type` (always set to ``1`` if so)
.. attribute:: webhook_update
A webhook was updated. This trigger in the following situations:
- The webhook name changed
- The webhook channel changed
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Object` with the webhook ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.channel`
- :attr:`~AuditLogDiff.name`
.. attribute:: webhook_delete
A webhook was deleted.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Object` with the webhook ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.channel`
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.type` (always set to ``1`` if so)
.. attribute:: emoji_create
An emoji was created.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Emoji` or :class:`Object` with the emoji ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.name`
.. attribute:: emoji_update
An emoji was updated. This triggers when the name has changed.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Emoji` or :class:`Object` with the emoji ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.name`
.. attribute:: emoji_delete
An emoji was deleted.
When this is the action, the type of :attr:`~AuditLogEntry.target` is
the :class:`Object` with the emoji ID.
Possible attributes for :class:`AuditLogDiff`:
- :attr:`~AuditLogDiff.name`
.. class:: AuditLogActionCategory
Represents the category that the :class:`AuditLogAction` belongs to.
This can be retrieved via :attr:`AuditLogEntry.category`.
.. attribute:: create
The action is the creation of something.
.. attribute:: delete
The action is the deletion of something.
.. attribute:: update
The action is the update of something.
Async Iterator
----------------
@ -785,6 +1179,297 @@ Certain utilities make working with async iterators easier, detailed below.
:param predicate: The predicate to call on every element. Could be a coroutine.
:return: An async iterator.
Audit Log Data
----------------
Working with :meth:`Guild.audit_logs` is a complicated process with a lot of machinery
involved. The library attempts to make it easy to use and friendly. In order to accomplish
this goal, it must make use of a couple of data classes that aid in this goal.
.. autoclass:: AuditLogEntry
:members:
.. class:: AuditLogChanges
An audit log change set.
.. attribute:: before
The old value. The attribute has the type of :class:`AuditLogDiff`.
Depending on the :class:`AuditLogActionCategory` retrieved by
:attr:`~AuditLogEntry.category`\, the data retrieved by this
attribute differs:
+----------------------------------------+---------------------------------------------------+
| Category | Description |
+----------------------------------------+---------------------------------------------------+
| :attr:`~AuditLogActionCategory.create` | All attributes are set to ``None``. |
+----------------------------------------+---------------------------------------------------+
| :attr:`~AuditLogActionCategory.delete` | All attributes are set the value before deletion. |
+----------------------------------------+---------------------------------------------------+
| :attr:`~AuditLogActionCategory.update` | All attributes are set the value before updating. |
+----------------------------------------+---------------------------------------------------+
| ``None`` | No attributes are set. |
+----------------------------------------+---------------------------------------------------+
.. attribute:: after
The new value. The attribute has the type of :class:`AuditLogDiff`.
Depending on the :class:`AuditLogActionCategory` retrieved by
:attr:`~AuditLogEntry.category`\, the data retrieved by this
attribute differs:
+----------------------------------------+--------------------------------------------------+
| Category | Description |
+----------------------------------------+--------------------------------------------------+
| :attr:`~AuditLogActionCategory.create` | All attributes are set to the created value |
+----------------------------------------+--------------------------------------------------+
| :attr:`~AuditLogActionCategory.delete` | All attributes are set to ``None`` |
+----------------------------------------+--------------------------------------------------+
| :attr:`~AuditLogActionCategory.update` | All attributes are set the value after updating. |
+----------------------------------------+--------------------------------------------------+
| ``None`` | No attributes are set. |
+----------------------------------------+--------------------------------------------------+
.. class:: AuditLogDiff
Represents an audit log "change" object. A change object has dynamic
attributes that depend on the type of action being done. Certain actions
map to certain attributes being set.
Note that accessing an attribute that does not match the specified action
will lead to an attribute error.
To get a list of attributes that have been set, you can iterate over
them. To see a list of all possible attributes that could be set based
on the action being done, check the documentation for :class:`AuditLogAction`,
otherwise check the documentation below for all attributes that are possible.
.. describe:: iter(diff)
Return an iterator over (attribute, value) tuple of this diff.
.. attribute:: name
*str* – A name of something.
.. attribute:: icon
*str* – A guild's icon hash. See also :attr:`Guild.icon`.
.. attribute:: splash
*str* – The guild's invite splash hash. See also :attr:`Guild.splash`.
.. attribute:: owner
*Union[:class:`Member`, :class:`User`]`* – The guild's owner. See also :attr:`Guild.owner`
.. attribute:: region
*:class:`GuildRegion`* – The guild's voice region. See also :attr:`Guild.region`.
.. attribute:: afk_channel
*Union[:class:`VoiceChannel`, :class:`Object`]* – The guild's AFK channel.
If this could not be found, then it falls back to a :class:`Object`
with the ID being set.
See :attr:`Guild.afk_channel`.
.. attribute:: afk_timeout
*int* – The guild's AFK timeout. See :attr:`Guild.afk_timeout`.
.. attribute:: mfa_level
*int* - The guild's MFA level. See :attr:`Guild.mfa_level`.
.. attribute:: widget_enabled
*bool* – The guild's widget has been enabled or disabled.
.. attribute:: widget_channel
*Union[:class:`TextChannel`, :class:`Object`]* – The widget's channel.
If this could not be found then it falls back to a :class:`Object`
with the ID being set.
.. attribute:: verification_level
*:class:`VerificationLevel`* – The guild's verification level.
See also :attr:`Guild.verification_level`.
.. attribute:: explicit_content_filter
*:class:`ContentFilter`* – The guild's content filter.
See also :attr:`Guild.explicit_content_filter`.
.. attribute:: default_message_notifications
*int* – The guild's default message notification setting.
.. attribute:: vanity_url_code
*str* – The guild's vanity URL.
.. attribute:: position
*int* – The position of a :class:`Role` or :class:`abc.GuildChannel`.
.. attribute:: type
*Union[int, str]* – The type of channel or channel permission overwrite.
If the type is an ``int``, then it is a type of channel which can be either
``0`` to indicate a text channel or ``1`` to indicate a voice channel.
If the type is a ``str``, then it is a type of permission overwrite which
can be either ``'role'`` or ``'member'``.
.. attribute:: topic
*str* – The topic of a :class:`TextChannel`.
See also :attr:`TextChannel.topic`.
.. attribute:: bitrate
*int* – The bitrate of a :class:`VoiceChannel`.
See also :attr:`VoiceChannel.bitrate`.
.. attribute:: overwrites
*List[Tuple[target, :class:`PermissionOverwrite`]]* – A list of
permission overwrite tuples that represents a target and a
:class:`PermissionOverwrite` for said target.
The first element is the object being targeted, which can either
be a :class:`Member` or :class:`User` or :class:`Role`. If this object
is not found then it is a :class:`Object` with an ID being filled and
a ``type`` attribute set to either ``'role'`` or ``'member'`` to help
decide what type of ID it is.
.. attribute:: role
*Union[:class:`Role`, :class:`Object`]* – A role being added or removed
from a member.
If the role is not found then it is a :class:`Object` with the ID being
filled in.
.. attribute:: nick
*Optional[str]* – The nickname of a member.
See also :attr:`Member.nick`
.. attribute:: deaf
*bool* – Whether the member is being server deafened.
See also :attr:`VoiceState.deaf`.
.. attribute:: mute
*bool* – Whether the member is being server muted.
See also :attr:`VoiceState.mute`.
.. attribute:: permissions
*:class:`Permissions`* – The permissions of a role.
See also :attr:`Role.permissions`.
.. attribute:: colour
color
*:class:`Colour`* – The colour of a role.
See also :attr:`Role.colour`
.. attribute:: hoist
*bool* – Whether the role is being hoisted or not.
See also :attr:`Role.hoist`
.. attribute:: mentionable
*bool* – Whether the role is mentionable or not.
See also :attr:`Role.mentionable`
.. attribute:: code
*str* – The invite's code.
See also :attr:`Invite.code`
.. attribute:: channel
*Union[:class:`abc.GuildChannel`, :class:`Object`]* – A guild channel.
If the channel is not found then it is a :class:`Object` with the ID
being set. In some cases the channel name is also set.
.. attribute:: inviter
*:class:`User`* – The user who created the invite.
See also :attr:`Invite.inviter`.
.. attribute:: max_uses
*int* – The invite's max uses.
See also :attr:`Invite.max_uses`.
.. attribute:: uses
*int* – The invite's current uses.
See also :attr:`Invite.uses`.
.. attribute:: max_age
*int* – The invite's max age in seconds.
See also :attr:`Invite.max_age`.
.. attribute:: temporary
*bool* – If the invite is a temporary invite.
See also :attr:`Invite.temporary`.
.. attribute:: allow
deny
*:class:`Permissions`* – The permissions being allowed or denied.
.. attribute:: id
*int* – The ID of the object being changed.
.. attribute:: avatar
*str* – The avatar hash of a member.
See also :attr:`User.avatar`.
.. this is currently missing the following keys: reason and application_id
I'm not sure how to about porting these
.. _discord_api_data:
Data Classes

Loading…
Cancel
Save