Catalogger.NET/Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs

322 lines
15 KiB
C#
Raw Normal View History

2024-08-14 16:05:43 +02:00
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);