using System.ComponentModel; using Catalogger.Backend.Cache.InMemoryCache; 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, GuildCache guildCache, ChannelCache channelCache, IFeedbackService feedbackService, ContextInjectionService contextInjection, InMemoryDataService dataService) : CommandGroup { private readonly ILogger _logger = logger.ForContext(); [Command("configure-channels")] [Description("Configure log channels for this server.")] [DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)] public async Task 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, List) BuildRootMenu(List guildChannels, IGuild guild, DbGuild guildConfig) { List 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 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") }; }