From 04b8049cc78c4ff0682c354929b14c29e068d955 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Tue, 20 Jun 2017 16:13:52 +0100 Subject: [PATCH 1/4] Initial audit logs implementation Frankly, I have no idea if any of this is in the correct place, or follows the "norm" for internal stuff. Feedback would be nice for this implementation, as I believe this implementation uses the least amount of partial objects (I couldn't really avoid them in RestAuditLogEntry, unless I wanted to write entry types for each type of entry) and is also fairly simple. --- src/Discord.Net.Core/DiscordConfig.cs | 1 + .../Entities/AuditLogs/ActionType.cs | 50 +++++++++++++++++++ .../Entities/AuditLogs/IAuditLogChange.cs | 16 ++++++ .../Entities/AuditLogs/IAuditLogEntry.cs | 44 ++++++++++++++++ .../Entities/AuditLogs/IAuditLogOptions.cs | 16 ++++++ .../Entities/Guilds/IGuild.cs | 3 ++ src/Discord.Net.Rest/API/Common/AuditLog.cs | 17 +++++++ .../API/Common/AuditLogChange.cs | 17 +++++++ .../API/Common/AuditLogEntry.cs | 26 ++++++++++ .../API/Common/AuditLogOptions.cs | 14 ++++++ .../API/Rest/GetAuditLogsParams.cs | 8 +++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 15 ++++++ .../Entities/AuditLogs/AuditLogHelper.cs | 37 ++++++++++++++ .../Changes/MemberRoleAuditLogChange.cs | 19 +++++++ .../Options/MessageDeleteAuditLogOptions.cs | 23 +++++++++ .../Entities/AuditLogs/RestAuditLogEntry.cs | 47 +++++++++++++++++ .../Entities/Guilds/GuildHelper.cs | 29 +++++++++++ .../Entities/Guilds/RestGuild.cs | 12 +++++ .../Entities/Guilds/SocketGuild.cs | 12 +++++ 19 files changed, 406 insertions(+) create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLog.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogChange.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogEntry.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogOptions.cs create mode 100644 src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index fd2fe92e8..31f187ffb 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -20,6 +20,7 @@ namespace Discord public const int MaxMessagesPerBatch = 100; public const int MaxUsersPerBatch = 1000; public const int MaxGuildsPerBatch = 100; + public const int MaxAuditLogEntriesPerBatch = 100; /// Gets or sets how a request should act in the case of an error, by default. public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; diff --git a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs new file mode 100644 index 000000000..e5a4ff30a --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// The action type within a + /// + public enum ActionType + { + GuildUpdated = 1, + + ChannelCreated = 10, + ChannelUpdated = 11, + ChannelDeleted = 12, + + OverwriteCreated = 13, + OverwriteUpdated = 14, + OverwriteDeleted = 15, + + Kick = 20, + Prune = 21, + Ban = 22, + Unban = 23, + + MemberUpdated = 24, + MemberRoleUpdated = 25, + + RoleCreated = 30, + RoleUpdated = 31, + RoleDeleted = 32, + + InviteCreated = 40, + InviteUpdated = 41, + InviteDeleted = 42, + + WebhookCreated = 50, + WebhookUpdated = 51, + WebhookDeleted = 52, + + EmojiCreated = 60, + EmojiUpdated = 61, + EmojiDeleted = 62, + + MessageDeleted = 72 + } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs new file mode 100644 index 000000000..0c36b53b2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents changes which may occur within a + /// + public interface IAuditLogChange + { + + } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs new file mode 100644 index 000000000..0569f5fff --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents an entry in an audit log + /// + public interface IAuditLogEntry : IEntity + { + /// + /// The action which occured to create this entry + /// + ActionType Action { get; } + + /// + /// The changes which occured within this entry. May be empty if no changes occured. + /// + IReadOnlyCollection Changes { get; } + + /// + /// Any options which apply to this entry. If no options were provided, this may be . + /// + IAuditLogOptions Options { get; } + + /// + /// The id which the target applies to + /// + ulong TargetId { get; } + + /// + /// The user responsible for causing the changes + /// + IUser User { get; } + + /// + /// The reason behind the change. May be if no reason was provided. + /// + string Reason { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs new file mode 100644 index 000000000..1ca12255e --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents options which may be applied to an + /// + public interface IAuditLogOptions + { + + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 506cbd3e4..4c7ade43b 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -114,5 +114,8 @@ namespace Discord Task DownloadUsersAsync(); /// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); + + Task> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, + CacheMode cacheMode = CacheMode.AllowDownload, RequestOptions options = null); } } \ No newline at end of file diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs new file mode 100644 index 000000000..f2147cb0e --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLog + { + //TODO: figure out how this works + //[JsonProperty("webhooks")] + //public object Webhooks { get; set; } + + [JsonProperty("users")] + public User[] Users { get; set; } + + [JsonProperty("audit_log_entries")] + public AuditLogEntry[] Entries { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogChange.cs b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs new file mode 100644 index 000000000..44e585021 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Discord.API +{ + internal class AuditLogChange + { + [JsonProperty("key")] + public string ChangedProperty { get; set; } + + [JsonProperty("new_value")] + public JToken NewValue { get; set; } + + [JsonProperty("old_value")] + public JToken OldValue { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs new file mode 100644 index 000000000..ecdb12650 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLogEntry + { + [JsonProperty("target_id")] + public ulong TargetId { get; set; } + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("changes")] + public AuditLogChange[] Changes { get; set; } + [JsonProperty("options")] + public AuditLogOptions Options { get; set; } + + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("action_type")] + public ActionType Action { get; set; } + + [JsonProperty("reason")] + public string Reason { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs new file mode 100644 index 000000000..bbda95405 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + // TODO: Complete this with all possible values for options + internal class AuditLogOptions + { + [JsonProperty("count")] + public int Count { get; set; } + + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs new file mode 100644 index 000000000..fc3e0d9f3 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs @@ -0,0 +1,8 @@ +namespace Discord.API.Rest +{ + class GetAuditLogsParams + { + public Optional Limit { get; set; } + public Optional AfterEntryId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a632e5d42..c7b1b23c5 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1059,6 +1059,21 @@ namespace Discord.API return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); } + //Audit logs + public async Task GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); + + int limit = args.Limit.GetValueOrDefault(int.MaxValue); + ulong afterEntryId = args.AfterEntryId.GetValueOrDefault(0); + + var ids = new BucketIds(guildId: guildId); + Expression> endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&after={afterEntryId}"; + return await SendAsync("GET", endpoint, ids, options: options).ConfigureAwait(false); + } + //Users public async Task GetUserAsync(ulong userId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs new file mode 100644 index 000000000..7e349e110 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; +using OptionModel = Discord.API.AuditLogOptions; + +namespace Discord.Rest +{ + internal static class AuditLogHelper + { + public static IAuditLogChange CreateChange(BaseDiscordClient discord, EntryModel entryModel, ChangeModel model) + { + switch (entryModel.Action) + { + case ActionType.MemberRoleUpdated: + return new MemberRoleAuditLogChange(discord, model); + default: + throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entryModel.Action} audit log action."); + } + } + + public static IAuditLogOptions CreateOptions(BaseDiscordClient discord, EntryModel entryModel, OptionModel model) + { + switch (entryModel.Action) + { + case ActionType.MessageDeleted: + return new MessageDeleteAuditLogOptions(discord, model); + default: + throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entryModel.Action} audit log action."); + } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs new file mode 100644 index 000000000..c5f167d6f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using Model = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class MemberRoleAuditLogChange : IAuditLogChange + { + internal MemberRoleAuditLogChange(BaseDiscordClient discord, Model model) + { + RoleAdded = model.ChangedProperty == "$add"; + RoleId = model.NewValue.Value("id"); + } + + public bool RoleAdded { get; set; } + //TODO: convert to IRole + public ulong RoleId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs new file mode 100644 index 000000000..3d53ed790 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLogOptions; + +namespace Discord.Rest +{ + public class MessageDeleteAuditLogOptions : IAuditLogOptions + { + internal MessageDeleteAuditLogOptions(BaseDiscordClient discord, Model model) + { + MessageCount = model.Count; + SourceChannelId = model.ChannelId; + } + + //TODO: turn this into an IChannel + public ulong SourceChannelId { get; } + public int MessageCount { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs new file mode 100644 index 000000000..6efe4e88e --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +using FullModel = Discord.API.AuditLog; +using Model = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RestAuditLogEntry : RestEntity, IAuditLogEntry + { + internal RestAuditLogEntry(BaseDiscordClient discord, Model model, API.User user) + : base(discord, model.Id) + { + Action = model.Action; + if (model.Changes != null) + Changes = model.Changes + .Select(x => AuditLogHelper.CreateChange(discord, model, x)) + .ToReadOnlyCollection(() => model.Changes.Length); + else + Changes = ImmutableArray.Create(); + + if (model.Options != null) + Options = AuditLogHelper.CreateOptions(discord, model, model.Options); + + TargetId = model.TargetId; + User = RestUser.Create(discord, user); + + Reason = model.Reason; + } + + internal static RestAuditLogEntry Create(BaseDiscordClient discord, FullModel fullLog, Model model) + { + var user = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + + return new RestAuditLogEntry(discord, model, user); + } + + public ActionType Action { get; } + public IReadOnlyCollection Changes { get; } + public IAuditLogOptions Options { get; } + public ulong TargetId { get; } + public IUser User { get; } + public string Reason { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 98303cea6..e568bf402 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -247,5 +247,34 @@ namespace Discord.Rest model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false); return model.Pruned; } + + public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client, + ulong? from, int? limit, RequestOptions options) + { + return new PagedAsyncEnumerable( + DiscordConfig.MaxAuditLogEntriesPerBatch, + async (info, ct) => + { + var args = new GetAuditLogsParams + { + Limit = info.PageSize + }; + if (info.Position != null) + args.AfterEntryId = info.Position.Value; + var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options); + return model.Entries.Select((x) => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray(); + }, + nextPage: (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch) + return false; + info.Position = lastPage.Max(x => x.Id); + return true; + }, + start: from, + count: limit + ); + + } } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 8b5598ffe..23f6353bf 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -239,6 +239,10 @@ namespace Discord.Rest public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); + //Audit logs + public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); + public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; @@ -361,5 +365,13 @@ namespace Discord.Rest return ImmutableArray.Create(); } Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } + + async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).Flatten().ConfigureAwait(false)).ToImmutableArray(); + else + return ImmutableArray.Create(); + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 5358605c8..72beac217 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -423,6 +423,10 @@ namespace Discord.WebSocket _downloaderPromise.TrySetResultAsync(true); } + //Audit logs + public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); + //Voice States internal async Task AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model) { @@ -659,5 +663,13 @@ namespace Discord.WebSocket Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Owner); Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } + + async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).Flatten().ConfigureAwait(false)).ToImmutableArray(); + else + return ImmutableArray.Create(); + } } } From 794816dcee8cb614905ce0100d0c8a2972cd0e6e Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Wed, 21 Jun 2017 17:09:53 +0100 Subject: [PATCH 2/4] Fix log pagination, implement some change types I still need to implement a bunch of stuff: - Move IAuditLogOptions into IAuditLogChanges (I initially misunderstood the purpose of options, but now I know) - Implement the rest of the change types - Debug and get API feedback --- ...IAuditLogChange.cs => IAuditLogChanges.cs} | 6 +- .../Entities/AuditLogs/IAuditLogEntry.cs | 6 +- .../API/Common/AuditLogEntry.cs | 2 +- .../API/Rest/GetAuditLogsParams.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 9 ++- .../Entities/AuditLogs/AuditLogHelper.cs | 67 ++++++++++++++++--- .../AuditLogs/Changes/ChannelDeleteChanges.cs | 40 +++++++++++ .../AuditLogs/Changes/EmoteCreateChanges.cs | 29 ++++++++ .../AuditLogs/Changes/EmoteDeleteChanges.cs | 23 +++++++ .../AuditLogs/Changes/EmoteUpdateChanges.cs | 33 +++++++++ .../AuditLogs/Changes/InviteCreateChanges.cs | 54 +++++++++++++++ .../Changes/MemberRoleAuditLogChange.cs | 19 ------ .../AuditLogs/Changes/MemberRoleChanges.cs | 51 ++++++++++++++ .../AuditLogs/Changes/MemberUpdateChanges.cs | 33 +++++++++ .../Changes/OverwriteDeleteChanges.cs | 45 +++++++++++++ .../Entities/AuditLogs/RestAuditLogEntry.cs | 39 ++++++----- .../Entities/Guilds/GuildHelper.cs | 4 +- 17 files changed, 401 insertions(+), 61 deletions(-) rename src/Discord.Net.Core/Entities/AuditLogs/{IAuditLogChange.cs => IAuditLogChanges.cs} (83%) create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs similarity index 83% rename from src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs rename to src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs index 0c36b53b2..2be25a13e 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs @@ -9,8 +9,6 @@ namespace Discord /// /// Represents changes which may occur within a /// - public interface IAuditLogChange - { - - } + public interface IAuditLogChanges + { } } diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs index 0569f5fff..b3cf6d4af 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -19,7 +19,7 @@ namespace Discord /// /// The changes which occured within this entry. May be empty if no changes occured. /// - IReadOnlyCollection Changes { get; } + IAuditLogChanges Changes { get; } /// /// Any options which apply to this entry. If no options were provided, this may be . @@ -27,9 +27,9 @@ namespace Discord IAuditLogOptions Options { get; } /// - /// The id which the target applies to + /// The id which the target applies to, or null if it does not apply to anything specific. /// - ulong TargetId { get; } + ulong? TargetId { get; } /// /// The user responsible for causing the changes diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs index ecdb12650..80d9a9e97 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -5,7 +5,7 @@ namespace Discord.API internal class AuditLogEntry { [JsonProperty("target_id")] - public ulong TargetId { get; set; } + public ulong? TargetId { get; set; } [JsonProperty("user_id")] public ulong UserId { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs index fc3e0d9f3..ceabccbc8 100644 --- a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs @@ -3,6 +3,6 @@ class GetAuditLogsParams { public Optional Limit { get; set; } - public Optional AfterEntryId { get; set; } + public Optional BeforeEntryId { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index c7b1b23c5..6ab6bd334 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1067,10 +1067,15 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); int limit = args.Limit.GetValueOrDefault(int.MaxValue); - ulong afterEntryId = args.AfterEntryId.GetValueOrDefault(0); var ids = new BucketIds(guildId: guildId); - Expression> endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&after={afterEntryId}"; + Expression> endpoint; + + if (args.BeforeEntryId.IsSpecified) + endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&before={args.BeforeEntryId.Value}"; + else + endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}"; + return await SendAsync("GET", endpoint, ids, options: options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 7e349e110..01855267a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; using ChangeModel = Discord.API.AuditLogChange; using OptionModel = Discord.API.AuditLogOptions; @@ -12,25 +10,76 @@ namespace Discord.Rest { internal static class AuditLogHelper { - public static IAuditLogChange CreateChange(BaseDiscordClient discord, EntryModel entryModel, ChangeModel model) + public static IAuditLogChanges CreateChange(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel[] models) { - switch (entryModel.Action) + switch (entry.Action) { + case ActionType.GuildUpdated: + break; + case ActionType.ChannelCreated: + break; + case ActionType.ChannelUpdated: + break; + case ActionType.ChannelDeleted: + return ChannelDeleteChanges.Create(discord, models); + case ActionType.OverwriteCreated: + break; + case ActionType.OverwriteUpdated: + break; + case ActionType.OverwriteDeleted: + return OverwriteDeleteChanges.Create(discord, log, entry, models); + case ActionType.Prune: + break; + case ActionType.Ban: + break; + case ActionType.Unban: + break; + case ActionType.MemberUpdated: + return MemberUpdateChanges.Create(discord, log, entry, models.FirstOrDefault()); case ActionType.MemberRoleUpdated: - return new MemberRoleAuditLogChange(discord, model); + return MemberRoleChanges.Create(discord, log, entry, models); + case ActionType.RoleCreated: + break; + case ActionType.RoleUpdated: + break; + case ActionType.RoleDeleted: + break; + case ActionType.InviteCreated: + return InviteCreateChanges.Create(discord, log, models); + case ActionType.InviteUpdated: + break; + case ActionType.InviteDeleted: + break; + case ActionType.WebhookCreated: + break; + case ActionType.WebhookUpdated: + break; + case ActionType.WebhookDeleted: + break; + case ActionType.EmojiCreated: + return EmoteCreateChanges.Create(discord, entry, models.FirstOrDefault()); + case ActionType.EmojiUpdated: + return EmoteUpdateChanges.Create(discord, entry, models.FirstOrDefault()); + case ActionType.EmojiDeleted: + return EmoteDeleteChanges.Create(discord, entry, models.FirstOrDefault()); + case ActionType.MessageDeleted: + case ActionType.Kick: default: - throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entryModel.Action} audit log action."); + return null; } + return null; + //throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entry.Action} audit log changeset."); } - public static IAuditLogOptions CreateOptions(BaseDiscordClient discord, EntryModel entryModel, OptionModel model) + public static IAuditLogOptions CreateOptions(BaseDiscordClient discord, Model fullModel, EntryModel entryModel, OptionModel model) { switch (entryModel.Action) { case ActionType.MessageDeleted: return new MessageDeleteAuditLogOptions(discord, model); default: - throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entryModel.Action} audit log action."); + return null; + //throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entryModel.Action} audit log action."); } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs new file mode 100644 index 000000000..ad2a2cd67 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class ChannelDeleteChanges : IAuditLogChanges + { + private ChannelDeleteChanges(string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelDeleteChanges Create(BaseDiscordClient discord, ChangeModel[] models) + { + //Use FirstOrDefault here in case the order ever changes + var overwritesModel = models.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = models.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = models.FirstOrDefault(x => x.ChangedProperty == "name"); + + var overwrites = overwritesModel.OldValue.ToObject() + .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) + .ToList(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + + return new ChannelDeleteChanges(name, type, overwrites.ToReadOnlyCollection()); + } + + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs new file mode 100644 index 000000000..e6e7e3b64 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class EmoteCreateChanges : IAuditLogChanges + { + private EmoteCreateChanges(Emote emote) + { + Emote = emote; + } + + internal static EmoteCreateChanges Create(BaseDiscordClient discord, EntryModel entry, ChangeModel model) + { + var emoteName = model.NewValue.ToObject(); + var emote = new Emote(entry.TargetId.Value, emoteName); + + return new EmoteCreateChanges(emote); + } + + public Emote Emote { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs new file mode 100644 index 000000000..7e876eff7 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs @@ -0,0 +1,23 @@ +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class EmoteDeleteChanges : IAuditLogChanges + { + private EmoteDeleteChanges(Emote emote) + { + Emote = emote; + } + + internal static EmoteDeleteChanges Create(BaseDiscordClient discord, EntryModel entry, ChangeModel model) + { + var emoteName = model.OldValue.ToObject(); + var emote = new Emote(entry.TargetId.Value, emoteName); + + return new EmoteDeleteChanges(emote); + } + + public Emote Emote { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs new file mode 100644 index 000000000..73b174f8b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class EmoteUpdateChanges : IAuditLogChanges + { + private EmoteUpdateChanges(Emote emote, string oldName) + { + Emote = emote; + PreviousName = oldName; + } + + internal static EmoteUpdateChanges Create(BaseDiscordClient discord, EntryModel entry, ChangeModel model) + { + var emoteName = model.NewValue.ToObject(); + var emote = new Emote(entry.TargetId.Value, emoteName); + + var oldName = model.OldValue.ToObject(); + + return new EmoteUpdateChanges(emote, oldName); + } + + public Emote Emote { get; } + public string PreviousName { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs new file mode 100644 index 000000000..9d0e4419d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs @@ -0,0 +1,54 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class InviteCreateChanges : IAuditLogChanges + { + private InviteCreateChanges(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + Creator = inviter; + ChannelId = channelId; + Uses = uses; + MaxUses = maxUses; + } + + internal static InviteCreateChanges Create(BaseDiscordClient discord, Model log, ChangeModel[] models) + { + //Again, FirstOrDefault to protect against ordering changes + var maxAgeModel = models.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = models.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = models.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = models.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = models.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = models.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = models.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + var maxAge = maxAgeModel.NewValue.ToObject(); + var code = codeModel.NewValue.ToObject(); + var temporary = temporaryModel.NewValue.ToObject(); + var inviterId = inviterIdModel.NewValue.ToObject(); + var channelId = channelIdModel.NewValue.ToObject(); + var uses = usesModel.NewValue.ToObject(); + var maxUses = maxUsesModel.NewValue.ToObject(); + + var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); + var inviter = RestUser.Create(discord, inviterInfo); + + return new InviteCreateChanges(maxAge, code, temporary, inviter, channelId, uses, maxUses); + } + + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } //TODO: IChannel-ify + public int Uses { get; } + public int MaxUses { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs deleted file mode 100644 index c5f167d6f..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Newtonsoft.Json; - -using Model = Discord.API.AuditLogChange; - -namespace Discord.Rest -{ - public class MemberRoleAuditLogChange : IAuditLogChange - { - internal MemberRoleAuditLogChange(BaseDiscordClient discord, Model model) - { - RoleAdded = model.ChangedProperty == "$add"; - RoleId = model.NewValue.Value("id"); - } - - public bool RoleAdded { get; set; } - //TODO: convert to IRole - public ulong RoleId { get; set; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs new file mode 100644 index 000000000..9abf5be79 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class MemberRoleChanges : IAuditLogChanges + { + private MemberRoleChanges(IReadOnlyCollection roles, IUser target) + { + Roles = roles; + TargetUser = target; + } + + internal static MemberRoleChanges Create(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel[] models) + { + var roleInfos = models.SelectMany(x => x.NewValue.ToObject(), + (model, role) => new { model.ChangedProperty, Role = role }) + .Select(x => new RoleInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) + .ToList(); + + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + var user = RestUser.Create(discord, userInfo); + + return new MemberRoleChanges(roleInfos.ToReadOnlyCollection(), user); + } + + public IReadOnlyCollection Roles { get; } + public IUser TargetUser { get; } + + public struct RoleInfo + { + internal RoleInfo(string name, ulong roleId, bool added) + { + Name = name; + RoleId = roleId; + Added = added; + } + + string Name { get; } + ulong RoleId { get; } + bool Added { get; } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs new file mode 100644 index 000000000..4097a6d9b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs @@ -0,0 +1,33 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class MemberUpdateChanges : IAuditLogChanges + { + private MemberUpdateChanges(IUser user, string newNick, string oldNick) + { + User = user; + NewNick = newNick; + OldNick = oldNick; + } + + internal static MemberUpdateChanges Create(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel model) + { + var newNick = model.NewValue?.ToObject(); + var oldNick = model.OldValue?.ToObject(); + + var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + var user = RestUser.Create(discord, targetInfo); + + return new MemberUpdateChanges(user, newNick, oldNick); + } + + public IUser User { get; } + public string NewNick { get; } + public string OldNick { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs new file mode 100644 index 000000000..7fe4ca475 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; +using OptionModel = Discord.API.AuditLogOptions; + +namespace Discord.Rest +{ + public class OverwriteDeleteChanges : IAuditLogChanges + { + private OverwriteDeleteChanges(Overwrite deletedOverwrite) + { + Overwrite = deletedOverwrite; + } + + internal static OverwriteDeleteChanges Create(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel[] models) + { + var denyModel = models.FirstOrDefault(x => x.ChangedProperty == "deny"); + var typeModel = models.FirstOrDefault(x => x.ChangedProperty == "type"); + var idModel = models.FirstOrDefault(x => x.ChangedProperty == "id"); + var allowModel = models.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var deny = denyModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(); //'role' or 'member', can't use PermissionsTarget :( + var id = idModel.OldValue.ToObject(); + var allow = allowModel.OldValue.ToObject(); + + PermissionTarget target; + + if (type == "member") + target = PermissionTarget.User; + else + target = PermissionTarget.Role; + + return new OverwriteDeleteChanges(new Overwrite(id, target, new OverwritePermissions(allow, deny))); + } + + public Overwrite Overwrite { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index 6efe4e88e..6f4de2e82 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -1,47 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; +using System.Linq; -using FullModel = Discord.API.AuditLog; -using Model = Discord.API.AuditLogEntry; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { public class RestAuditLogEntry : RestEntity, IAuditLogEntry { - internal RestAuditLogEntry(BaseDiscordClient discord, Model model, API.User user) + private RestAuditLogEntry(BaseDiscordClient discord, Model fullLog, EntryModel model, IUser user) : base(discord, model.Id) { Action = model.Action; - if (model.Changes != null) - Changes = model.Changes - .Select(x => AuditLogHelper.CreateChange(discord, model, x)) - .ToReadOnlyCollection(() => model.Changes.Length); - else - Changes = ImmutableArray.Create(); + if (model.Changes != null) + Changes = AuditLogHelper.CreateChange(discord, fullLog, model, model.Changes); if (model.Options != null) - Options = AuditLogHelper.CreateOptions(discord, model, model.Options); + Options = AuditLogHelper.CreateOptions(discord, fullLog, model, model.Options); TargetId = model.TargetId; - User = RestUser.Create(discord, user); + User = user; Reason = model.Reason; } - internal static RestAuditLogEntry Create(BaseDiscordClient discord, FullModel fullLog, Model model) + internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) { - var user = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + IUser user = null; + if (userInfo != null) + user = RestUser.Create(discord, userInfo); - return new RestAuditLogEntry(discord, model, user); + return new RestAuditLogEntry(discord, fullLog, model, user); } public ActionType Action { get; } - public IReadOnlyCollection Changes { get; } + + public IAuditLogChanges Changes { get; } public IAuditLogOptions Options { get; } - public ulong TargetId { get; } + + public ulong? TargetId { get; } //TODO: if we're exposing this on the changes instead, do we need this? public IUser User { get; } + public string Reason { get; } } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index e568bf402..de7512fa3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -260,7 +260,7 @@ namespace Discord.Rest Limit = info.PageSize }; if (info.Position != null) - args.AfterEntryId = info.Position.Value; + args.BeforeEntryId = info.Position.Value; var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options); return model.Entries.Select((x) => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray(); }, @@ -268,7 +268,7 @@ namespace Discord.Rest { if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch) return false; - info.Position = lastPage.Max(x => x.Id); + info.Position = lastPage.Min(x => x.Id); return true; }, start: from, From 94ea9c3189508d89b7036c3a1597a95b8f7b044b Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Thu, 22 Jun 2017 16:45:12 +0100 Subject: [PATCH 3/4] Implement most audit log action types Missing at the moment: - GuildUpdateAuditLogData - UnbanAuditLogData - InviteUpdateAuditLogData - InviteDeleteAuditLogData - WebhookCreateAuditLogData - WebhookUpdateAuditLogData - WebhookDeleteAuditLogData --- .../{IAuditLogChanges.cs => IAuditLogData.cs} | 4 +- .../Entities/AuditLogs/IAuditLogEntry.cs | 14 +--- .../Entities/AuditLogs/IAuditLogOptions.cs | 16 ---- .../API/Common/AuditLogOptions.cs | 22 +++++- .../Entities/AuditLogs/AuditLogHelper.cs | 79 +++++++++---------- .../AuditLogs/Changes/ChannelDeleteChanges.cs | 40 ---------- .../AuditLogs/Changes/EmoteCreateChanges.cs | 29 ------- .../AuditLogs/Changes/EmoteDeleteChanges.cs | 23 ------ .../AuditLogs/Changes/EmoteUpdateChanges.cs | 33 -------- .../AuditLogs/DataTypes/BanAuditLogData.cs | 23 ++++++ .../DataTypes/ChannelCreateAuditLogData.cs | 56 +++++++++++++ .../DataTypes/ChannelDeleteAuditLogData.cs | 45 +++++++++++ .../DataTypes/ChannelUpdateAuditLogData.cs | 67 ++++++++++++++++ .../DataTypes/EmoteCreateAuditLogData.cs | 31 ++++++++ .../DataTypes/EmoteDeleteAuditLogData.cs | 27 +++++++ .../DataTypes/EmoteUpdateAuditLogData.cs | 31 ++++++++ .../DataTypes/GuildUpdateAuditLogData.cs | 58 ++++++++++++++ .../InviteCreateAuditLogData.cs} | 27 ++++--- .../AuditLogs/DataTypes/KickAuditLogData.cs | 23 ++++++ .../MemberRoleAuditLogData.cs} | 15 ++-- .../MemberUpdateAuditLogData.cs} | 14 ++-- .../DataTypes/MessageDeleteAuditLogData.cs | 22 ++++++ .../DataTypes/OverwriteCreateAuditLogData.cs | 41 ++++++++++ .../OverwriteDeleteAuditLogData.cs} | 18 +++-- .../DataTypes/OverwriteUpdateAuditLogData.cs | 50 ++++++++++++ .../AuditLogs/DataTypes/PruneAuditLogData.cs | 22 ++++++ .../DataTypes/RoleCreateAuditLogData.cs | 52 ++++++++++++ .../DataTypes/RoleDeleteAuditLogData.cs | 52 ++++++++++++ .../DataTypes/RoleUpdateAuditLogData.cs | 65 +++++++++++++++ .../Options/MessageDeleteAuditLogOptions.cs | 23 ------ .../Entities/AuditLogs/RestAuditLogEntry.cs | 16 ++-- 31 files changed, 769 insertions(+), 269 deletions(-) rename src/Discord.Net.Core/Entities/AuditLogs/{IAuditLogChanges.cs => IAuditLogData.cs} (61%) delete mode 100644 src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs rename src/Discord.Net.Rest/Entities/AuditLogs/{Changes/InviteCreateChanges.cs => DataTypes/InviteCreateAuditLogData.cs} (51%) create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs rename src/Discord.Net.Rest/Entities/AuditLogs/{Changes/MemberRoleChanges.cs => DataTypes/MemberRoleAuditLogData.cs} (67%) rename src/Discord.Net.Rest/Entities/AuditLogs/{Changes/MemberUpdateChanges.cs => DataTypes/MemberUpdateAuditLogData.cs} (50%) create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs rename src/Discord.Net.Rest/Entities/AuditLogs/{Changes/OverwriteDeleteChanges.cs => DataTypes/OverwriteDeleteAuditLogData.cs} (55%) create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs similarity index 61% rename from src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs rename to src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs index 2be25a13e..47aaffb26 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs @@ -7,8 +7,8 @@ using System.Threading.Tasks; namespace Discord { /// - /// Represents changes which may occur within a + /// Represents data applied to an /// - public interface IAuditLogChanges + public interface IAuditLogData { } } diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs index b3cf6d4af..b85730a1d 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -17,19 +17,9 @@ namespace Discord ActionType Action { get; } /// - /// The changes which occured within this entry. May be empty if no changes occured. + /// The data for this entry. May be if no data was available. /// - IAuditLogChanges Changes { get; } - - /// - /// Any options which apply to this entry. If no options were provided, this may be . - /// - IAuditLogOptions Options { get; } - - /// - /// The id which the target applies to, or null if it does not apply to anything specific. - /// - ulong? TargetId { get; } + IAuditLogData Data { get; } /// /// The user responsible for causing the changes diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs deleted file mode 100644 index 1ca12255e..000000000 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord -{ - /// - /// Represents options which may be applied to an - /// - public interface IAuditLogOptions - { - - } -} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs index bbda95405..c5b229337 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -2,13 +2,27 @@ namespace Discord.API { - // TODO: Complete this with all possible values for options + //TODO: Complete this with all possible values for options internal class AuditLogOptions { + //Message delete [JsonProperty("count")] - public int Count { get; set; } - + public int? MessageDeleteCount { get; set; } //TODO: what type of int? (returned as string) [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } + public ulong? MessageDeleteChannelId { get; set; } + + //Prune + [JsonProperty("delete_member_days")] + public int? PruneDeleteMemberDays { get; set; } //TODO: what type of int? (returned as string) + [JsonProperty("members_removed")] + public int? PruneMembersRemoved { get; set; } //TODO: what type of int? (returned as string) + + //Overwrite Update + [JsonProperty("role_name")] + public string OverwriteRoleName { get; set; } + [JsonProperty("type")] + public string OverwriteType { get; set; } + [JsonProperty("id")] + public ulong? OverwriteTargetId { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 01855267a..71c806a0d 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -3,84 +3,79 @@ using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; -using OptionModel = Discord.API.AuditLogOptions; namespace Discord.Rest { internal static class AuditLogHelper { - public static IAuditLogChanges CreateChange(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel[] models) + public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) { switch (entry.Action) { - case ActionType.GuildUpdated: - break; - case ActionType.ChannelCreated: - break; + case ActionType.GuildUpdated: //1 + return GuildUpdateAuditLogData.Create(discord, log, entry); + + case ActionType.ChannelCreated: //10 + return ChannelCreateAuditLogData.Create(discord, log, entry); case ActionType.ChannelUpdated: - break; + return ChannelUpdateAuditLogData.Create(discord, log, entry); case ActionType.ChannelDeleted: - return ChannelDeleteChanges.Create(discord, models); + return ChannelDeleteAuditLogData.Create(discord, log, entry); case ActionType.OverwriteCreated: - break; + return OverwriteCreateAuditLogData.Create(discord, log, entry); case ActionType.OverwriteUpdated: - break; + return OverwriteUpdateAuditLogData.Create(discord, log, entry); case ActionType.OverwriteDeleted: - return OverwriteDeleteChanges.Create(discord, log, entry, models); + return OverwriteDeleteAuditLogData.Create(discord, log, entry); + + case ActionType.Kick: //20 + return KickAuditLogData.Create(discord, log, entry); case ActionType.Prune: - break; + return PruneAuditLogData.Create(discord, log, entry); case ActionType.Ban: - break; + return BanAuditLogData.Create(discord, log, entry); case ActionType.Unban: break; case ActionType.MemberUpdated: - return MemberUpdateChanges.Create(discord, log, entry, models.FirstOrDefault()); + return MemberUpdateAuditLogData.Create(discord, log, entry); case ActionType.MemberRoleUpdated: - return MemberRoleChanges.Create(discord, log, entry, models); - case ActionType.RoleCreated: - break; + return MemberRoleAuditLogData.Create(discord, log, entry); + + case ActionType.RoleCreated: //30 + return RoleCreateAuditLogData.Create(discord, log, entry); case ActionType.RoleUpdated: - break; + return RoleUpdateAuditLogData.Create(discord, log, entry); case ActionType.RoleDeleted: - break; - case ActionType.InviteCreated: - return InviteCreateChanges.Create(discord, log, models); + return RoleDeleteAuditLogData.Create(discord, log, entry); + + case ActionType.InviteCreated: //40 + return InviteCreateAuditLogData.Create(discord, log, entry); case ActionType.InviteUpdated: break; case ActionType.InviteDeleted: break; - case ActionType.WebhookCreated: + + case ActionType.WebhookCreated: //50 break; case ActionType.WebhookUpdated: break; case ActionType.WebhookDeleted: break; - case ActionType.EmojiCreated: - return EmoteCreateChanges.Create(discord, entry, models.FirstOrDefault()); + + case ActionType.EmojiCreated: //60 + return EmoteCreateAuditLogData.Create(discord, log, entry); case ActionType.EmojiUpdated: - return EmoteUpdateChanges.Create(discord, entry, models.FirstOrDefault()); + return EmoteUpdateAuditLogData.Create(discord, log, entry); case ActionType.EmojiDeleted: - return EmoteDeleteChanges.Create(discord, entry, models.FirstOrDefault()); - case ActionType.MessageDeleted: - case ActionType.Kick: - default: - return null; - } - return null; - //throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entry.Action} audit log changeset."); - } + return EmoteDeleteAuditLogData.Create(discord, log, entry); - public static IAuditLogOptions CreateOptions(BaseDiscordClient discord, Model fullModel, EntryModel entryModel, OptionModel model) - { - switch (entryModel.Action) - { - case ActionType.MessageDeleted: - return new MessageDeleteAuditLogOptions(discord, model); + case ActionType.MessageDeleted: //72 + return MessageDeleteAuditLogData.Create(discord, log, entry); default: return null; - //throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entryModel.Action} audit log action."); } + return null; + //throw new NotImplementedException($"{nameof(AuditLogHelper)} does not implement the {entry.Action} audit log event."); } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs deleted file mode 100644 index ad2a2cd67..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using ChangeModel = Discord.API.AuditLogChange; - -namespace Discord.Rest -{ - public class ChannelDeleteChanges : IAuditLogChanges - { - private ChannelDeleteChanges(string name, ChannelType type, IReadOnlyCollection overwrites) - { - ChannelName = name; - ChannelType = type; - Overwrites = overwrites; - } - - internal static ChannelDeleteChanges Create(BaseDiscordClient discord, ChangeModel[] models) - { - //Use FirstOrDefault here in case the order ever changes - var overwritesModel = models.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); - var typeModel = models.FirstOrDefault(x => x.ChangedProperty == "type"); - var nameModel = models.FirstOrDefault(x => x.ChangedProperty == "name"); - - var overwrites = overwritesModel.OldValue.ToObject() - .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) - .ToList(); - var type = typeModel.OldValue.ToObject(); - var name = nameModel.OldValue.ToObject(); - - return new ChannelDeleteChanges(name, type, overwrites.ToReadOnlyCollection()); - } - - public string ChannelName { get; } - public ChannelType ChannelType { get; } - public IReadOnlyCollection Overwrites { get; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs deleted file mode 100644 index e6e7e3b64..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; - -namespace Discord.Rest -{ - public class EmoteCreateChanges : IAuditLogChanges - { - private EmoteCreateChanges(Emote emote) - { - Emote = emote; - } - - internal static EmoteCreateChanges Create(BaseDiscordClient discord, EntryModel entry, ChangeModel model) - { - var emoteName = model.NewValue.ToObject(); - var emote = new Emote(entry.TargetId.Value, emoteName); - - return new EmoteCreateChanges(emote); - } - - public Emote Emote { get; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs deleted file mode 100644 index 7e876eff7..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs +++ /dev/null @@ -1,23 +0,0 @@ -using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; - -namespace Discord.Rest -{ - public class EmoteDeleteChanges : IAuditLogChanges - { - private EmoteDeleteChanges(Emote emote) - { - Emote = emote; - } - - internal static EmoteDeleteChanges Create(BaseDiscordClient discord, EntryModel entry, ChangeModel model) - { - var emoteName = model.OldValue.ToObject(); - var emote = new Emote(entry.TargetId.Value, emoteName); - - return new EmoteDeleteChanges(emote); - } - - public Emote Emote { get; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs deleted file mode 100644 index 73b174f8b..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; - -namespace Discord.Rest -{ - public class EmoteUpdateChanges : IAuditLogChanges - { - private EmoteUpdateChanges(Emote emote, string oldName) - { - Emote = emote; - PreviousName = oldName; - } - - internal static EmoteUpdateChanges Create(BaseDiscordClient discord, EntryModel entry, ChangeModel model) - { - var emoteName = model.NewValue.ToObject(); - var emote = new Emote(entry.TargetId.Value, emoteName); - - var oldName = model.OldValue.ToObject(); - - return new EmoteUpdateChanges(emote, oldName); - } - - public Emote Emote { get; } - public string PreviousName { get; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs new file mode 100644 index 000000000..4b9d5875f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class BanAuditLogData : IAuditLogData + { + private BanAuditLogData(IUser user) + { + Target = user; + } + + internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new BanAuditLogData(RestUser.Create(discord, userInfo)); + } + + public IUser Target { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs new file mode 100644 index 000000000..e0448a502 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelCreateAuditLogData : IAuditLogData + { + private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelId = id; + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + var overwrites = new List(); + + var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var type = typeModel.NewValue.ToObject(); + var name = nameModel.NewValue.ToObject(); + + foreach (var overwrite in overwritesModel.NewValue) + { + var deny = overwrite.Value("deny"); + var _type = overwrite.Value("type"); + var id = overwrite.Value("id"); + var allow = overwrite.Value("allow"); + + PermissionTarget permType; + if (_type == "member") + permType = PermissionTarget.User; + else + permType = PermissionTarget.Role; + + overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); + } + + return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection()); + } + + public ulong ChannelId { get; } //TODO: IChannel + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs new file mode 100644 index 000000000..b29205714 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelDeleteAuditLogData : IAuditLogData + { + private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelId = id; + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var overwrites = overwritesModel.OldValue.ToObject() + .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) + .ToList(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + var id = entry.TargetId.Value; //NOTE: assuming this is the channel id here + + return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); + } + + public ulong ChannelId { get; } //TODO: IChannel + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs new file mode 100644 index 000000000..ca7a9ce58 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -0,0 +1,67 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelUpdateAuditLogData : IAuditLogData + { + private ChannelUpdateAuditLogData(GuildChannelProperties before, GuildChannelProperties after) + { + Before = before; + After = after; + } + + internal static ChannelUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var topicModel = changes.FirstOrDefault(x => x.ChangedProperty == "topic"); + var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); + var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit"); + + if (topicModel != null) //If topic is supplied, we must be a text channel + { + var before = new TextChannelProperties + { + Name = nameModel?.OldValue?.ToObject(), + Topic = topicModel.OldValue?.ToObject() + }; + var after = new TextChannelProperties + { + Name = nameModel?.NewValue?.ToObject(), + Topic = topicModel.NewValue?.ToObject() + }; + + return new ChannelUpdateAuditLogData(before, after); + } + else //By process of elimination, we must be a voice channel + { + var beforeBitrate = bitrateModel?.OldValue?.ToObject(); + var afterBitrate = bitrateModel?.NewValue?.ToObject(); + var beforeUserLimit = userLimitModel?.OldValue?.ToObject(); + var afterUserLimit = userLimitModel?.NewValue?.ToObject(); + + var before = new VoiceChannelProperties + { + Name = nameModel?.OldValue?.ToObject(), + Bitrate = beforeBitrate ?? Optional.Unspecified, + UserLimit = beforeUserLimit + }; + var after = new VoiceChannelProperties + { + Name = nameModel?.NewValue?.ToObject(), + Bitrate = afterBitrate ?? Optional.Unspecified, + UserLimit = afterUserLimit + }; + + return new ChannelUpdateAuditLogData(before, after); + } + } + + public GuildChannelProperties Before { get; set; } + public GuildChannelProperties After { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs new file mode 100644 index 000000000..30b85d4e4 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteCreateAuditLogData : IAuditLogData + { + private EmoteCreateAuditLogData(Emote emote) + { + Emote = emote; + } + + internal static EmoteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); //TODO: only change? + + var emoteName = change.NewValue?.ToObject(); + var emote = new Emote(entry.TargetId.Value, emoteName); + + return new EmoteCreateAuditLogData(emote); + } + + public Emote Emote { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs new file mode 100644 index 000000000..21f7b4076 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -0,0 +1,27 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteDeleteAuditLogData : IAuditLogData + { + private EmoteDeleteAuditLogData(Emote emote) + { + Emote = emote; + } + + internal static EmoteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); //TODO: only change? + + var emoteName = change.OldValue?.ToObject(); + var emote = new Emote(entry.TargetId.Value, emoteName); + + return new EmoteDeleteAuditLogData(emote); + } + + public Emote Emote { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs new file mode 100644 index 000000000..980c02195 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -0,0 +1,31 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteUpdateAuditLogData : IAuditLogData + { + private EmoteUpdateAuditLogData(Emote emote, string oldName) + { + Emote = emote; + PreviousName = oldName; + } + + internal static EmoteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); //TODO: only change? + + var emoteName = change.NewValue?.ToObject(); + var emote = new Emote(entry.TargetId.Value, emoteName); + + var oldName = change.OldValue?.ToObject(); + + return new EmoteUpdateAuditLogData(emote, oldName); + } + + public Emote Emote { get; } + public string PreviousName { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs new file mode 100644 index 000000000..cd7603f78 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -0,0 +1,58 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class GuildUpdateAuditLogData : IAuditLogData + { + private GuildUpdateAuditLogData() + { } + + internal static GuildUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + int? oldAfkTimeout, newAfkTimeout; + DefaultMessageNotifications? oldDefaultMessageNotifications, newDefaultMessageNotifications; + ulong? oldAfkChannelId, newAfkChannelId; + string oldName, newName; + string oldRegionId, newRegionId; + string oldIconHash, newIconHash; + + foreach (var change in changes) + { + switch (change.ChangedProperty) + { + case "afk_timeout": + oldAfkTimeout = change.OldValue?.ToObject(); + newAfkTimeout = change.NewValue?.ToObject(); + break; + case "default_message_notifications": + oldDefaultMessageNotifications = change.OldValue?.ToObject(); + newDefaultMessageNotifications = change.OldValue?.ToObject(); + break; + case "afk_channel_id": + oldAfkChannelId = change.OldValue?.ToObject(); + newAfkChannelId = change.NewValue?.ToObject(); + break; + case "name": + oldName = change.OldValue?.ToObject(); + newName = change.NewValue?.ToObject(); + break; + case "region": + oldRegionId = change.OldValue?.ToObject(); + newRegionId = change.NewValue?.ToObject(); + break; + case "icon_hash": + oldIconHash = change.OldValue?.ToObject(); + newIconHash = change.NewValue?.ToObject(); + break; + //TODO: owner, verification level + } + } + + //TODO: implement this + return null; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs similarity index 51% rename from src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs rename to src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs index 9d0e4419d..85e2b0b9b 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -1,13 +1,13 @@ using System.Linq; using Model = Discord.API.AuditLog; -using ChangeModel = Discord.API.AuditLogChange; +using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { - public class InviteCreateChanges : IAuditLogChanges + public class InviteCreateAuditLogData : IAuditLogData { - private InviteCreateChanges(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) { MaxAge = maxAge; Code = code; @@ -18,16 +18,17 @@ namespace Discord.Rest MaxUses = maxUses; } - internal static InviteCreateChanges Create(BaseDiscordClient discord, Model log, ChangeModel[] models) + internal static InviteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - //Again, FirstOrDefault to protect against ordering changes - var maxAgeModel = models.FirstOrDefault(x => x.ChangedProperty == "max_age"); - var codeModel = models.FirstOrDefault(x => x.ChangedProperty == "code"); - var temporaryModel = models.FirstOrDefault(x => x.ChangedProperty == "temporary"); - var inviterIdModel = models.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); - var channelIdModel = models.FirstOrDefault(x => x.ChangedProperty == "channel_id"); - var usesModel = models.FirstOrDefault(x => x.ChangedProperty == "uses"); - var maxUsesModel = models.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); var maxAge = maxAgeModel.NewValue.ToObject(); var code = codeModel.NewValue.ToObject(); @@ -40,7 +41,7 @@ namespace Discord.Rest var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); var inviter = RestUser.Create(discord, inviterInfo); - return new InviteCreateChanges(maxAge, code, temporary, inviter, channelId, uses, maxUses); + return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); } public int MaxAge { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs new file mode 100644 index 000000000..41b5526b8 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class KickAuditLogData : IAuditLogData + { + private KickAuditLogData(RestUser user) + { + Target = user; + } + + internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new KickAuditLogData(RestUser.Create(discord, userInfo)); + } + + public IUser Target { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs similarity index 67% rename from src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs rename to src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index 9abf5be79..43d97ecf3 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -1,26 +1,25 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; namespace Discord.Rest { - public class MemberRoleChanges : IAuditLogChanges + public class MemberRoleAuditLogData : IAuditLogData { - private MemberRoleChanges(IReadOnlyCollection roles, IUser target) + private MemberRoleAuditLogData(IReadOnlyCollection roles, IUser target) { Roles = roles; TargetUser = target; } - internal static MemberRoleChanges Create(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel[] models) + internal static MemberRoleAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var roleInfos = models.SelectMany(x => x.NewValue.ToObject(), + var changes = entry.Changes; + + var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), (model, role) => new { model.ChangedProperty, Role = role }) .Select(x => new RoleInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) .ToList(); @@ -28,7 +27,7 @@ namespace Discord.Rest var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); var user = RestUser.Create(discord, userInfo); - return new MemberRoleChanges(roleInfos.ToReadOnlyCollection(), user); + return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user); } public IReadOnlyCollection Roles { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs similarity index 50% rename from src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs rename to src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 4097a6d9b..7bbcdc69a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -6,24 +6,26 @@ using ChangeModel = Discord.API.AuditLogChange; namespace Discord.Rest { - public class MemberUpdateChanges : IAuditLogChanges + public class MemberUpdateAuditLogData : IAuditLogData { - private MemberUpdateChanges(IUser user, string newNick, string oldNick) + private MemberUpdateAuditLogData(IUser user, string newNick, string oldNick) { User = user; NewNick = newNick; OldNick = oldNick; } - internal static MemberUpdateChanges Create(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel model) + internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var newNick = model.NewValue?.ToObject(); - var oldNick = model.OldValue?.ToObject(); + var changes = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "nick"); //TODO: only change? + + var newNick = changes.NewValue?.ToObject(); + var oldNick = changes.OldValue?.ToObject(); var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); var user = RestUser.Create(discord, targetInfo); - return new MemberUpdateChanges(user, newNick, oldNick); + return new MemberUpdateAuditLogData(user, newNick, oldNick); } public IUser User { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs new file mode 100644 index 000000000..73af24209 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class MessageDeleteAuditLogData : IAuditLogData + { + private MessageDeleteAuditLogData(ulong channelId, int count) + { + ChannelId = channelId; + MessageCount = count; + } + + internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value); + } + + public int MessageCount { get; } + public ulong ChannelId { get; } //TODO: IChannel + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs new file mode 100644 index 000000000..6e5658f66 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -0,0 +1,41 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class OverwriteCreateAuditLogData : IAuditLogData + { + private OverwriteCreateAuditLogData(Overwrite overwrite) + { + Overwrite = overwrite; + } + + internal static OverwriteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var deny = denyModel.NewValue.ToObject(); + var allow = allowModel.NewValue.ToObject(); + + var permissions = new OverwritePermissions(allow, deny); + + var id = entry.Options.OverwriteTargetId.Value; + var type = entry.Options.OverwriteType; + + PermissionTarget target; + if (type == "member") + target = PermissionTarget.User; + else + target = PermissionTarget.Role; + + return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); + } + + public Overwrite Overwrite { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs similarity index 55% rename from src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs rename to src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index 7fe4ca475..b1f2195b5 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -11,19 +11,21 @@ using OptionModel = Discord.API.AuditLogOptions; namespace Discord.Rest { - public class OverwriteDeleteChanges : IAuditLogChanges + public class OverwriteDeleteAuditLogData : IAuditLogData { - private OverwriteDeleteChanges(Overwrite deletedOverwrite) + private OverwriteDeleteAuditLogData(Overwrite deletedOverwrite) { Overwrite = deletedOverwrite; } - internal static OverwriteDeleteChanges Create(BaseDiscordClient discord, Model log, EntryModel entry, ChangeModel[] models) + internal static OverwriteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var denyModel = models.FirstOrDefault(x => x.ChangedProperty == "deny"); - var typeModel = models.FirstOrDefault(x => x.ChangedProperty == "type"); - var idModel = models.FirstOrDefault(x => x.ChangedProperty == "id"); - var allowModel = models.FirstOrDefault(x => x.ChangedProperty == "allow"); + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); var deny = denyModel.OldValue.ToObject(); var type = typeModel.OldValue.ToObject(); //'role' or 'member', can't use PermissionsTarget :( @@ -37,7 +39,7 @@ namespace Discord.Rest else target = PermissionTarget.Role; - return new OverwriteDeleteChanges(new Overwrite(id, target, new OverwritePermissions(allow, deny))); + return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); } public Overwrite Overwrite { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs new file mode 100644 index 000000000..923cce28f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -0,0 +1,50 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class OverwriteUpdateAuditLogData : IAuditLogData + { + private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, PermissionTarget targetType) + { + Before = before; + After = after; + OverwriteTargetId = targetId; + OverwriteType = targetType; + } + + internal static OverwriteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var beforeAllow = allowModel?.OldValue?.ToObject(); + var afterAllow = allowModel?.NewValue?.ToObject(); + var beforeDeny = denyModel?.OldValue?.ToObject(); + var afterDeny = denyModel?.OldValue?.ToObject(); + + var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); + var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); + + PermissionTarget target; + if (entry.Options.OverwriteType == "member") + target = PermissionTarget.User; + else + target = PermissionTarget.Role; + + return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); + } + + //TODO: this is kind of janky. Should I leave it, create a custom type, or what? + public OverwritePermissions Before { get; } + public OverwritePermissions After { get; } + + public ulong OverwriteTargetId { get; } + public PermissionTarget OverwriteType { get; } + //TODO: should we also include the role name if it is given? + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs new file mode 100644 index 000000000..0005e304d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class PruneAuditLogData : IAuditLogData + { + private PruneAuditLogData(int pruneDays, int membersRemoved) + { + PruneDays = pruneDays; + MembersRemoved = membersRemoved; + } + + internal static PruneAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + return new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value); + } + + public int PruneDays { get; } + public int MembersRemoved { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs new file mode 100644 index 000000000..30457f38c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -0,0 +1,52 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleCreateAuditLogData : IAuditLogData + { + private RoleCreateAuditLogData(RoleProperties newProps) + { + Properties = newProps; + } + + internal static RoleCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var newProps = new RoleProperties(); + + foreach (var model in changes) + { + switch (model.ChangedProperty) + { + case "color": + if (model.NewValue != null) + newProps.Color = new Color(model.NewValue.ToObject()); + break; + case "mentionable": + if (model.NewValue != null) + newProps.Mentionable = model.NewValue.ToObject(); + break; + case "hoist": + if (model.NewValue != null) + newProps.Hoist = model.NewValue.ToObject(); + break; + case "name": + if (model.NewValue != null) + newProps.Name = model.NewValue.ToObject(); + break; + case "permissions": + if (model.NewValue != null) + newProps.Permissions = new GuildPermissions(model.NewValue.ToObject()); + break; + } + } + + return new RoleCreateAuditLogData(newProps); + } + + //TODO: replace this with something read-only + public RoleProperties Properties { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs new file mode 100644 index 000000000..baeb27cc2 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -0,0 +1,52 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleDeleteAuditLogData : IAuditLogData + { + private RoleDeleteAuditLogData(RoleProperties properties) + { + Properties = properties; + } + + internal static RoleDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var oldProps = new RoleProperties(); + + foreach (var model in changes) + { + switch (model.ChangedProperty) + { + case "color": + if (model.OldValue != null) + oldProps.Color = new Color(model.OldValue.ToObject()); + break; + case "mentionable": + if (model.OldValue != null) + oldProps.Mentionable = model.OldValue.ToObject(); + break; + case "hoist": + if (model.OldValue != null) + oldProps.Hoist = model.OldValue.ToObject(); + break; + case "name": + if (model.OldValue != null) + oldProps.Name = model.OldValue.ToObject(); + break; + case "permissions": + if (model.OldValue != null) + oldProps.Permissions = new GuildPermissions(model.OldValue.ToObject()); + break; + } + } + + return new RoleDeleteAuditLogData(oldProps); + } + + //TODO: replace this with something read-only + public RoleProperties Properties { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs new file mode 100644 index 000000000..4c8d2cf9b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -0,0 +1,65 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleUpdateAuditLogData : IAuditLogData + { + private RoleUpdateAuditLogData(RoleProperties oldProps, RoleProperties newProps) + { + Before = oldProps; + After = newProps; + } + + internal static RoleUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var newProps = new RoleProperties(); + var oldProps = new RoleProperties(); + + foreach (var model in changes) + { + switch (model.ChangedProperty) + { + case "color": + if (model.NewValue != null) + newProps.Color = new Color(model.NewValue.ToObject()); + if (model.OldValue != null) + oldProps.Color = new Color(model.OldValue.ToObject()); + break; + case "mentionable": + if (model.NewValue != null) + newProps.Mentionable = model.NewValue.ToObject(); + if (model.OldValue != null) + oldProps.Mentionable = model.OldValue.ToObject(); + break; + case "hoist": + if (model.NewValue != null) + newProps.Hoist = model.NewValue.ToObject(); + if (model.OldValue != null) + oldProps.Hoist = model.OldValue.ToObject(); + break; + case "name": + if (model.NewValue != null) + newProps.Name = model.NewValue.ToObject(); + if (model.OldValue != null) + oldProps.Name = model.OldValue.ToObject(); + break; + case "permissions": + if (model.NewValue != null) + newProps.Permissions = new GuildPermissions(model.NewValue.ToObject()); + if (model.OldValue != null) + oldProps.Permissions = new GuildPermissions(model.OldValue.ToObject()); + break; + } + } + + return new RoleUpdateAuditLogData(oldProps, newProps); + } + + //TODO: replace these with something read-only + public RoleProperties Before { get; } + public RoleProperties After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs b/src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs deleted file mode 100644 index 3d53ed790..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/Options/MessageDeleteAuditLogOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Model = Discord.API.AuditLogOptions; - -namespace Discord.Rest -{ - public class MessageDeleteAuditLogOptions : IAuditLogOptions - { - internal MessageDeleteAuditLogOptions(BaseDiscordClient discord, Model model) - { - MessageCount = model.Count; - SourceChannelId = model.ChannelId; - } - - //TODO: turn this into an IChannel - public ulong SourceChannelId { get; } - public int MessageCount { get; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index 6f4de2e82..e30972913 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -13,11 +13,8 @@ namespace Discord.Rest Action = model.Action; if (model.Changes != null) - Changes = AuditLogHelper.CreateChange(discord, fullLog, model, model.Changes); - if (model.Options != null) - Options = AuditLogHelper.CreateOptions(discord, fullLog, model, model.Options); + Data = AuditLogHelper.CreateData(discord, fullLog, model); - TargetId = model.TargetId; User = user; Reason = model.Reason; @@ -33,14 +30,13 @@ namespace Discord.Rest return new RestAuditLogEntry(discord, fullLog, model, user); } + /// public ActionType Action { get; } - - public IAuditLogChanges Changes { get; } - public IAuditLogOptions Options { get; } - - public ulong? TargetId { get; } //TODO: if we're exposing this on the changes instead, do we need this? + /// + public IAuditLogData Data { get; } + /// public IUser User { get; } - + /// public string Reason { get; } } } From aa67a5b209d100646c12fa71e1becbb3f350fc43 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sun, 25 Jun 2017 15:33:37 +0100 Subject: [PATCH 4/4] Finish implementing audit logs Except for InviteUpdateAuditLogData, everything else is implemented, as I'm not completely sure how to cause an invite update action to occur right now. --- src/Discord.Net.Rest/API/Common/AuditLog.cs | 4 +- .../API/Common/AuditLogWebhook.cs | 16 ++++ .../Entities/AuditLogs/AuditLogHelper.cs | 18 ++-- .../DataTypes/GuildUpdateAuditLogData.cs | 94 ++++++++++++++++--- .../DataTypes/InviteDeleteAuditLogData.cs | 55 +++++++++++ .../DataTypes/OverwriteCreateAuditLogData.cs | 6 +- .../DataTypes/OverwriteDeleteAuditLogData.cs | 7 +- .../DataTypes/OverwriteUpdateAuditLogData.cs | 6 +- .../AuditLogs/DataTypes/UnbanAuditLogData.cs | 23 +++++ .../DataTypes/WebhookCreateAuditLogData.cs | 44 +++++++++ .../DataTypes/WebhookDeleteAuditLogData.cs | 44 +++++++++ .../DataTypes/WebhookUpdateAuditLogData.cs | 68 ++++++++++++++ .../Entities/AuditLogs/RestAuditLogEntry.cs | 6 +- 13 files changed, 346 insertions(+), 45 deletions(-) create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogWebhook.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs index f2147cb0e..964399d92 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLog.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -5,8 +5,8 @@ namespace Discord.API internal class AuditLog { //TODO: figure out how this works - //[JsonProperty("webhooks")] - //public object Webhooks { get; set; } + [JsonProperty("webhooks")] + public AuditLogWebhook[] Webhooks { get; set; } [JsonProperty("users")] public User[] Users { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/AuditLogWebhook.cs b/src/Discord.Net.Rest/API/Common/AuditLogWebhook.cs new file mode 100644 index 000000000..12d8bfba7 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogWebhook.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLogWebhook : User + { + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("token")] + public string Token { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 71c806a0d..541f008b0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -using Model = Discord.API.AuditLog; +using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest @@ -35,7 +32,7 @@ namespace Discord.Rest case ActionType.Ban: return BanAuditLogData.Create(discord, log, entry); case ActionType.Unban: - break; + return UnbanAuditLogData.Create(discord, log, entry); case ActionType.MemberUpdated: return MemberUpdateAuditLogData.Create(discord, log, entry); case ActionType.MemberRoleUpdated: @@ -53,14 +50,14 @@ namespace Discord.Rest case ActionType.InviteUpdated: break; case ActionType.InviteDeleted: - break; + return InviteDeleteAuditLogData.Create(discord, log, entry); case ActionType.WebhookCreated: //50 - break; + return WebhookCreateAuditLogData.Create(discord, log, entry); case ActionType.WebhookUpdated: - break; + return WebhookUpdateAuditLogData.Create(discord, log, entry); case ActionType.WebhookDeleted: - break; + return WebhookDeleteAuditLogData.Create(discord, log, entry); case ActionType.EmojiCreated: //60 return EmoteCreateAuditLogData.Create(discord, log, entry); @@ -71,7 +68,8 @@ namespace Discord.Rest case ActionType.MessageDeleted: //72 return MessageDeleteAuditLogData.Create(discord, log, entry); - default: + + default: //Unknown return null; } return null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs index cd7603f78..5d034d9d9 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -1,23 +1,38 @@ -using Model = Discord.API.AuditLog; +using System.Linq; + +using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { public class GuildUpdateAuditLogData : IAuditLogData { - private GuildUpdateAuditLogData() - { } + private GuildUpdateAuditLogData(GuildInfo before, GuildInfo after) + { + Before = before; + After = after; + } internal static GuildUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; - int? oldAfkTimeout, newAfkTimeout; - DefaultMessageNotifications? oldDefaultMessageNotifications, newDefaultMessageNotifications; - ulong? oldAfkChannelId, newAfkChannelId; - string oldName, newName; - string oldRegionId, newRegionId; - string oldIconHash, newIconHash; + int? oldAfkTimeout = null, + newAfkTimeout = null; + DefaultMessageNotifications? oldDefaultMessageNotifications = null, + newDefaultMessageNotifications = null; + ulong? oldAfkChannelId = null, + newAfkChannelId = null; + string oldName = null, + newName = null; + string oldRegionId = null, + newRegionId = null; + string oldIconHash = null, + newIconHash = null; + VerificationLevel? oldVerificationLevel = null, + newVerificationLevel = null; + ulong? oldOwnerId = null, + newOwnerId = null; foreach (var change in changes) { @@ -47,12 +62,67 @@ namespace Discord.Rest oldIconHash = change.OldValue?.ToObject(); newIconHash = change.NewValue?.ToObject(); break; - //TODO: owner, verification level + case "verification_level": + oldVerificationLevel = change.OldValue?.ToObject(); + newVerificationLevel = change.NewValue?.ToObject(); + break; + case "owner": + oldOwnerId = change.OldValue?.ToObject(); + newOwnerId = change.NewValue?.ToObject(); + break; + //TODO: 2fa auth, explicit content filter } } - //TODO: implement this - return null; + IUser oldOwner = null; + if (oldOwnerId != null) + { + var oldOwnerInfo = log.Users.FirstOrDefault(x => x.Id == oldOwnerId.Value); + oldOwner = RestUser.Create(discord, oldOwnerInfo); + } + + IUser newOwner = null; + if (newOwnerId != null) + { + var newOwnerInfo = log.Users.FirstOrDefault(x => x.Id == newOwnerId.Value); + newOwner = RestUser.Create(discord, newOwnerInfo); + } + + var before = new GuildInfo(oldAfkTimeout, oldDefaultMessageNotifications, + oldAfkChannelId, oldName, oldRegionId, oldIconHash, oldVerificationLevel, oldOwner); + var after = new GuildInfo(newAfkTimeout, newDefaultMessageNotifications, + newAfkChannelId, newName, newRegionId, newIconHash, newVerificationLevel, newOwner); + + return new GuildUpdateAuditLogData(before, after); + } + + public GuildInfo Before { get; } + public GuildInfo After { get; } + + public struct GuildInfo + { + internal GuildInfo(int? afkTimeout, DefaultMessageNotifications? defaultNotifs, + ulong? afkChannel, string name, string region, string icon, + VerificationLevel? verification, IUser owner) + { + AfkTimeout = afkTimeout; + DefaultMessageNotifications = defaultNotifs; + AfkChannelId = afkChannel; + Name = name; + RegionId = region; + IconHash = icon; + VerificationLevel = verification; + Owner = owner; + } + + public int? AfkTimeout { get; } + public DefaultMessageNotifications? DefaultMessageNotifications { get; } + public ulong? AfkChannelId { get; } + public string Name { get; } + public string RegionId { get; } + public string IconHash { get; } + public VerificationLevel? VerificationLevel { get; } + public IUser Owner { get; } } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs new file mode 100644 index 000000000..97c7a9b2c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -0,0 +1,55 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteDeleteAuditLogData : IAuditLogData + { + private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + Creator = inviter; + ChannelId = channelId; + Uses = uses; + MaxUses = maxUses; + } + + internal static InviteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + var maxAge = maxAgeModel.OldValue.ToObject(); + var code = codeModel.OldValue.ToObject(); + var temporary = temporaryModel.OldValue.ToObject(); + var inviterId = inviterIdModel.OldValue.ToObject(); + var channelId = channelIdModel.OldValue.ToObject(); + var uses = usesModel.OldValue.ToObject(); + var maxUses = maxUsesModel.OldValue.ToObject(); + + var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); + var inviter = RestUser.Create(discord, inviterInfo); + + return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); + } + + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } //TODO: IChannel-ify + public int Uses { get; } + public int MaxUses { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs index 6e5658f66..d58488136 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -27,11 +27,7 @@ namespace Discord.Rest var id = entry.Options.OverwriteTargetId.Value; var type = entry.Options.OverwriteType; - PermissionTarget target; - if (type == "member") - target = PermissionTarget.User; - else - target = PermissionTarget.Role; + PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index b1f2195b5..9c635d577 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -32,12 +32,7 @@ namespace Discord.Rest var id = idModel.OldValue.ToObject(); var allow = allowModel.OldValue.ToObject(); - PermissionTarget target; - - if (type == "member") - target = PermissionTarget.User; - else - target = PermissionTarget.Role; + PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs index 923cce28f..8ed9d1415 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -30,11 +30,7 @@ namespace Discord.Rest var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); - PermissionTarget target; - if (entry.Options.OverwriteType == "member") - target = PermissionTarget.User; - else - target = PermissionTarget.Role; + PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs new file mode 100644 index 000000000..c94f18271 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class UnbanAuditLogData : IAuditLogData + { + private UnbanAuditLogData(IUser user) + { + Target = user; + } + + internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new UnbanAuditLogData(RestUser.Create(discord, userInfo)); + } + + public IUser Target { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs new file mode 100644 index 000000000..158ee4d4e --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -0,0 +1,44 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookCreateAuditLogData : IAuditLogData + { + private WebhookCreateAuditLogData(IWebhookUser user, string token, string name, ulong channelId) + { + Webhook = user; + Token = token; + Name = name; + ChannelId = channelId; + } + + internal static WebhookCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var channelId = channelIdModel.NewValue.ToObject(); + var type = typeModel.NewValue.ToObject(); //TODO: what on *earth* is this for + var name = nameModel.NewValue.ToObject(); + + var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); + var userInfo = RestWebhookUser.Create(discord, null, webhookInfo, entry.TargetId.Value); + + return new WebhookCreateAuditLogData(userInfo, webhookInfo.Token, name, channelId); + } + + //Corresponds to the *current* data + public IWebhookUser Webhook { get; } + public string Token { get; } + + //Corresponds to the *audit log* data + public string Name { get; } + public ulong ChannelId { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs new file mode 100644 index 000000000..0121f48ad --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookDeleteAuditLogData : IAuditLogData + { + private WebhookDeleteAuditLogData(ulong id, ulong channel, string name, string avatar) + { + WebhookId = id; + ChannelId = channel; + Name = name; + Avatar = avatar; + } + + internal static WebhookDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + var channelId = channelIdModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + var avatarHash = avatarHashModel?.OldValue?.ToObject(); + + return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, name, avatarHash); + } + + public ulong WebhookId { get; } + public ulong ChannelId { get; } + public string Name { get; } + public string Avatar { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs new file mode 100644 index 000000000..3241832ba --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookUpdateAuditLogData : IAuditLogData + { + private WebhookUpdateAuditLogData(IWebhookUser user, string token, WebhookInfo before, WebhookInfo after) + { + Webhook = user; + Token = token; + Before = before; + After = after; + } + + internal static WebhookUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + var oldName = nameModel?.OldValue?.ToObject(); + var oldChannelId = channelIdModel?.OldValue?.ToObject(); + var oldAvatar = avatarHashModel?.OldValue?.ToObject(); + var before = new WebhookInfo(oldName, oldChannelId, oldAvatar); + + var newName = nameModel?.NewValue?.ToObject(); + var newChannelId = channelIdModel?.NewValue?.ToObject(); + var newAvatar = avatarHashModel?.NewValue?.ToObject(); + var after = new WebhookInfo(newName, newChannelId, newAvatar); + + var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); + var userInfo = RestWebhookUser.Create(discord, null, webhookInfo, entry.TargetId.Value); + + return new WebhookUpdateAuditLogData(userInfo, webhookInfo.Token, before, after); + } + + //Again, the *current* data + public IWebhookUser Webhook { get; } + public string Token { get; } + + //And the *audit log* data + public WebhookInfo Before { get; } + public WebhookInfo After { get; } + + public struct WebhookInfo + { + internal WebhookInfo(string name, ulong? channelId, string avatar) + { + Name = name; + ChannelId = channelId; + Avatar = avatar; + } + + public string Name { get; } + public ulong? ChannelId { get; } + public string Avatar { get; } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index e30972913..9e30a5014 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -11,12 +11,8 @@ namespace Discord.Rest : base(discord, model.Id) { Action = model.Action; - - if (model.Changes != null) - Data = AuditLogHelper.CreateData(discord, fullLog, model); - + Data = AuditLogHelper.CreateData(discord, fullLog, model); User = user; - Reason = model.Reason; }