diff --git a/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts b/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts index aa76140a..f7fc72ea 100644 --- a/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts +++ b/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts @@ -10,6 +10,7 @@ import { onButtonInteraction } from "./events/buttonInteraction"; import { pluginInfo } from "./info"; import { createButtonComponents } from "./functions/createButtonComponents"; import { TooManyComponentsError } from "./functions/TooManyComponentsError"; +import { resetButtonsCmd } from "./commands/resetButtons"; export const RoleButtonsPlugin = zeppelinGuildPlugin()({ name: "role_buttons", @@ -17,6 +18,21 @@ export const RoleButtonsPlugin = zeppelinGuildPlugin()({ info: pluginInfo, showInDocs: true, + defaultOptions: { + config: { + buttons: {}, + can_reset: false, + }, + overrides: [ + { + level: ">=100", + config: { + can_reset: true, + }, + }, + ], + }, + configPreprocessor(options) { // Auto-fill "name" property for buttons based on the object key const buttonsArray = Array.isArray(options.config?.buttons) ? options.config.buttons : []; @@ -58,6 +74,8 @@ export const RoleButtonsPlugin = zeppelinGuildPlugin()({ events: [onButtonInteraction], + commands: [resetButtonsCmd], + beforeLoad(pluginData) { pluginData.state.roleButtons = GuildRoleButtons.getGuildInstance(pluginData.guild.id); }, diff --git a/backend/src/plugins/RoleButtons/commands/resetButtons.ts b/backend/src/plugins/RoleButtons/commands/resetButtons.ts new file mode 100644 index 00000000..0445e5b3 --- /dev/null +++ b/backend/src/plugins/RoleButtons/commands/resetButtons.ts @@ -0,0 +1,27 @@ +import { typedGuildCommand } from "knub"; +import { RoleButtonsPluginType } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { applyAllRoleButtons } from "../functions/applyAllRoleButtons"; + +export const resetButtonsCmd = typedGuildCommand()({ + trigger: "role_buttons reset", + description: + "In case of issues, you can run this command to have Zeppelin 'forget' about specific role buttons and re-apply them. This will also repost the message, if not targeting an existing message.", + usage: "!role_buttons reset my_roles", + permission: "can_reset", + signature: { + name: ct.string(), + }, + async run({ pluginData, args, message }) { + const config = pluginData.config.get(); + if (!config.buttons[args.name]) { + sendErrorMessage(pluginData, message.channel, `Can't find role buttons with the name "${args.name}"`); + return; + } + + await pluginData.state.roleButtons.deleteRoleButtonItem(args.name); + await applyAllRoleButtons(pluginData); + sendSuccessMessage(pluginData, message.channel, "Done!"); + }, +}); diff --git a/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts b/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts index b171bc2b..b3fc76f4 100644 --- a/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts +++ b/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts @@ -20,7 +20,7 @@ export async function applyRoleButtons( if (existingSavedButtons?.channel_id) { const existingChannel = await pluginData.guild.channels.fetch(configItem.message.channel_id); const existingMessage = await (existingChannel?.isText() && - existingChannel.messages.fetch(existingSavedButtons.message_id)); + existingChannel.messages.fetch(existingSavedButtons.message_id).catch(() => null)); if (existingMessage && existingMessage.components.length) { await existingMessage.edit({ components: [], @@ -32,7 +32,8 @@ export async function applyRoleButtons( if ("message_id" in configItem.message) { // channel id + message id: apply role buttons to existing message const channel = await pluginData.guild.channels.fetch(configItem.message.channel_id); - const messageCandidate = await (channel?.isText() && channel.messages.fetch(configItem.message.message_id)); + const messageCandidate = await (channel?.isText() && + channel.messages.fetch(configItem.message.message_id).catch(() => null)); if (!messageCandidate) { pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Message not found for role_buttons/${configItem.name}`, @@ -87,7 +88,7 @@ export async function applyRoleButtons( candidateMessage = await channel.send(configItem.message.content as string | MessageOptions); } catch (err) { pluginData.getPlugin(LogsPlugin).logBotAlert({ - body: `Error while posting message for role_buttons/${configItem.name}`, + body: `Error while posting message for role_buttons/${configItem.name}: ${String(err)}`, }); return null; } @@ -98,14 +99,19 @@ export async function applyRoleButtons( if (message.author.id !== pluginData.client.user?.id) { pluginData.getPlugin(LogsPlugin).logBotAlert({ - body: `Error applying role buttons for role_buttons/${configItem.name}: target message must be posted by the bot`, + body: `Error applying role buttons for role_buttons/${configItem.name}: target message must be posted by Zeppelin`, }); return null; } // Apply role buttons const components = createButtonComponents(configItem); - await message.edit({ components }); + await message.edit({ components }).catch((err) => { + pluginData.getPlugin(LogsPlugin).logBotAlert({ + body: `Error applying role buttons for role_buttons/${configItem.name}: ${String(err)}`, + }); + return null; + }); return { channel_id: message.channelId, diff --git a/backend/src/plugins/RoleButtons/info.ts b/backend/src/plugins/RoleButtons/info.ts index 7b0976a5..6f64806e 100644 --- a/backend/src/plugins/RoleButtons/info.ts +++ b/backend/src/plugins/RoleButtons/info.ts @@ -15,7 +15,7 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = { config: buttons: my_roles: # You can use any name you want here, but make sure not to change it afterwards - messages: + message: channel_id: "967407495544983552" content: "Click the reactions below to get roles! Click again to remove the role." options: @@ -37,7 +37,7 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = { config: buttons: my_roles: - messages: + message: channel_id: "967407495544983552" content: embeds: @@ -55,7 +55,7 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = { config: buttons: my_roles: - messages: + message: channel_id: "967407495544983552" message_id: "967407554412040193" options: @@ -69,7 +69,7 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = { config: buttons: my_roles: - messages: + message: channel_id: "967407495544983552" message_id: "967407554412040193" exclusive: true # With this option set, only one role can be selected at a time diff --git a/backend/src/plugins/RoleButtons/types.ts b/backend/src/plugins/RoleButtons/types.ts index 2b23b692..e7b4096d 100644 --- a/backend/src/plugins/RoleButtons/types.ts +++ b/backend/src/plugins/RoleButtons/types.ts @@ -3,19 +3,20 @@ import { BasePluginType } from "knub"; import { tMessageContent, tNullable } from "../../utils"; import { GuildRoleButtons } from "../../data/GuildRoleButtons"; -enum ButtonStyles { - PRIMARY = 1, - SECONDARY = 2, - SUCCESS = 3, - DANGER = 4, - // LINK = 5, We do not want users to create link buttons, but it would be style 5 -} - const RoleButtonOption = t.type({ role_id: t.string, label: tNullable(t.string), emoji: tNullable(t.string), - style: tNullable(t.keyof(ButtonStyles)), // https://discord.js.org/#/docs/discord.js/v13/typedef/MessageButtonStyle + // https://discord.js.org/#/docs/discord.js/v13/typedef/MessageButtonStyle + style: tNullable( + t.union([ + t.literal("PRIMARY"), + t.literal("SECONDARY"), + t.literal("SUCCESS"), + t.literal("DANGER"), + // t.literal("LINK"), // Role buttons don't use link buttons, but adding this here so it's documented why it's not available + ]), + ), start_new_row: tNullable(t.boolean), }); export type TRoleButtonOption = t.TypeOf; @@ -39,6 +40,7 @@ export type TRoleButtonsConfigItem = t.TypeOf; export const ConfigSchema = t.type({ buttons: t.record(t.string, RoleButtonsConfigItem), + can_reset: t.boolean, }); export type TConfigSchema = t.TypeOf;