// Copyright (C) 2021-present sam (starshines.gay) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using System.ComponentModel; using Catalogger.Backend.Cache; 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; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.Commands.Attributes; using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Commands.Services; using Remora.Discord.Extensions.Embeds; using Remora.Rest.Core; using IResult = Remora.Results.IResult; namespace Catalogger.Backend.Bot.Commands; [Group("ignored-channels")] [Description("Manage channels ignored for logging.")] [DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)] public class IgnoreChannelCommands( ILogger logger, DatabaseContext db, IMemberCache memberCache, GuildCache guildCache, ChannelCache channelCache, PermissionResolverService permissionResolver, ContextInjectionService contextInjection, FeedbackService feedbackService ) : CommandGroup { private readonly ILogger _logger = logger.ForContext(); [Command("add")] [Description("Add a channel to the list of ignored channels.")] public async Task AddIgnoredChannelAsync( [ChannelTypes( ChannelType.GuildCategory, ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.GuildForum, ChannelType.GuildMedia, ChannelType.GuildVoice, ChannelType.GuildStageVoice )] [Description("The channel to ignore")] IChannel channel ) { var (_, guildId) = contextInjection.GetUserAndGuild(); var guildConfig = await db.GetGuildAsync(guildId); if (guildConfig.Channels.IgnoredChannels.Contains(channel.ID.Value)) return await feedbackService.ReplyAsync( "That channel is already being ignored.", isEphemeral: true ); guildConfig.Channels.IgnoredChannels.Add(channel.ID.Value); db.Update(guildConfig); await db.SaveChangesAsync(); return await feedbackService.ReplyAsync( $"Successfully added {(channel.Type == ChannelType.GuildCategory ? channel.Name : $"<#{channel.ID}>")} to the list of ignored channels." ); } [Command("remove")] [Description("Remove a channel from the list of ignored channels.")] public async Task RemoveIgnoredChannelAsync( [Description("The channel to stop ignoring")] IChannel channel ) { var (_, guildId) = contextInjection.GetUserAndGuild(); var guildConfig = await db.GetGuildAsync(guildId); if (!guildConfig.Channels.IgnoredChannels.Contains(channel.ID.Value)) return await feedbackService.ReplyAsync( "That channel is already not ignored.", isEphemeral: true ); guildConfig.Channels.IgnoredChannels.Remove(channel.ID.Value); db.Update(guildConfig); await db.SaveChangesAsync(); return await feedbackService.ReplyAsync( $"Successfully removed {(channel.Type == ChannelType.GuildCategory ? channel.Name : $"<#{channel.ID}>")} from the list of ignored channels." ); } [Command("list")] [Description("List channels ignored for logging.")] public async Task ListIgnoredChannelsAsync() { 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 member = await memberCache.TryGetAsync(guildId, userId); if (member == null) throw new CataloggerError("Executing member not found"); var ignoredChannels = guildConfig .Channels.IgnoredChannels.Select(id => { var channel = guildChannels.FirstOrDefault(c => c.ID.Value == id); if (channel == null) return new IgnoredChannel(IgnoredChannelType.Unknown, DiscordSnowflake.New(id)); var type = channel.Type switch { ChannelType.GuildCategory => IgnoredChannelType.Category, _ => IgnoredChannelType.Base, }; return new IgnoredChannel( type, channel.ID, permissionResolver .GetChannelPermissions(guildId, member, channel) .HasPermission(DiscordPermission.ViewChannel) ); }) .ToList(); foreach (var ch in ignoredChannels) { _logger.Debug("Channel: {ChannelId}, type: {Type}", ch.Id, ch.Type); } var embed = new EmbedBuilder() .WithTitle($"Ignored channels in {guild.Name}") .WithColour(DiscordUtils.Purple); var nonVisibleCategories = ignoredChannels.Count(c => c is { Type: IgnoredChannelType.Category, CanSee: false } ); var visibleCategories = ignoredChannels .Where(c => c is { Type: IgnoredChannelType.Category, CanSee: true }) .ToList(); if (nonVisibleCategories != 0 || visibleCategories.Count != 0) { var value = string.Join("\n", visibleCategories.Select(c => $"<#{c.Id}>")); if (nonVisibleCategories != 0) value += $"\n\n{nonVisibleCategories} channel(s) are not shown as you do not have access to them."; embed.AddField("Categories", value); } var nonVisibleBase = ignoredChannels.Count(c => c is { Type: IgnoredChannelType.Base, CanSee: false } ); var visibleBase = ignoredChannels .Where(c => c is { Type: IgnoredChannelType.Base, CanSee: true }) .ToList(); if (nonVisibleBase != 0 || visibleBase.Count != 0) { var value = string.Join("\n", visibleBase.Select(c => $"<#{c.Id}>")); if (nonVisibleBase != 0) value += $"\n\n{nonVisibleBase} channel(s) are not shown as you do not have access to them."; embed.AddField("Channels", value); } var unknownChannels = string.Join( "\n", ignoredChannels .Where(c => c.Type == IgnoredChannelType.Unknown) .Select(c => $"{c.Id} <#{c.Id}>") ); if (!string.IsNullOrWhiteSpace(unknownChannels)) { embed.AddField("Unknown", unknownChannels); } return await feedbackService.ReplyAsync(embeds: [embed.Build().GetOrThrow()]); } private record struct IgnoredChannel(IgnoredChannelType Type, Snowflake Id, bool CanSee = true); private enum IgnoredChannelType { Unknown, Base, Category, } }