Skip to content

Commit

Permalink
Audit Logs implementation (#1055)
Browse files Browse the repository at this point in the history
* Copy audit logs impl from old branch and clean up

I suck at using git, so I'm gonna use brute force.

* Remove unnecessary TODOs

Category channels do not provide any new information, and the other
I forgot to remove beforehand

* Add invite update data, clean up after feedback

* Remove TODOs, add WebhookType enum for future use

WebhookType is a future-use type, as currently audit logs are the only
thing which may return it.
  • Loading branch information
FiniteReality authored and foxbot committed May 13, 2018
1 parent 97c8931 commit 39dffe8
Show file tree
Hide file tree
Showing 48 changed files with 1,576 additions and 3 deletions.
7 changes: 4 additions & 3 deletions src/Discord.Net.Core/DiscordConfig.cs
Expand Up @@ -4,10 +4,10 @@ namespace Discord
{
public class DiscordConfig
{
public const int APIVersion = 6;
public const int APIVersion = 6;
public static string Version { get; } =
typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ??
typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ??
typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ??
"Unknown";

public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})";
Expand All @@ -20,10 +20,11 @@ public class DiscordConfig
public const int MaxMessagesPerBatch = 100;
public const int MaxUsersPerBatch = 1000;
public const int MaxGuildsPerBatch = 100;
public const int MaxAuditLogEntriesPerBatch = 100;

/// <summary> Gets or sets how a request should act in the case of an error, by default. </summary>
public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry;

/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary>
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;

Expand Down
50 changes: 50 additions & 0 deletions 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
{
/// <summary>
/// The action type within a <see cref="IAuditLogEntry"/>
/// </summary>
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
}
}
14 changes: 14 additions & 0 deletions src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents data applied to an <see cref="IAuditLogEntry"/>
/// </summary>
public interface IAuditLogData
{ }
}
34 changes: 34 additions & 0 deletions src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents an entry in an audit log
/// </summary>
public interface IAuditLogEntry : IEntity<ulong>
{
/// <summary>
/// The action which occured to create this entry
/// </summary>
ActionType Action { get; }

/// <summary>
/// The data for this entry. May be <see cref="null"/> if no data was available.
/// </summary>
IAuditLogData Data { get; }

/// <summary>
/// The user responsible for causing the changes
/// </summary>
IUser User { get; }

/// <summary>
/// The reason behind the change. May be <see cref="null"/> if no reason was provided.
/// </summary>
string Reason { get; }
}
}
4 changes: 4 additions & 0 deletions src/Discord.Net.Core/Entities/Guilds/IGuild.cs
Expand Up @@ -139,6 +139,10 @@ public interface IGuild : IDeletable, ISnowflakeEntity
/// <summary> 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. </summary>
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);

/// <summary> Gets the specified number of audit log entries for this guild. </summary>
Task<IReadOnlyCollection<IAuditLogEntry>> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch,
CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary> Gets the webhook in this guild with the provided id, or null if not found. </summary>
Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null);
/// <summary> Gets a collection of all webhooks for this guild. </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs
@@ -0,0 +1,14 @@
namespace Discord
{
/// <summary>
/// Represents the type of a webhook.
/// </summary>
/// <remarks>
/// This type is currently unused, and is only returned in audit log responses.
/// </remarks>
public enum WebhookType
{
/// <summary> An incoming webhook </summary>
Incoming = 1
}
}
16 changes: 16 additions & 0 deletions src/Discord.Net.Rest/API/Common/AuditLog.cs
@@ -0,0 +1,16 @@
using Newtonsoft.Json;

namespace Discord.API
{
internal class AuditLog
{
[JsonProperty("webhooks")]
public Webhook[] Webhooks { get; set; }

[JsonProperty("users")]
public User[] Users { get; set; }

[JsonProperty("audit_log_entries")]
public AuditLogEntry[] Entries { get; set; }
}
}
17 changes: 17 additions & 0 deletions 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; }
}
}
26 changes: 26 additions & 0 deletions 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; }
}
}
27 changes: 27 additions & 0 deletions src/Discord.Net.Rest/API/Common/AuditLogOptions.cs
@@ -0,0 +1,27 @@
using Newtonsoft.Json;

