feat: kick logs

This commit is contained in:
sam 2024-10-12 23:28:15 +02:00
parent 6cb515c67d
commit f524afb05b
7 changed files with 118 additions and 83 deletions

View file

@ -16,6 +16,8 @@ public class GuildMemberRemoveResponder(
DatabaseContext db,
IMemberCache memberCache,
RoleCache roleCache,
UserCache userCache,
AuditLogCache auditLogCache,
WebhookExecutorService webhookExecutor
) : IResponder<IGuildMemberRemove>
{
@ -79,6 +81,33 @@ public class GuildMemberRemoveResponder(
LogChannelType.GuildMemberRemove,
embed.Build().GetOrThrow()
);
// Check for a kick audit log event. We don't get a separate "kick" event so we have to check this manually
await Task.Delay(2000, ct);
if (!auditLogCache.TryGetKick(evt.GuildID, evt.User.ID, out var actionData))
{
return Result.Success;
}
var kick = new EmbedBuilder()
.WithTitle("User kicked")
.WithAuthor(evt.User.Tag(), iconUrl: evt.User.AvatarUrl())
.WithColour(DiscordUtils.Red)
.WithCurrentTimestamp()
.WithDescription($"<@{evt.User.ID}>");
kick.AddField(
"Responsible moderator",
await userCache.TryFormatModeratorAsync(actionData)
);
kick.AddField("Reason", actionData.Reason ?? "No reason given");
webhookExecutor.QueueLog(
guildConfig,
LogChannelType.GuildMemberKick,
kick.Build().GetOrThrow()
);
return Result.Success;
}
finally

View file

@ -220,14 +220,8 @@ public class GuildMemberUpdateResponder(
if (auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData))
{
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
embed.AddField(
"Responsible moderator",
moderator == null
? $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"
: $"{moderator.Tag()} <@{moderator.ID}>"
);
var moderator = await userCache.TryFormatModeratorAsync(actionData);
embed.AddField("Responsible moderator", moderator);
embed.AddField("Reason", actionData.Reason ?? "No reason given");
}
else
@ -337,13 +331,8 @@ public class GuildMemberUpdateResponder(
auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData)
)
{
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
keyRoleUpdate.AddField(
"Responsible moderator",
moderator == null
? $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"
: $"{moderator.Tag()} <@{moderator.ID}>"
);
var moderator = await userCache.TryFormatModeratorAsync(actionData);
keyRoleUpdate.AddField("Responsible moderator", moderator);
}
else
{

View file

@ -6,48 +6,34 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<!-- Local clones of Remora.Discord with our PRs merged in, until a new version is released on nuget -->
<ItemGroup>
<ProjectReference Include="../../Remora.Discord/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj"/>
<ProjectReference Include="../../Remora.Discord/Backend/Remora.Discord.API/Remora.Discord.API.csproj"/>
<ProjectReference Include="../../Remora.Discord/Backend/Remora.Discord.Gateway/Remora.Discord.Gateway.csproj"/>
<ProjectReference Include="../../Remora.Discord/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj"/>
<ProjectReference Include="../../Remora.Discord/Remora.Discord.Commands/Remora.Discord.Commands.csproj"/>
<ProjectReference Include="../../Remora.Discord/Remora.Discord.Extensions/Remora.Discord.Extensions.csproj"/>
<ProjectReference Include="../../Remora.Discord/Remora.Discord.Hosting/Remora.Discord.Hosting.csproj"/>
<ProjectReference Include="../../Remora.Discord/Remora.Discord.Interactivity/Remora.Discord.Interactivity.csproj"/>
<ProjectReference Include="../../Remora.Discord/Remora.Discord.Pagination/Remora.Discord.Pagination.csproj"/>
<ProjectReference Include="../../Remora.Discord/Remora.Discord/Remora.Discord.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3"/>
<PackageReference Include="LazyCache" Version="2.4.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8"/>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3" />
<PackageReference Include="LazyCache" Version="2.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="NodaTime" Version="3.1.12"/>
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="8.0.8"/>
<PackageReference Include="Polly.Core" Version="8.4.2"/>
<PackageReference Include="Polly.RateLimiting" Version="8.4.2"/>
<PackageReference Include="prometheus-net" Version="8.2.1"/>
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1"/>
<PackageReference Include="Remora.Sdk" Version="3.1.2"/>
<PackageReference Include="Remora.Discord" Version="2024.3.0"/>
<PackageReference Include="Serilog" Version="4.0.2"/>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2"/>
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageReference Include="StackExchange.Redis" Version="2.8.16"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NodaTime" Version="3.1.12" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="8.0.8" />
<PackageReference Include="Polly.Core" Version="8.4.2" />
<PackageReference Include="Polly.RateLimiting" Version="8.4.2" />
<PackageReference Include="prometheus-net" Version="8.2.1" />
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageReference Include="Remora.Sdk" Version="3.1.2" />
<PackageReference Include="Remora.Discord" Version="2024.3.0-github11168366508" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="StackExchange.Redis" Version="2.8.16" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
</ItemGroup>
</Project>

View file

@ -1,4 +1,5 @@
using System.Drawing;
using Catalogger.Backend.Cache.InMemoryCache;
using Humanizer;
using OneOf;
using Remora.Discord.API.Abstractions.Gateway.Events;
@ -8,6 +9,7 @@ using Remora.Discord.API.Objects;
using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Extensions;
using Remora.Discord.Commands.Services;
using Remora.Discord.Extensions.Embeds;
using Remora.Rest.Core;
using Remora.Results;
@ -117,26 +119,6 @@ public static class DiscordExtensions
public static async Task<T> GetOrThrow<T>(this Task<Result<T>> result) =>
(await result).GetOrThrow();
public static async Task<Result> UpdateMessageAsync(
this IDiscordRestInteractionAPI interactionApi,
IInteraction interaction,
InteractionMessageCallbackData data
) =>
await interactionApi.CreateInteractionResponseAsync(
interaction.ID,
interaction.Token,
new InteractionResponse(
InteractionCallbackType.UpdateMessage,
new Optional<
OneOf<
IInteractionMessageCallbackData,
IInteractionAutocompleteCallbackData,
IInteractionModalCallbackData
>
>(data)
)
);
public static string ToPrettyString(this IDiscordPermissionSet permissionSet) =>
string.Join(
", ",
@ -172,5 +154,16 @@ public static class DiscordExtensions
return filterByIds != null ? sorted.Where(r => filterByIds.Contains(r.ID)) : sorted;
}
public static async Task<string> TryFormatModeratorAsync(
this UserCache userCache,
AuditLogCache.ActionData actionData
)
{
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
return moderator != null
? $"{moderator.Tag()} <@{moderator.ID}>"
: $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*";
}
public class DiscordRestException(string message) : Exception(message);
}

