372 lines
No EOL
12 KiB
TypeScript
372 lines
No EOL
12 KiB
TypeScript
import {GroupConfigurationTransformers, TransformerType} from "./GroupConfigurationTransformers";
|
|
import {GroupConfigurationHandler} from "./GroupConfigurationHandler";
|
|
import {
|
|
ActionRowBuilder,
|
|
AnySelectMenuInteraction,
|
|
ButtonBuilder,
|
|
ButtonStyle, channelMention,
|
|
ChannelSelectMenuBuilder, ChannelType,
|
|
ChatInputCommandInteraction, EmbedBuilder, inlineCode, Interaction,
|
|
InteractionReplyOptions,
|
|
InteractionUpdateOptions, italic, MessageFlags,
|
|
StringSelectMenuBuilder,
|
|
StringSelectMenuOptionBuilder, UserSelectMenuBuilder
|
|
} from "discord.js";
|
|
import {Logger} from "log4js";
|
|
import {Container} from "../Container/Container";
|
|
import {Nullable} from "../types/Nullable";
|
|
import {
|
|
MentionableSelectMenuBuilder,
|
|
MessageActionRowComponentBuilder,
|
|
RoleSelectMenuBuilder
|
|
} from "@discordjs/builders";
|
|
import {ChannelId} from "../types/DiscordTypes";
|
|
import {IconCache} from "../Icons/IconCache";
|
|
|
|
type UIElementCollection = Record<string, UIElement>;
|
|
type UIElement = {
|
|
label: string,
|
|
key: string,
|
|
description: string,
|
|
childrenElements?: UIElementCollection,
|
|
isConfiguration?: true
|
|
}
|
|
|
|
export class GroupConfigurationRenderer {
|
|
private static MOVETO_COMMAND = 'moveto_';
|
|
private static SETVALUE_COMMAND = 'setvalue_';
|
|
private static MOVEBACK_COMMAND = 'back';
|
|
|
|
private breadcrumbs: string[] = [];
|
|
|
|
private static UI_ELEMENTS: UIElementCollection = {
|
|
channels: {
|
|
label: 'Channels',
|
|
key: 'channels',
|
|
description: "Provides settings to define in what channels the bot sends messages, when not directly interacting with it.",
|
|
childrenElements: {
|
|
newPlaydates: {
|
|
label: 'New Playdates',
|
|
key: 'newPlaydates',
|
|
description: "Sets the channel, where the group get notified when new Playdates are set.",
|
|
isConfiguration: true
|
|
},
|
|
playdateReminders: {
|
|
label: 'Playdate Reminders',
|
|
key: 'playdateReminders',
|
|
description: "Sets the channel, where the group gets reminded of upcoming playdates.",
|
|
isConfiguration: true
|
|
}
|
|
}
|
|
},
|
|
locale: {
|
|
label: "Locale",
|
|
key: 'locale',
|
|
description: "Provides locale to be used for this group. This mostly sets how the dates are displayed, but this can also be later used for translations.",
|
|
isConfiguration: true
|
|
},
|
|
permissions: {
|
|
label: "Permissions",
|
|
key: "permissions",
|
|
description: "Allows customization, how the members are allowed to interact with the data stored in the group.",
|
|
childrenElements: {
|
|
allowMemberManagingPlaydates: {
|
|
label: "Manage Playdates",
|
|
key: "allowMemberManagingPlaydates",
|
|
description: "Defines if the members are allowed to manage playdates like adding or deleting them.",
|
|
isConfiguration: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
constructor(
|
|
private readonly configurationHandler: GroupConfigurationHandler,
|
|
private readonly transformers: GroupConfigurationTransformers,
|
|
) {
|
|
}
|
|
|
|
public async setup(interaction: ChatInputCommandInteraction) {
|
|
let response = await interaction.reply(this.getReplyOptions());
|
|
let exit = false;
|
|
let eventResponse;
|
|
const filter = (i: Interaction) => i.user.id === interaction.user.id;
|
|
do {
|
|
|
|
if (eventResponse) {
|
|
response = await eventResponse.update(this.getReplyOptions());
|
|
}
|
|
|
|
try {
|
|
eventResponse = await response.resource?.message?.awaitMessageComponent({
|
|
dispose: true,
|
|
filter: filter,
|
|
time: 60_000
|
|
});
|
|
} catch (_: unknown) {
|
|
break;
|
|
}
|
|
|
|
if (!eventResponse || eventResponse.customId === 'exit') {
|
|
exit = true;
|
|
continue;
|
|
}
|
|
|
|
if (eventResponse.customId === GroupConfigurationRenderer.MOVEBACK_COMMAND) {
|
|
this.breadcrumbs.pop()
|
|
continue;
|
|
}
|
|
|
|
if (eventResponse.customId.startsWith(GroupConfigurationRenderer.MOVETO_COMMAND)) {
|
|
this.breadcrumbs.push(
|
|
eventResponse.customId.substring(GroupConfigurationRenderer.MOVETO_COMMAND.length)
|
|
)
|
|
continue;
|
|
}
|
|
|
|
if (eventResponse.customId.startsWith(GroupConfigurationRenderer.SETVALUE_COMMAND)) {
|
|
this.handleSelection(<AnySelectMenuInteraction>eventResponse);
|
|
continue;
|
|
}
|
|
|
|
} while (!exit);
|
|
|
|
if (eventResponse) {
|
|
try {
|
|
await eventResponse.update(
|
|
this.getReplyOptions()
|
|
);
|
|
} catch (_) {
|
|
|
|
}
|
|
await eventResponse.deleteReply();
|
|
|
|
return;
|
|
}
|
|
|
|
const message = response.resource?.message
|
|
if (!message) {
|
|
return;
|
|
}
|
|
|
|
if (message.deletable) {
|
|
await message.delete()
|
|
}
|
|
}
|
|
|
|
private getReplyOptions(): InteractionUpdateOptions & InteractionReplyOptions & { withResponse: true } {
|
|
const embed = this.createEmbed();
|
|
const icons = Container.get<IconCache>(IconCache.name);
|
|
embed.setAuthor({
|
|
name: "/ " + this.breadcrumbs.join(" / ")
|
|
});
|
|
|
|
const exitButton = new ButtonBuilder()
|
|
.setLabel("Exit")
|
|
.setStyle(ButtonStyle.Danger)
|
|
.setCustomId("exit")
|
|
.setEmoji(icons.get("door_open_solid_white") ?? '');
|
|
|
|
const actionrow = new ActionRowBuilder<ButtonBuilder>()
|
|
|
|
if (this.breadcrumbs.length > 0) {
|
|
const backButton = new ButtonBuilder()
|
|
.setLabel("Back")
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setCustomId(GroupConfigurationRenderer.MOVEBACK_COMMAND)
|
|
.setEmoji(icons.get("angle_left_solid") ?? '');
|
|
|
|
actionrow.addComponents(backButton)
|
|
}
|
|
actionrow.addComponents(exitButton)
|
|
|
|
return {
|
|
content: "",
|
|
embeds: [embed],
|
|
components: [...this.createActionRowBuildersForMenu(), actionrow],
|
|
withResponse: true,
|
|
flags: MessageFlags.Ephemeral
|
|
};
|
|
}
|
|
|
|
private createEmbed(): EmbedBuilder {
|
|
const {currentElement} = this.findCurrentUI();
|
|
|
|
if (currentElement === null) {
|
|
return new EmbedBuilder()
|
|
.setTitle("Group Configuration")
|
|
.setDescription("This UI allows you to change settings for your group.")
|
|
}
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(currentElement?.label ?? '')
|
|
.setDescription(currentElement?.description ?? '');
|
|
|
|
if (currentElement?.isConfiguration ?? false) {
|
|
embed.addFields(
|
|
{name: "Current Value", value: this.getCurrentValueAsUI(), inline: false}
|
|
)
|
|
}
|
|
|
|
return embed;
|
|
}
|
|
|
|
private getCurrentValueAsUI(): string {
|
|
const path = this.breadcrumbs.join(".");
|
|
const value = this.configurationHandler.getConfigurationByPath(path);
|
|
|
|
if (value === undefined) return italic("None");
|
|
|
|
const type = this.transformers.getTransformerType(path);
|
|
|
|
if (type === undefined) {
|
|
throw new Error("Could not find the type for " + path);
|
|
}
|
|
|
|
|
|
const displaynames = new Intl.DisplayNames(["en"], {type: "language"});
|
|
|
|
switch (type) {
|
|
case TransformerType.Locale:
|
|
if (!value) {
|
|
return inlineCode("Default");
|
|
}
|
|
return displaynames.of((<Intl.Locale>value)?.baseName) ?? "Unknown";
|
|
case TransformerType.Channel:
|
|
if (!value) {
|
|
return inlineCode("None");
|
|
}
|
|
return channelMention(<ChannelId>value);
|
|
case TransformerType.PermissionBoolean:
|
|
return value ? "Allowed" : "Disallowed"
|
|
|
|
default:
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
private createActionRowBuildersForMenu(): ActionRowBuilder<MessageActionRowComponentBuilder>[] {
|
|
const {currentCollection, currentElement} = this.findCurrentUI();
|
|
const icons = Container.get<IconCache>(IconCache.name);
|
|
|
|
if (currentElement?.isConfiguration ?? false) {
|
|
return [
|
|
new ActionRowBuilder<ChannelSelectMenuBuilder | MentionableSelectMenuBuilder | RoleSelectMenuBuilder | StringSelectMenuBuilder | UserSelectMenuBuilder>()
|
|
.addComponents(this.getSelectForBreadcrumbs())
|
|
]
|
|
}
|
|
|
|
return [
|
|
new ActionRowBuilder<ButtonBuilder>()
|
|
.setComponents(
|
|
...Object.values(currentCollection).map(elem => new ButtonBuilder()
|
|
.setLabel(` ${elem.label}`)
|
|
.setStyle(ButtonStyle.Primary)
|
|
.setCustomId(GroupConfigurationRenderer.MOVETO_COMMAND + elem.key)
|
|
.setEmoji(icons.get(elem.isConfiguration ? 'pen_solid' : "folder_solid") ?? '')
|
|
)
|
|
)
|
|
]
|
|
}
|
|
|
|
private getSelectForBreadcrumbs(): ChannelSelectMenuBuilder | MentionableSelectMenuBuilder | RoleSelectMenuBuilder | StringSelectMenuBuilder | UserSelectMenuBuilder {
|
|
const breadcrumbPath = this.breadcrumbs.join('.')
|
|
const transformerType = this.transformers.getTransformerType(breadcrumbPath);
|
|
if (transformerType === undefined) {
|
|
throw new Error(`Can not find transformer type for ${breadcrumbPath}`)
|
|
}
|
|
|
|
switch (transformerType) {
|
|
case TransformerType.Locale:
|
|
const options = [
|
|
'en-US',
|
|
'fr-FR',
|
|
'it-IT',
|
|
'de-DE'
|
|
]
|
|
const displaynames = new Intl.DisplayNames(["en"], {type: "language"});
|
|
return new StringSelectMenuBuilder()
|
|
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
|
.setOptions(
|
|
options.map(intl => new StringSelectMenuOptionBuilder()
|
|
.setLabel(displaynames.of(intl) ?? '')
|
|
.setValue(intl)
|
|
)
|
|
)
|
|
case TransformerType.Channel:
|
|
return new ChannelSelectMenuBuilder()
|
|
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
|
.setChannelTypes(ChannelType.GuildText)
|
|
.setPlaceholder("New Value");
|
|
case TransformerType.PermissionBoolean:
|
|
return new StringSelectMenuBuilder()
|
|
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
|
.setOptions(
|
|
[
|
|
{
|
|
label: "Allow",
|
|
value: "1"
|
|
},
|
|
{
|
|
label: "Disallow",
|
|
value: "0"
|
|
}
|
|
]
|
|
)
|
|
|
|
default:
|
|
return new StringSelectMenuBuilder()
|
|
.setCustomId("...")
|
|
.setOptions(
|
|
new StringSelectMenuOptionBuilder()
|
|
.setLabel("Nothing to see here")
|
|
.setValue("0")
|
|
)
|
|
}
|
|
}
|
|
|
|
private handleSelection(interaction: AnySelectMenuInteraction) {
|
|
const path = interaction.customId.substring(GroupConfigurationRenderer.SETVALUE_COMMAND.length);
|
|
|
|
const savingValue = this.getSaveValue(interaction, path);
|
|
Container.get<Logger>("logger").debug(`Saving '${savingValue}' to '${path}'`);
|
|
|
|
this.configurationHandler.saveConfiguration(path, savingValue);
|
|
}
|
|
|
|
private getSaveValue(interaction: AnySelectMenuInteraction, path: string): string {
|
|
const transformerType = this.transformers.getTransformerType(path);
|
|
if (transformerType === undefined || transformerType === null) {
|
|
throw new Error(`Can not find transformer type for ${path}`)
|
|
}
|
|
|
|
switch (transformerType) {
|
|
case TransformerType.Locale:
|
|
case TransformerType.Channel:
|
|
case TransformerType.PermissionBoolean:
|
|
return interaction.values.join('; ');
|
|
default:
|
|
throw new Error("Unhandled select menu");
|
|
}
|
|
}
|
|
|
|
private findCurrentUI(): { currentElement: Nullable<UIElement>, currentCollection: UIElementCollection } {
|
|
let currentCollection: UIElementCollection = GroupConfigurationRenderer.UI_ELEMENTS;
|
|
let currentElement: Nullable<UIElement> = null;
|
|
|
|
for (const breadcrumb of this.breadcrumbs) {
|
|
currentElement = currentCollection[breadcrumb];
|
|
|
|
if (currentElement.isConfiguration ?? false) {
|
|
break;
|
|
}
|
|
|
|
currentCollection = currentElement.childrenElements ?? {};
|
|
}
|
|
|
|
return {
|
|
currentElement,
|
|
currentCollection,
|
|
}
|
|
}
|
|
} |