feat: kick logs
This commit is contained in:
parent
6cb515c67d
commit
f524afb05b
7 changed files with 118 additions and 83 deletions
|
|
@ -16,6 +16,8 @@ public class GuildMemberRemoveResponder(
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
RoleCache roleCache,
|
RoleCache roleCache,
|
||||||
|
UserCache userCache,
|
||||||
|
AuditLogCache auditLogCache,
|
||||||
WebhookExecutorService webhookExecutor
|
WebhookExecutorService webhookExecutor
|
||||||
) : IResponder<IGuildMemberRemove>
|
) : IResponder<IGuildMemberRemove>
|
||||||
{
|
{
|
||||||
|
|
@ -79,6 +81,33 @@ public class GuildMemberRemoveResponder(
|
||||||
LogChannelType.GuildMemberRemove,
|
LogChannelType.GuildMemberRemove,
|
||||||
embed.Build().GetOrThrow()
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -220,14 +220,8 @@ public class GuildMemberUpdateResponder(
|
||||||
|
|
||||||
if (auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData))
|
if (auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData))
|
||||||
{
|
{
|
||||||
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
|
var moderator = await userCache.TryFormatModeratorAsync(actionData);
|
||||||
embed.AddField(
|
embed.AddField("Responsible moderator", moderator);
|
||||||
"Responsible moderator",
|
|
||||||
moderator == null
|
|
||||||
? $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"
|
|
||||||
: $"{moderator.Tag()} <@{moderator.ID}>"
|
|
||||||
);
|
|
||||||
|
|
||||||
embed.AddField("Reason", actionData.Reason ?? "No reason given");
|
embed.AddField("Reason", actionData.Reason ?? "No reason given");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -337,13 +331,8 @@ public class GuildMemberUpdateResponder(
|
||||||
auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData)
|
auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
|
var moderator = await userCache.TryFormatModeratorAsync(actionData);
|
||||||
keyRoleUpdate.AddField(
|
keyRoleUpdate.AddField("Responsible moderator", moderator);
|
||||||
"Responsible moderator",
|
|
||||||
moderator == null
|
|
||||||
? $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"
|
|
||||||
: $"{moderator.Tag()} <@{moderator.ID}>"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,6 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</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>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||||
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3" />
|
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.3" />
|
||||||
|
|
@ -41,7 +27,7 @@
|
||||||
<PackageReference Include="prometheus-net" Version="8.2.1" />
|
<PackageReference Include="prometheus-net" Version="8.2.1" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||||
<PackageReference Include="Remora.Sdk" Version="3.1.2" />
|
<PackageReference Include="Remora.Sdk" Version="3.1.2" />
|
||||||
<PackageReference Include="Remora.Discord" Version="2024.3.0"/>
|
<PackageReference Include="Remora.Discord" Version="2024.3.0-github11168366508" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
<PackageReference Include="Serilog" Version="4.0.2" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using OneOf;
|
using OneOf;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
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.Contexts;
|
||||||
using Remora.Discord.Commands.Extensions;
|
using Remora.Discord.Commands.Extensions;
|
||||||
using Remora.Discord.Commands.Services;
|
using Remora.Discord.Commands.Services;
|
||||||
|
using Remora.Discord.Extensions.Embeds;
|
||||||
using Remora.Rest.Core;
|
using Remora.Rest.Core;
|
||||||
using Remora.Results;
|
using Remora.Results;
|
||||||
|
|
||||||
|
|
@ -117,26 +119,6 @@ public static class DiscordExtensions
|
||||||
public static async Task<T> GetOrThrow<T>(this Task<Result<T>> result) =>
|
public static async Task<T> GetOrThrow<T>(this Task<Result<T>> result) =>
|
||||||
(await result).GetOrThrow();
|
(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) =>
|
public static string ToPrettyString(this IDiscordPermissionSet permissionSet) =>
|
||||||
string.Join(
|
string.Join(
|
||||||
", ",
|
", ",
|
||||||
|
|
@ -172,5 +154,16 @@ public static class DiscordExtensions
|
||||||
return filterByIds != null ? sorted.Where(r => filterByIds.Contains(r.ID)) : sorted;
|
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);
|
public class DiscordRestException(string message) : Exception(message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
Catalogger.Backend/Extensions/DiscordRestExtensions.cs
Normal file
31
Catalogger.Backend/Extensions/DiscordRestExtensions.cs
Normal 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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,15 @@ public class PluralkitApiService(ILogger logger)
|
||||||
private const string ApiBaseUrl = "https://api.pluralkit.me/v2";
|
private const string ApiBaseUrl = "https://api.pluralkit.me/v2";
|
||||||
private readonly HttpClient _client = new();
|
private readonly HttpClient _client = new();
|
||||||
private readonly ILogger _logger = logger.ForContext<PluralkitApiService>();
|
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()
|
private readonly ResiliencePipeline _pipeline = new ResiliencePipelineBuilder()
|
||||||
.AddRateLimiter(
|
.AddRateLimiter(
|
||||||
|
|
@ -59,17 +68,7 @@ public class PluralkitApiService(ILogger logger)
|
||||||
throw new CataloggerError("Non-200 status code from PluralKit API");
|
throw new CataloggerError("Non-200 status code from PluralKit API");
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonOptions = new JsonSerializerOptions
|
return await resp.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
|
||||||
{
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
|
||||||
}.ConfigureForNodaTime(
|
|
||||||
new NodaJsonSettings
|
|
||||||
{
|
|
||||||
InstantConverter = new NodaPatternConverter<Instant>(InstantPattern.ExtendedIso),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return await resp.Content.ReadFromJsonAsync<T>(jsonOptions, ct)
|
|
||||||
?? throw new CataloggerError("JSON response from PluralKit API was null");
|
?? throw new CataloggerError("JSON response from PluralKit API was null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@ Command-line tools for this project can be installed with `dotnet tool restore`.
|
||||||
- We use [CSharpier][csharpier] for formatting .NET code.
|
- We use [CSharpier][csharpier] for formatting .NET code.
|
||||||
It can be called with `dotnet csharpier .`, but is automatically run by Husky pre-commit.
|
It can be called with `dotnet csharpier .`, but is automatically run by Husky pre-commit.
|
||||||
|
|
||||||
|
### Nuget
|
||||||
|
|
||||||
|
We currently use Remora's GitHub packages as the releases on nuget.org are missing some key features.
|
||||||
|
Add these with `dotnet nuget add source --username <githubUsername> --password <githubToken> --store-password-in-clear-text --name Remora "https://nuget.pkg.github.com/Remora/index.json"`
|
||||||
|
|
||||||
|
You must generate a personal access token (classic) [here](personal-access-token). Only give it the `read:packages` permission.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2021-present sam (https://starshines.gay)
|
Copyright (C) 2021-present sam (https://starshines.gay)
|
||||||
|
|
@ -30,3 +37,4 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
[old-repo]: https://github.com/starshine-sys/catalogger/tree/main
|
[old-repo]: https://github.com/starshine-sys/catalogger/tree/main
|
||||||
[husky]: https://github.com/alirezanet/Husky.Net
|
[husky]: https://github.com/alirezanet/Husky.Net
|
||||||
[csharpier]: https://csharpier.com/
|
[csharpier]: https://csharpier.com/
|
||||||
|
[personal-access-token]: https://github.com/settings/tokens
|
||||||
Loading…
Add table
Add a link
Reference in a new issue