feat: add /configure-channels command
This commit is contained in:
parent
110edd34b4
commit
4db09346e2
7 changed files with 588 additions and 25 deletions
224
Catalogger.Backend/Bot/Commands/ChannelCommands.cs
Normal file
224
Catalogger.Backend/Bot/Commands/ChannelCommands.cs
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Catalogger.Backend.Cache;
|
||||||
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Queries;
|
||||||
|
using Catalogger.Backend.Extensions;
|
||||||
|
using Catalogger.Backend.Services;
|
||||||
|
using Remora.Commands.Attributes;
|
||||||
|
using Remora.Commands.Groups;
|
||||||
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
using Remora.Discord.API.Objects;
|
||||||
|
using Remora.Discord.Commands.Attributes;
|
||||||
|
using Remora.Discord.Commands.Contexts;
|
||||||
|
using Remora.Discord.Commands.Extensions;
|
||||||
|
using Remora.Discord.Commands.Feedback.Messages;
|
||||||
|
using Remora.Discord.Commands.Feedback.Services;
|
||||||
|
using Remora.Discord.Commands.Services;
|
||||||
|
using Remora.Discord.Interactivity;
|
||||||
|
using Remora.Discord.Interactivity.Services;
|
||||||
|
using Remora.Rest.Core;
|
||||||
|
using Remora.Results;
|
||||||
|
using DbGuild = Catalogger.Backend.Database.Models.Guild;
|
||||||
|
using IResult = Remora.Results.IResult;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Bot.Commands;
|
||||||
|
|
||||||
|
public class ChannelCommands(
|
||||||
|
ILogger logger,
|
||||||
|
DatabaseContext db,
|
||||||
|
GuildCacheService guildCache,
|
||||||
|
ChannelCacheService channelCache,
|
||||||
|
IFeedbackService feedbackService,
|
||||||
|
ContextInjectionService contextInjection,
|
||||||
|
InMemoryDataService<Snowflake, ChannelCommandData> dataService) : CommandGroup
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = logger.ForContext<ChannelCommands>();
|
||||||
|
|
||||||
|
[Command("configure-channels")]
|
||||||
|
[Description("Configure log channels for this server.")]
|
||||||
|
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
||||||
|
public async Task<IResult> ConfigureChannelsAsync()
|
||||||
|
{
|
||||||
|
if (contextInjection.Context is not IInteractionCommandContext ctx) throw new CataloggerError("No context");
|
||||||
|
if (!ctx.TryGetUserID(out var userId)) throw new CataloggerError("No user ID in context");
|
||||||
|
if (!ctx.TryGetGuildID(out var guildId)) throw new CataloggerError("No guild ID in context");
|
||||||
|
if (!guildCache.TryGet(guildId, out var guild)) throw new CataloggerError("Guild not in cache");
|
||||||
|
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||||
|
var guildConfig = await db.GetGuildAsync(guildId);
|
||||||
|
|
||||||
|
var (embeds, components) = BuildRootMenu(guildChannels, guild, guildConfig);
|
||||||
|
|
||||||
|
var msg = await feedbackService.SendContextualAsync(embeds: embeds,
|
||||||
|
options: new FeedbackMessageOptions(MessageComponents: components)).GetOrThrow();
|
||||||
|
|
||||||
|
dataService.TryAddData(msg.ID, new ChannelCommandData(userId, CurrentPage: null));
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (List<IEmbed>, List<IMessageComponent>) BuildRootMenu(List<IChannel> guildChannels, IGuild guild,
|
||||||
|
DbGuild guildConfig)
|
||||||
|
{
|
||||||
|
List<IEmbed> embeds =
|
||||||
|
[
|
||||||
|
new Embed(
|
||||||
|
Title: $"Log channels for {guild.Name}",
|
||||||
|
Description: "Press one of the buttons below to change the channel for that log type.",
|
||||||
|
Colour: DiscordUtils.Purple,
|
||||||
|
Fields: new[]
|
||||||
|
{
|
||||||
|
new EmbedField("Server changes", PrettyChannelString(guildConfig.Channels.GuildUpdate), true),
|
||||||
|
new EmbedField("Emoji changes", PrettyChannelString(guildConfig.Channels.GuildEmojisUpdate), true),
|
||||||
|
new EmbedField("New roles", PrettyChannelString(guildConfig.Channels.GuildRoleCreate), true),
|
||||||
|
new EmbedField("Edited roles", PrettyChannelString(guildConfig.Channels.GuildRoleUpdate), true),
|
||||||
|
new EmbedField("Deleted roles", PrettyChannelString(guildConfig.Channels.GuildRoleDelete), true),
|
||||||
|
|
||||||
|
new EmbedField("New channels", PrettyChannelString(guildConfig.Channels.ChannelCreate), true),
|
||||||
|
new EmbedField("Edited channels", PrettyChannelString(guildConfig.Channels.ChannelUpdate), true),
|
||||||
|
new EmbedField("Deleted channels", PrettyChannelString(guildConfig.Channels.ChannelDelete), true),
|
||||||
|
new EmbedField("Members joining", PrettyChannelString(guildConfig.Channels.GuildMemberAdd), true),
|
||||||
|
new EmbedField("Members leaving", PrettyChannelString(guildConfig.Channels.GuildMemberRemove),
|
||||||
|
true),
|
||||||
|
|
||||||
|
new EmbedField("Member role changes", PrettyChannelString(guildConfig.Channels.GuildMemberUpdate),
|
||||||
|
true),
|
||||||
|
new EmbedField("Key role changes", PrettyChannelString(guildConfig.Channels.GuildKeyRoleUpdate),
|
||||||
|
true),
|
||||||
|
new EmbedField("Member name changes",
|
||||||
|
PrettyChannelString(guildConfig.Channels.GuildMemberNickUpdate),
|
||||||
|
true),
|
||||||
|
new EmbedField("Member avatar changes",
|
||||||
|
PrettyChannelString(guildConfig.Channels.GuildMemberAvatarUpdate), true),
|
||||||
|
new EmbedField("Kicks", PrettyChannelString(guildConfig.Channels.GuildMemberKick), true),
|
||||||
|
|
||||||
|
new EmbedField("Bans", PrettyChannelString(guildConfig.Channels.GuildBanAdd), true),
|
||||||
|
new EmbedField("Unbans", PrettyChannelString(guildConfig.Channels.GuildBanRemove), true),
|
||||||
|
new EmbedField("New invites", PrettyChannelString(guildConfig.Channels.InviteCreate), true),
|
||||||
|
new EmbedField("Deleted invites", PrettyChannelString(guildConfig.Channels.InviteDelete), true),
|
||||||
|
new EmbedField("Edited messages", PrettyChannelString(guildConfig.Channels.MessageUpdate), true),
|
||||||
|
|
||||||
|
new EmbedField("Deleted messages", PrettyChannelString(guildConfig.Channels.MessageDelete), true),
|
||||||
|
new EmbedField("Bulk deleted messages", PrettyChannelString(guildConfig.Channels.MessageDeleteBulk),
|
||||||
|
true),
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
List<IMessageComponent> components =
|
||||||
|
[
|
||||||
|
new ActionRowComponent([
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Server changes",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Emoji changes",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildEmojisUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "New roles",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildRoleCreate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Edited roles",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildRoleUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Deleted roles",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildRoleDelete))),
|
||||||
|
]),
|
||||||
|
new ActionRowComponent([
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "New channels",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.ChannelCreate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Edited channels",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.ChannelUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Deleted channels",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.ChannelDelete))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Members joining",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberAdd))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Members leaving",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberRemove))),
|
||||||
|
]),
|
||||||
|
new ActionRowComponent([
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Member role changes",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Key role changes",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildKeyRoleUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Member name changes",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberNickUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Members avatar changes",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberAvatarUpdate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Kicks",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberKick))),
|
||||||
|
]),
|
||||||
|
new ActionRowComponent([
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Bans",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildBanAdd))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Unbans",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.GuildBanRemove))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "New invites",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.InviteCreate))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Deleted invites",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.InviteDelete))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Edited messages",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.MessageUpdate))),
|
||||||
|
]),
|
||||||
|
new ActionRowComponent([
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Deleted messages",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.MessageDelete))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Primary, Label: "Bulk deleted messages",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels",
|
||||||
|
nameof(LogChannelType.MessageDeleteBulk))),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Secondary, Label: "Close",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels", "close")),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (embeds, components);
|
||||||
|
|
||||||
|
string PrettyChannelString(ulong id)
|
||||||
|
{
|
||||||
|
if (id == 0) return "Not set";
|
||||||
|
if (guildChannels.All(c => c.ID != id)) return $"unknown channel {id}";
|
||||||
|
return $"<#{id}>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string PrettyLogTypeName(LogChannelType type) => type switch
|
||||||
|
{
|
||||||
|
LogChannelType.GuildUpdate => "Server changes",
|
||||||
|
LogChannelType.GuildEmojisUpdate => "Emoji changes",
|
||||||
|
LogChannelType.GuildRoleCreate => "New roles",
|
||||||
|
LogChannelType.GuildRoleUpdate => "Edited roles",
|
||||||
|
LogChannelType.GuildRoleDelete => "Deleted roles",
|
||||||
|
LogChannelType.ChannelCreate => "New channels",
|
||||||
|
LogChannelType.ChannelUpdate => "Edited channels",
|
||||||
|
LogChannelType.ChannelDelete => "Deleted channels",
|
||||||
|
LogChannelType.GuildMemberAdd => "Members joining",
|
||||||
|
LogChannelType.GuildMemberUpdate => "Members leaving",
|
||||||
|
LogChannelType.GuildKeyRoleUpdate => "Key role changes",
|
||||||
|
LogChannelType.GuildMemberNickUpdate => "Member name changes",
|
||||||
|
LogChannelType.GuildMemberAvatarUpdate => "Member avatar changes",
|
||||||
|
LogChannelType.GuildMemberRemove => "Members leaving",
|
||||||
|
LogChannelType.GuildMemberKick => "Kicks",
|
||||||
|
LogChannelType.GuildBanAdd => "Bans",
|
||||||
|
LogChannelType.GuildBanRemove => "Unbans",
|
||||||
|
LogChannelType.InviteCreate => "New invites",
|
||||||
|
LogChannelType.InviteDelete => "Deleted invites",
|
||||||
|
LogChannelType.MessageUpdate => "Edited messages",
|
||||||
|
LogChannelType.MessageDelete => "Deleted messages",
|
||||||
|
LogChannelType.MessageDeleteBulk => "Bulk deleted messages",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Invalid LogChannelType value")
|
||||||
|
};
|
||||||
|
}
|
||||||
322
Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs
Normal file
322
Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
using Catalogger.Backend.Cache;
|
||||||
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Queries;
|
||||||
|
using Catalogger.Backend.Extensions;
|
||||||
|
using Catalogger.Backend.Services;
|
||||||
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
|
using Remora.Discord.API.Objects;
|
||||||
|
using Remora.Discord.Commands.Attributes;
|
||||||
|
using Remora.Discord.Commands.Contexts;
|
||||||
|
using Remora.Discord.Commands.Extensions;
|
||||||
|
using Remora.Discord.Commands.Feedback.Messages;
|
||||||
|
using Remora.Discord.Commands.Feedback.Services;
|
||||||
|
using Remora.Discord.Commands.Services;
|
||||||
|
using Remora.Discord.Interactivity;
|
||||||
|
using Remora.Discord.Interactivity.Services;
|
||||||
|
using Remora.Rest.Core;
|
||||||
|
using Remora.Results;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Bot.Commands;
|
||||||
|
|
||||||
|
public class ChannelCommandsComponents(
|
||||||
|
ILogger logger,
|
||||||
|
DatabaseContext db,
|
||||||
|
GuildCacheService guildCache,
|
||||||
|
ChannelCacheService channelCache,
|
||||||
|
ContextInjectionService contextInjection,
|
||||||
|
IFeedbackService feedbackService,
|
||||||
|
IDiscordRestInteractionAPI interactionApi,
|
||||||
|
InMemoryDataService<Snowflake, ChannelCommandData> dataService) : InteractionGroup
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = logger.ForContext<ChannelCommandsComponents>();
|
||||||
|
|
||||||
|
[Button("config-channels")]
|
||||||
|
[SuppressInteractionResponse(true)]
|
||||||
|
public async Task<Result> OnButtonPressedAsync(string state)
|
||||||
|
{
|
||||||
|
if (contextInjection.Context is not IInteractionCommandContext ctx) throw new CataloggerError("No context");
|
||||||
|
if (!ctx.TryGetUserID(out var userId)) throw new CataloggerError("No user ID in context");
|
||||||
|
if (!ctx.Interaction.Message.TryGet(out var msg)) throw new CataloggerError("No message ID in context");
|
||||||
|
if (!ctx.TryGetGuildID(out var guildId)) throw new CataloggerError("No guild ID in context");
|
||||||
|
if (!guildCache.TryGet(guildId, out var guild)) throw new CataloggerError("Guild not in cache");
|
||||||
|
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||||
|
var guildConfig = await db.GetGuildAsync(guildId);
|
||||||
|
|
||||||
|
var result = await dataService.LeaseDataAsync(msg.ID);
|
||||||
|
await using var lease = result.GetOrThrow();
|
||||||
|
if (lease.Data.UserId != userId)
|
||||||
|
{
|
||||||
|
return (Result)await feedbackService.SendContextualAsync("This is not your configuration menu.",
|
||||||
|
options: new FeedbackMessageOptions(MessageFlags: MessageFlags.Ephemeral));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case "close":
|
||||||
|
return await interactionApi.UpdateMessageAsync(ctx.Interaction,
|
||||||
|
new InteractionMessageCallbackData(Components: Array.Empty<IMessageComponent>()));
|
||||||
|
case "reset":
|
||||||
|
if (lease.Data.CurrentPage == null)
|
||||||
|
throw new CataloggerError("CurrentPage was null in reset button callback");
|
||||||
|
if (!Enum.TryParse<LogChannelType>(lease.Data.CurrentPage, out var channelType))
|
||||||
|
throw new CataloggerError($"Invalid config-channels CurrentPage: '{lease.Data.CurrentPage}'");
|
||||||
|
|
||||||
|
// TODO: figure out some way to make this less verbose?
|
||||||
|
switch (channelType)
|
||||||
|
{
|
||||||
|
case LogChannelType.GuildUpdate:
|
||||||
|
guildConfig.Channels.GuildUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildEmojisUpdate:
|
||||||
|
guildConfig.Channels.GuildEmojisUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildRoleCreate:
|
||||||
|
guildConfig.Channels.GuildRoleCreate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildRoleUpdate:
|
||||||
|
guildConfig.Channels.GuildRoleUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildRoleDelete:
|
||||||
|
guildConfig.Channels.GuildRoleDelete = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.ChannelCreate:
|
||||||
|
guildConfig.Channels.ChannelCreate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.ChannelUpdate:
|
||||||
|
guildConfig.Channels.ChannelUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.ChannelDelete:
|
||||||
|
guildConfig.Channels.ChannelDelete = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberAdd:
|
||||||
|
guildConfig.Channels.GuildMemberAdd = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberUpdate:
|
||||||
|
guildConfig.Channels.GuildMemberUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildKeyRoleUpdate:
|
||||||
|
guildConfig.Channels.GuildKeyRoleUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberNickUpdate:
|
||||||
|
guildConfig.Channels.GuildMemberNickUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberAvatarUpdate:
|
||||||
|
guildConfig.Channels.GuildMemberAvatarUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberRemove:
|
||||||
|
guildConfig.Channels.GuildMemberRemove = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberKick:
|
||||||
|
guildConfig.Channels.GuildMemberKick = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildBanAdd:
|
||||||
|
guildConfig.Channels.GuildBanAdd = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildBanRemove:
|
||||||
|
guildConfig.Channels.GuildBanRemove = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.InviteCreate:
|
||||||
|
guildConfig.Channels.InviteCreate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.InviteDelete:
|
||||||
|
guildConfig.Channels.InviteDelete = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.MessageUpdate:
|
||||||
|
guildConfig.Channels.MessageUpdate = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.MessageDelete:
|
||||||
|
guildConfig.Channels.MessageDelete = 0;
|
||||||
|
break;
|
||||||
|
case LogChannelType.MessageDeleteBulk:
|
||||||
|
guildConfig.Channels.MessageDeleteBulk = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Update(guildConfig);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
goto case "return";
|
||||||
|
case "return":
|
||||||
|
var (e, c) = ChannelCommands.BuildRootMenu(guildChannels, guild, guildConfig);
|
||||||
|
await interactionApi.UpdateMessageAsync(ctx.Interaction,
|
||||||
|
new InteractionMessageCallbackData(Embeds: e, Components: c));
|
||||||
|
lease.Data = new ChannelCommandData(userId, CurrentPage: null);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Enum.TryParse<LogChannelType>(state, out var logChannelType))
|
||||||
|
throw new CataloggerError($"Invalid config-channels state {state}");
|
||||||
|
|
||||||
|
var channelId = WebhookExecutorService.GetDefaultLogChannel(guildConfig, logChannelType);
|
||||||
|
string? channelMention;
|
||||||
|
if (channelId is null or 0) channelMention = null;
|
||||||
|
else if (guildChannels.All(c => c.ID != channelId.Value)) channelMention = $"unknown channel {channelId}";
|
||||||
|
else channelMention = $"<#{channelId}>";
|
||||||
|
|
||||||
|
List<IEmbed> embeds =
|
||||||
|
[
|
||||||
|
new Embed(
|
||||||
|
Title: ChannelCommands.PrettyLogTypeName(logChannelType),
|
||||||
|
Description: channelMention == null
|
||||||
|
? "This event is not currently logged.\nTo start logging it somewhere, select a channel below."
|
||||||
|
: $"This event is currently set to log to {channelMention}." +
|
||||||
|
"\nTo change where it is logged, select a channel below." +
|
||||||
|
"\nTo disable logging this event entirely, select \"Stop logging\" below.",
|
||||||
|
Colour: DiscordUtils.Purple)
|
||||||
|
];
|
||||||
|
|
||||||
|
List<IMessageComponent> components =
|
||||||
|
[
|
||||||
|
new ActionRowComponent(new[]
|
||||||
|
{
|
||||||
|
new ChannelSelectComponent(CustomID: CustomIDHelpers.CreateSelectMenuID("config-channels"),
|
||||||
|
ChannelTypes: new[] { ChannelType.GuildText })
|
||||||
|
}),
|
||||||
|
new ActionRowComponent(new[]
|
||||||
|
{
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Danger, Label: "Stop logging",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels", "reset"),
|
||||||
|
IsDisabled: channelMention == null),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Secondary, Label: "Return to menu",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels", "return"))
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
lease.Data = new ChannelCommandData(userId, CurrentPage: state);
|
||||||
|
return await interactionApi.UpdateMessageAsync(ctx.Interaction,
|
||||||
|
new InteractionMessageCallbackData(Embeds: embeds, Components: components));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SelectMenu("config-channels")]
|
||||||
|
[SuppressInteractionResponse(true)]
|
||||||
|
public async Task<Result> OnMenuSelectionAsync(IReadOnlyList<IPartialChannel> channels)
|
||||||
|
{
|
||||||
|
if (contextInjection.Context is not IInteractionCommandContext ctx) throw new CataloggerError("No context");
|
||||||
|
if (!ctx.TryGetUserID(out var userId)) throw new CataloggerError("No user ID in context");
|
||||||
|
if (!ctx.Interaction.Message.TryGet(out var msg)) throw new CataloggerError("No message ID in context");
|
||||||
|
if (!ctx.TryGetGuildID(out var guildId)) throw new CataloggerError("No guild ID in context");
|
||||||
|
if (!guildCache.TryGet(guildId, out var guild)) throw new CataloggerError("Guild not in cache");
|
||||||
|
var guildConfig = await db.GetGuildAsync(guildId);
|
||||||
|
var channelId = channels[0].ID.ToUlong();
|
||||||
|
|
||||||
|
var result = await dataService.LeaseDataAsync(msg.ID);
|
||||||
|
await using var lease = result.GetOrThrow();
|
||||||
|
if (lease.Data.UserId != userId)
|
||||||
|
{
|
||||||
|
return (Result)await feedbackService.SendContextualAsync("This is not your configuration menu.",
|
||||||
|
options: new FeedbackMessageOptions(MessageFlags: MessageFlags.Ephemeral));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Enum.TryParse<LogChannelType>(lease.Data.CurrentPage, out var channelType))
|
||||||
|
throw new CataloggerError($"Invalid config-channels CurrentPage '{lease.Data.CurrentPage}'");
|
||||||
|
|
||||||
|
switch (channelType)
|
||||||
|
{
|
||||||
|
case LogChannelType.GuildUpdate:
|
||||||
|
guildConfig.Channels.GuildUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildEmojisUpdate:
|
||||||
|
guildConfig.Channels.GuildEmojisUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildRoleCreate:
|
||||||
|
guildConfig.Channels.GuildRoleCreate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildRoleUpdate:
|
||||||
|
guildConfig.Channels.GuildRoleUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildRoleDelete:
|
||||||
|
guildConfig.Channels.GuildRoleDelete = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.ChannelCreate:
|
||||||
|
guildConfig.Channels.ChannelCreate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.ChannelUpdate:
|
||||||
|
guildConfig.Channels.ChannelUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.ChannelDelete:
|
||||||
|
guildConfig.Channels.ChannelDelete = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberAdd:
|
||||||
|
guildConfig.Channels.GuildMemberAdd = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberUpdate:
|
||||||
|
guildConfig.Channels.GuildMemberUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildKeyRoleUpdate:
|
||||||
|
guildConfig.Channels.GuildKeyRoleUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberNickUpdate:
|
||||||
|
guildConfig.Channels.GuildMemberNickUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberAvatarUpdate:
|
||||||
|
guildConfig.Channels.GuildMemberAvatarUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberRemove:
|
||||||
|
guildConfig.Channels.GuildMemberRemove = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildMemberKick:
|
||||||
|
guildConfig.Channels.GuildMemberKick = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildBanAdd:
|
||||||
|
guildConfig.Channels.GuildBanAdd = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.GuildBanRemove:
|
||||||
|
guildConfig.Channels.GuildBanRemove = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.InviteCreate:
|
||||||
|
guildConfig.Channels.InviteCreate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.InviteDelete:
|
||||||
|
guildConfig.Channels.InviteDelete = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.MessageUpdate:
|
||||||
|
guildConfig.Channels.MessageUpdate = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.MessageDelete:
|
||||||
|
guildConfig.Channels.MessageDelete = channelId;
|
||||||
|
break;
|
||||||
|
case LogChannelType.MessageDeleteBulk:
|
||||||
|
guildConfig.Channels.MessageDeleteBulk = channelId;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Update(guildConfig);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
List<IEmbed> embeds =
|
||||||
|
[
|
||||||
|
new Embed(
|
||||||
|
Title: ChannelCommands.PrettyLogTypeName(channelType),
|
||||||
|
Description: $"This event is currently set to log to <#{channelId}>." +
|
||||||
|
"\nTo change where it is logged, select a channel below." +
|
||||||
|
"\nTo disable logging this event entirely, select \"Stop logging\" below.",
|
||||||
|
Colour: DiscordUtils.Purple)
|
||||||
|
];
|
||||||
|
|
||||||
|
List<IMessageComponent> components =
|
||||||
|
[
|
||||||
|
new ActionRowComponent(new[]
|
||||||
|
{
|
||||||
|
new ChannelSelectComponent(CustomID: CustomIDHelpers.CreateSelectMenuID("config-channels"),
|
||||||
|
ChannelTypes: new[] { ChannelType.GuildText })
|
||||||
|
}),
|
||||||
|
new ActionRowComponent(new[]
|
||||||
|
{
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Danger, Label: "Stop logging",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels", "reset")),
|
||||||
|
new ButtonComponent(ButtonComponentStyle.Secondary, Label: "Return to menu",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState("config-channels", "return"))
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
lease.Data = lease.Data with { UserId = userId };
|
||||||
|
return await interactionApi.UpdateMessageAsync(ctx.Interaction,
|
||||||
|
new InteractionMessageCallbackData(Embeds: embeds, Components: components));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ChannelCommandData(Snowflake UserId, string? CurrentPage);
|
||||||
|
|
@ -34,27 +34,27 @@ public class Guild
|
||||||
public Dictionary<ulong, List<ulong>> IgnoredUsersPerChannel { get; init; } = [];
|
public Dictionary<ulong, List<ulong>> IgnoredUsersPerChannel { get; init; } = [];
|
||||||
public Dictionary<ulong, ulong> Redirects { get; init; } = [];
|
public Dictionary<ulong, ulong> Redirects { get; init; } = [];
|
||||||
|
|
||||||
public ulong GuildUpdate { get; init; }
|
public ulong GuildUpdate { get; set; }
|
||||||
public ulong GuildEmojisUpdate { get; init; }
|
public ulong GuildEmojisUpdate { get; set; }
|
||||||
public ulong GuildRoleCreate { get; init; }
|
public ulong GuildRoleCreate { get; set; }
|
||||||
public ulong GuildRoleUpdate { get; init; }
|
public ulong GuildRoleUpdate { get; set; }
|
||||||
public ulong GuildRoleDelete { get; init; }
|
public ulong GuildRoleDelete { get; set; }
|
||||||
public ulong ChannelCreate { get; init; }
|
public ulong ChannelCreate { get; set; }
|
||||||
public ulong ChannelUpdate { get; init; }
|
public ulong ChannelUpdate { get; set; }
|
||||||
public ulong ChannelDelete { get; init; }
|
public ulong ChannelDelete { get; set; }
|
||||||
public ulong GuildMemberAdd { get; init; }
|
public ulong GuildMemberAdd { get; set; }
|
||||||
public ulong GuildMemberUpdate { get; init; }
|
public ulong GuildMemberUpdate { get; set; }
|
||||||
public ulong GuildKeyRoleUpdate { get; init; }
|
public ulong GuildKeyRoleUpdate { get; set; }
|
||||||
public ulong GuildMemberNickUpdate { get; init; }
|
public ulong GuildMemberNickUpdate { get; set; }
|
||||||
public ulong GuildMemberAvatarUpdate { get; init; }
|
public ulong GuildMemberAvatarUpdate { get; set; }
|
||||||
public ulong GuildMemberRemove { get; init; }
|
public ulong GuildMemberRemove { get; set; }
|
||||||
public ulong GuildMemberKick { get; init; }
|
public ulong GuildMemberKick { get; set; }
|
||||||
public ulong GuildBanAdd { get; init; }
|
public ulong GuildBanAdd { get; set; }
|
||||||
public ulong GuildBanRemove { get; init; }
|
public ulong GuildBanRemove { get; set; }
|
||||||
public ulong InviteCreate { get; init; }
|
public ulong InviteCreate { get; set; }
|
||||||
public ulong InviteDelete { get; init; }
|
public ulong InviteDelete { get; set; }
|
||||||
public ulong MessageUpdate { get; init; }
|
public ulong MessageUpdate { get; set; }
|
||||||
public ulong MessageDelete { get; init; }
|
public ulong MessageDelete { get; set; }
|
||||||
public ulong MessageDeleteBulk { get; init; }
|
public ulong MessageDeleteBulk { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
using OneOf;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
|
using Remora.Discord.API.Objects;
|
||||||
using Remora.Rest.Core;
|
using Remora.Rest.Core;
|
||||||
using Remora.Results;
|
using Remora.Results;
|
||||||
|
|
||||||
|
|
@ -49,5 +52,13 @@ public static class DiscordExtensions
|
||||||
|
|
||||||
public static async Task<T> GetOrThrow<T>(this Task<Result<T>> result) => (await result).GetOrThrow();
|
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 class DiscordRestException(string message) : Exception(message);
|
public class DiscordRestException(string message) : Exception(message);
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Catalogger.Backend.Bot.Commands;
|
||||||
using Catalogger.Backend.Bot.Responders;
|
using Catalogger.Backend.Bot.Responders;
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
|
@ -8,6 +9,8 @@ using NodaTime;
|
||||||
using Remora.Discord.API;
|
using Remora.Discord.API;
|
||||||
using Remora.Discord.API.Abstractions.Rest;
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
using Remora.Discord.Commands.Services;
|
using Remora.Discord.Commands.Services;
|
||||||
|
using Remora.Discord.Interactivity.Services;
|
||||||
|
using Remora.Rest.Core;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
|
|
@ -71,7 +74,8 @@ public static class StartupExtensions
|
||||||
.AddScoped<IEncryptionService, EncryptionService>()
|
.AddScoped<IEncryptionService, EncryptionService>()
|
||||||
.AddScoped<MessageRepository>()
|
.AddScoped<MessageRepository>()
|
||||||
.AddSingleton<WebhookExecutorService>()
|
.AddSingleton<WebhookExecutorService>()
|
||||||
.AddSingleton<PkMessageHandler>();
|
.AddSingleton<PkMessageHandler>()
|
||||||
|
.AddSingleton(InMemoryDataService<Snowflake, ChannelCommandData>.Instance);
|
||||||
|
|
||||||
public static async Task Initialize(this WebApplication app)
|
public static async Task Initialize(this WebApplication app)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,16 @@ builder.Host
|
||||||
GatewayIntents.GuildWebhooks |
|
GatewayIntents.GuildWebhooks |
|
||||||
GatewayIntents.MessageContents |
|
GatewayIntents.MessageContents |
|
||||||
GatewayIntents.GuildEmojisAndStickers)
|
GatewayIntents.GuildEmojisAndStickers)
|
||||||
.AddDiscordCommands(enableSlash: true)
|
.AddDiscordCommands(enableSlash: true, useDefaultCommandResponder: false)
|
||||||
.AddCommandTree()
|
.AddCommandTree()
|
||||||
// Start command tree
|
// Start command tree
|
||||||
.WithCommandGroup<MetaCommands>()
|
.WithCommandGroup<MetaCommands>()
|
||||||
|
.WithCommandGroup<ChannelCommands>()
|
||||||
// End command tree
|
// End command tree
|
||||||
.Finish()
|
.Finish()
|
||||||
.AddPagination()
|
.AddPagination()
|
||||||
.AddInteractivity()
|
.AddInteractivity()
|
||||||
|
.AddInteractionGroup<ChannelCommandsComponents>()
|
||||||
);
|
);
|
||||||
|
|
||||||
builder.Services
|
builder.Services
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ public class WebhookExecutorService(
|
||||||
return GetDefaultLogChannel(guild, logChannelType);
|
return GetDefaultLogChannel(guild, logChannelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ulong? GetDefaultLogChannel(Guild guild, LogChannelType channelType) => channelType switch
|
public static ulong? GetDefaultLogChannel(Guild guild, LogChannelType channelType) => channelType switch
|
||||||
{
|
{
|
||||||
LogChannelType.GuildUpdate => guild.Channels.GuildUpdate,
|
LogChannelType.GuildUpdate => guild.Channels.GuildUpdate,
|
||||||
LogChannelType.GuildEmojisUpdate => guild.Channels.GuildEmojisUpdate,
|
LogChannelType.GuildEmojisUpdate => guild.Channels.GuildEmojisUpdate,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue