From b69c941317cdec36ab254acd57523757b199a524 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 17 Jun 2017 00:41:01 -0700 Subject: [PATCH] Further progress on audit log --- disco/api/client.py | 12 ++++++---- disco/api/http.py | 3 +++ disco/cli.py | 3 +++ disco/types/base.py | 4 ++-- disco/types/guild.py | 47 +++++++++++++++++++++++++++++++++++++++- disco/types/message.py | 1 + disco/util/paginator.py | 9 ++++---- examples/basic_plugin.py | 4 ---- 8 files changed, 68 insertions(+), 15 deletions(-) diff --git a/disco/api/client.py b/disco/api/client.py index 0be4a60..02e4c11 100644 --- a/disco/api/client.py +++ b/disco/api/client.py @@ -384,15 +384,19 @@ class APIClient(LoggingClass): dict(guild=guild, emoji=emoji), headers=_reason_header(reason)) - def guilds_auditlogs_list(self, guild, before, user_id=None, action_type=None, limit=50): - r = self.http(Routes.GUILDS, dict(guild=guild), json=optional( + def guilds_auditlogs_list(self, guild, before=None, user_id=None, action_type=None, limit=50): + r = self.http(Routes.GUILDS_AUDITLOGS_LIST, dict(guild=guild), params=optional( before=before, user_id=user_id, - action_type=action_type, + action_type=int(action_type) if action_type else None, limit=limit, )) - return AuditLogEntry.create_map(self.client, r.json()['audit_log_entries']) + data = r.json() + + users = User.create_hash(self.client, 'id', data['users']) + webhooks = Webhook.create_hash(self.client, 'id', data['webhooks']) + return AuditLogEntry.create_map(self.client, r.json()['audit_log_entries'], users, webhooks, guild_id=guild) def users_me_get(self): return User.create(self.client, self.http(Routes.USERS_ME_GET).json()) diff --git a/disco/api/http.py b/disco/api/http.py index 13864a2..cc592ca 100644 --- a/disco/api/http.py +++ b/disco/api/http.py @@ -252,6 +252,8 @@ class HTTPClient(LoggingClass): # Possibly wait if we're rate limited self.limiter.check(bucket) + self.log.debug('KW: %s', kwargs) + # Make the actual request url = self.BASE_URL + route[1].format(**args) self.log.info('%s %s (%s)', route[0].value, url, kwargs.get('params')) @@ -264,6 +266,7 @@ class HTTPClient(LoggingClass): if r.status_code < 400: return r elif r.status_code != 429 and 400 <= r.status_code < 500: + self.log.warning('Request failed with code %s: %s', r.status_code, r.content) raise APIException(r) else: if r.status_code == 429: diff --git a/disco/cli.py b/disco/cli.py index df04313..f96f8d2 100644 --- a/disco/cli.py +++ b/disco/cli.py @@ -49,6 +49,9 @@ def disco_main(run=False): else: config = ClientConfig() + config.manhole_enable = args.manhole + config.manhole_bind = args.manhole_bind.split(':', 1) + for k, v in six.iteritems(vars(args)): if hasattr(config, k) and v is not None: setattr(config, k, v) diff --git a/disco/types/base.py b/disco/types/base.py index f219b9b..54cbcfd 100644 --- a/disco/types/base.py +++ b/disco/types/base.py @@ -349,8 +349,8 @@ class Model(six.with_metaclass(ModelMeta, Chainable)): return inst @classmethod - def create_map(cls, client, data, **kwargs): - return list(map(functools.partial(cls.create, client, **kwargs), data)) + def create_map(cls, client, data, *args, **kwargs): + return list(map(functools.partial(cls.create, client, *args, **kwargs), data)) @classmethod def create_hash(cls, client, key, data, **kwargs): diff --git a/disco/types/guild.py b/disco/types/guild.py index 857bf5c..aa3d2a1 100644 --- a/disco/types/guild.py +++ b/disco/types/guild.py @@ -4,6 +4,7 @@ from holster.enum import Enum from disco.gateway.packets import OPCode from disco.api.http import APIException +from disco.util.paginator import Paginator from disco.util.snowflake import to_snowflake from disco.util.functional import cached_property from disco.types.base import ( @@ -471,6 +472,18 @@ class Guild(SlottedModel, Permissible): def splash_url(self): return self.get_splash_url() + @property + def audit_log(self): + return Paginator( + self.client.api.guilds_auditlogs_list, + 'before', + self.id, + ) + + def get_audit_log_entries(self, *args, **kwargs): + return self.client.api.guilds_auditlogs_list(self.id, *args, **kwargs) + + AuditLogActionTypes = Enum( GUILD_UPDATE=1, CHANNEL_CREATE=10, @@ -560,7 +573,7 @@ class AuditLogObjectChange(SlottedModel): class AuditLogEntry(SlottedModel): id = Field(snowflake) - guild = Field(None) + guild_id = Field(snowflake) user_id = Field(snowflake) target_id = Field(snowflake) action_type = Field(enum(AuditLogActionTypes)) @@ -568,6 +581,38 @@ class AuditLogEntry(SlottedModel): options = DictField(text, text) reason = Field(text) + _cached_target = Field(None) + + @classmethod + def create(cls, client, users, webhooks, data, **kwargs): + self = super(SlottedModel, cls).create(client, data, **kwargs) + + if self.action_type in MEMBER_ACTIONS: + self._cached_target = users[self.target_id] + elif self.action_type in WEBHOOK_ACTIONS: + self._cached_target = webhooks[self.target_id] + + return self + + @cached_property + def guild(self): + return self.client.state.guilds.get(self.guild_id) + @cached_property def user(self): return self.client.state.users.get(self.user_id) + + @cached_property + def target(self): + if self.action_type in GUILD_ACTIONS: + return self.guild + elif self.action_type in CHANNEL_ACTIONS: + return self.guild.channels.get(self.target_id) + elif self.action_type in MEMBER_ACTIONS: + return self._cached_target or self.state.users.get(self.target_id) + elif self.action_type in ROLE_ACTIONS: + return self.guild.roles.get(self.target_id) + elif self.action_type in WEBHOOK_ACTIONS: + return self._cached_target + elif self.action_type in EMOJI_ACTIONS: + return self.guild.emojis.get(self.target_id) diff --git a/disco/types/message.py b/disco/types/message.py index 07b44af..3077b32 100644 --- a/disco/types/message.py +++ b/disco/types/message.py @@ -315,6 +315,7 @@ class Message(SlottedModel): return Paginator( self.client.api.channels_messages_reactions_get, + 'after', self.channel_id, self.id, emoji, diff --git a/disco/util/paginator.py b/disco/util/paginator.py index 715f8d9..7e4b732 100644 --- a/disco/util/paginator.py +++ b/disco/util/paginator.py @@ -5,25 +5,26 @@ class Paginator(object): """ Implements a class which provides paginated iteration over an endpoint. """ - def __init__(self, func, *args, **kwargs): + def __init__(self, func, sort_key, *args, **kwargs): self.func = func + self.sort_key = sort_key self.args = args self.kwargs = kwargs self._key = kwargs.pop('key', operator.attrgetter('id')) self._bulk = kwargs.pop('bulk', False) - self._after = kwargs.pop('after', None) + self._sort_key_value = kwargs.pop(self.sort_key, 0) self._buffer = [] def fill(self): - self.kwargs['after'] = self._after + self.kwargs[self.sort_key] = self._sort_key_value result = self.func(*self.args, **self.kwargs) if not len(result): return self._buffer.extend(result) - self._after = self._key(result[-1]) + self._sort_key_value = self._key(result[-1]) def next(self): return self.__next__() diff --git a/examples/basic_plugin.py b/examples/basic_plugin.py index 27e8d35..947388e 100644 --- a/examples/basic_plugin.py +++ b/examples/basic_plugin.py @@ -19,10 +19,6 @@ class BasicPlugin(Plugin): self.log.info('Message created: {}: {}'.format(msg.author, msg.content)) @Plugin.command('test') - def on_test(self, event): - c = event.guild.create_channel('WOWSOCOOL', 'text', reason='TEST REASONS') - c.delete(reason='TEST REASONS2') - @Plugin.command('echo', '') def on_echo_command(self, event, content): event.msg.reply(content)