Browse Source

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
pull/719/head
FiniteReality 8 years ago
parent
commit
794816dcee
17 changed files with 401 additions and 61 deletions
  1. +2
    -4
      src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs
  2. +3
    -3
      src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs
  3. +1
    -1
      src/Discord.Net.Rest/API/Common/AuditLogEntry.cs
  4. +1
    -1
      src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs
  5. +7
    -2
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  6. +58
    -9
      src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs
  7. +40
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs
  8. +29
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs
  9. +23
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs
  10. +33
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs
  11. +54
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs
  12. +0
    -19
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs
  13. +51
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs
  14. +33
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs
  15. +45
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs
  16. +19
    -20
      src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs
  17. +2
    -2
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs

src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChange.cs → src/Discord.Net.Core/Entities/AuditLogs/IAuditLogChanges.cs View File

@@ -9,8 +9,6 @@ namespace Discord
/// <summary>
/// Represents changes which may occur within a <see cref="IAuditLogEntry"/>
/// </summary>
public interface IAuditLogChange
{

}
public interface IAuditLogChanges
{ }
}

+ 3
- 3
src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs View File

@@ -19,7 +19,7 @@ namespace Discord
/// <summary>
/// The changes which occured within this entry. May be empty if no changes occured.
/// </summary>
IReadOnlyCollection<IAuditLogChange> Changes { get; }
IAuditLogChanges Changes { get; }

/// <summary>
/// Any options which apply to this entry. If no options were provided, this may be <see cref="null"/>.
@@ -27,9 +27,9 @@ namespace Discord
IAuditLogOptions Options { get; }

/// <summary>
/// The id which the target applies to
/// The id which the target applies to, or null if it does not apply to anything specific.
/// </summary>
ulong TargetId { get; }
ulong? TargetId { get; }

/// <summary>
/// The user responsible for causing the changes


+ 1
- 1
src/Discord.Net.Rest/API/Common/AuditLogEntry.cs View File

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



+ 1
- 1
src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs View File

@@ -3,6 +3,6 @@
class GetAuditLogsParams
{
public Optional<int> Limit { get; set; }
public Optional<ulong> AfterEntryId { get; set; }
public Optional<ulong> BeforeEntryId { get; set; }
}
}

+ 7
- 2
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -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<Func<string>> endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&after={afterEntryId}";
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);
}



+ 58
- 9
src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs View File

@@ -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.");
}
}
}


+ 40
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/ChannelDeleteChanges.cs View File

@@ -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<Overwrite> 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<API.Overwrite[]>()
.Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny)))
.ToList();
var type = typeModel.OldValue.ToObject<ChannelType>();
var name = nameModel.OldValue.ToObject<string>();

return new ChannelDeleteChanges(name, type, overwrites.ToReadOnlyCollection());
}

public string ChannelName { get; }
public ChannelType ChannelType { get; }
public IReadOnlyCollection<Overwrite> Overwrites { get; }
}
}

+ 29
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteCreateChanges.cs View File

@@ -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<string>();
var emote = new Emote(entry.TargetId.Value, emoteName);

return new EmoteCreateChanges(emote);
}

public Emote Emote { get; }
}
}

+ 23
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteDeleteChanges.cs View File

@@ -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<string>();
var emote = new Emote(entry.TargetId.Value, emoteName);
return new EmoteDeleteChanges(emote);
}

public Emote Emote { get; }
}
}

+ 33
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/EmoteUpdateChanges.cs View File

@@ -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<string>();
var emote = new Emote(entry.TargetId.Value, emoteName);

var oldName = model.OldValue.ToObject<string>();

return new EmoteUpdateChanges(emote, oldName);
}

public Emote Emote { get; }
public string PreviousName { get; }
}
}

+ 54
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/InviteCreateChanges.cs View File

@@ -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<int>();
var code = codeModel.NewValue.ToObject<string>();
var temporary = temporaryModel.NewValue.ToObject<bool>();
var inviterId = inviterIdModel.NewValue.ToObject<ulong>();
var channelId = channelIdModel.NewValue.ToObject<ulong>();
var uses = usesModel.NewValue.ToObject<int>();
var maxUses = maxUsesModel.NewValue.ToObject<int>();

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; }
}
}

+ 0
- 19
src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleAuditLogChange.cs View File

@@ -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<ulong>("id");
}

public bool RoleAdded { get; set; }
//TODO: convert to IRole
public ulong RoleId { get; set; }
}
}

+ 51
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberRoleChanges.cs View File

@@ -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<RoleInfo> 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<API.Role[]>(),
(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<RoleInfo> 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; }
}
}
}

+ 33
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/MemberUpdateChanges.cs View File

@@ -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<string>();
var oldNick = model.OldValue?.ToObject<string>();

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; }
}
}

+ 45
- 0
src/Discord.Net.Rest/Entities/AuditLogs/Changes/OverwriteDeleteChanges.cs View File

@@ -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<ulong>();
var type = typeModel.OldValue.ToObject<string>(); //'role' or 'member', can't use PermissionsTarget :(
var id = idModel.OldValue.ToObject<ulong>();
var allow = allowModel.OldValue.ToObject<ulong>();

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; }
}
}

+ 19
- 20
src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs View File

@@ -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<ulong>, 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<IAuditLogChange>();

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<IAuditLogChange> 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; }
}
}

+ 2
- 2
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -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,


Loading…
Cancel
Save