View file

@ -0,0 +1,31 @@
using OneOf;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects;
using Remora.Rest.Core;
using Remora.Results;
namespace Catalogger.Backend.Extensions;
public static class DiscordRestExtensions
{
public static async Task<Result> UpdateMessageAsync(
this IDiscordRestInteractionAPI interactionApi,
IInteraction interaction,
InteractionMessageCallbackData data
) =>
await interactionApi.CreateInteractionResponseAsync(
interaction.ID,
interaction.Token,
new InteractionResponse(
InteractionCallbackType.UpdateMessage,
new Optional<
OneOf<
IInteractionMessageCallbackData,
IInteractionAutocompleteCallbackData,
IInteractionModalCallbackData
>
>(data)
)
);
}

View file

@ -15,6 +15,15 @@ public class PluralkitApiService(ILogger logger)
private const string ApiBaseUrl = "https://api.pluralkit.me/v2";
private readonly HttpClient _client = new();
private readonly ILogger _logger = logger.ForContext<PluralkitApiService>();
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
}.ConfigureForNodaTime(
new NodaJsonSettings
{
InstantConverter = new NodaPatternConverter<Instant>(InstantPattern.ExtendedIso),
}
);
private readonly ResiliencePipeline _pipeline = new ResiliencePipelineBuilder()
.AddRateLimiter(
@ -59,17 +68,7 @@ public class PluralkitApiService(ILogger logger)
throw new CataloggerError("Non-200 status code from PluralKit API");
}
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
}.ConfigureForNodaTime(
new NodaJsonSettings
{
InstantConverter = new NodaPatternConverter<Instant>(InstantPattern.ExtendedIso),
}
);
return await resp.Content.ReadFromJsonAsync<T>(jsonOptions, ct)
return await resp.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
?? throw new CataloggerError("JSON response from PluralKit API was null");
}