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() { var (userId, guildId) = contextInjection.GetUserAndGuild(); 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" ), }; }