namespace Discord.API
{
internal class AuditLogOptions
{
//Message delete
[JsonProperty("count")]
public int? MessageDeleteCount { get; set; }
[JsonProperty("channel_id")]
public ulong? MessageDeleteChannelId { get; set; }

//Prune
[JsonProperty("delete_member_days")]
public int? PruneDeleteMemberDays { get; set; }
[JsonProperty("members_removed")]
public int? PruneMembersRemoved { get; set; }

//Overwrite Update
[JsonProperty("role_name")]
public string OverwriteRoleName { get; set; }
[JsonProperty("type")]
public string OverwriteType { get; set; }
[JsonProperty("id")]
public ulong? OverwriteTargetId { get; set; }
}
}
8 changes: 8 additions & 0 deletions src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs
@@ -0,0 +1,8 @@
namespace Discord.API.Rest
{
class GetAuditLogsParams
{
public Optional<int> Limit { get; set; }
public Optional<ulong> BeforeEntryId { get; set; }
}
}
20 changes: 20 additions & 0 deletions src/Discord.Net.Rest/DiscordRestApiClient.cs
Expand Up @@ -1206,6 +1206,26 @@ public async Task<IReadOnlyCollection<VoiceRegion>> GetGuildVoiceRegionsAsync(ul
return await SendAsync<IReadOnlyCollection<VoiceRegion>>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false);
}

//Audit logs
public async Task<AuditLog> 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);

var ids = new BucketIds(guildId: guildId);
Expression<Func<string>> 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<AuditLog>("GET", endpoint, ids, options: options).ConfigureAwait(false);
}

//Webhooks
public async Task<Webhook> CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null)
{
Expand Down
58 changes: 58 additions & 0 deletions src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
internal static class AuditLogHelper
{
private static readonly Dictionary<ActionType, Func<BaseDiscordClient, Model, EntryModel, IAuditLogData>> CreateMapping
= new Dictionary<ActionType, Func<BaseDiscordClient, Model, EntryModel, IAuditLogData>>()
{
[ActionType.GuildUpdated] = GuildUpdateAuditLogData.Create,

[ActionType.ChannelCreated] = ChannelCreateAuditLogData.Create,
[ActionType.ChannelUpdated] = ChannelUpdateAuditLogData.Create,
[ActionType.ChannelDeleted] = ChannelDeleteAuditLogData.Create,

[ActionType.OverwriteCreated] = OverwriteCreateAuditLogData.Create,
[ActionType.OverwriteUpdated] = OverwriteUpdateAuditLogData.Create,
[ActionType.OverwriteDeleted] = OverwriteDeleteAuditLogData.Create,

[ActionType.Kick] = KickAuditLogData.Create,
[ActionType.Prune] = PruneAuditLogData.Create,
[ActionType.Ban] = BanAuditLogData.Create,
[ActionType.Unban] = UnbanAuditLogData.Create,
[ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create,
[ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create,

[ActionType.RoleCreated] = RoleCreateAuditLogData.Create,
[ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create,
[ActionType.RoleDeleted] = RoleDeleteAuditLogData.Create,

[ActionType.InviteCreated] = InviteCreateAuditLogData.Create,
[ActionType.InviteUpdated] = InviteUpdateAuditLogData.Create,
[ActionType.InviteDeleted] = InviteDeleteAuditLogData.Create,

[ActionType.WebhookCreated] = WebhookCreateAuditLogData.Create,
[ActionType.WebhookUpdated] = WebhookUpdateAuditLogData.Create,
[ActionType.WebhookDeleted] = WebhookDeleteAuditLogData.Create,

[ActionType.EmojiCreated] = EmoteCreateAuditLogData.Create,
[ActionType.EmojiUpdated] = EmoteUpdateAuditLogData.Create,
[ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create,

[ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create,
};

public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry)
{
if (CreateMapping.TryGetValue(entry.Action, out var func))
return func(discord, log, entry);

return null;
}
}
}
@@ -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; }
}
}

0 comments on commit 39dffe8

Please sign in to comment.