// Button-based self-assignable role menus. customId is `rolemenu:` — // the message's own id (not known until after it's sent, so it can't be // embedded in the customId itself) is instead used to look up the tracked // role_menus row and confirm the clicked roleId is really part of that // menu's mapping, so a stale/foreign button can't toggle an untracked role. const roleMenus = require('../model/roleMenus') const createLogger = require('../utils/logger') const log = createLogger('rolemenu') const PREFIX = 'rolemenu:' // Returns true if this handler owned the interaction (caller should stop // looking for another handler), false if it's not a role-menu button at all. async function handleInteraction(interaction) { if (!interaction.isButton() || !interaction.customId.startsWith(PREFIX)) return false const roleId = interaction.customId.slice(PREFIX.length) try { const menu = await roleMenus.getByMessageId(interaction.message.id) if (!menu || !menu.mapping.some((m) => m.roleId === roleId)) { await interaction.reply({ content: 'This role menu is no longer valid.', ephemeral: true }) return true } const member = interaction.member if (member.roles.cache.has(roleId)) { await member.roles.remove(roleId) await interaction.reply({ content: `Removed <@&${roleId}>.`, ephemeral: true }) } else { await member.roles.add(roleId) await interaction.reply({ content: `Added <@&${roleId}>.`, ephemeral: true }) } } catch (err) { log.error('role menu toggle failed', { message: err.message }) await interaction.reply({ content: 'Something went wrong toggling that role.', ephemeral: true }).catch(() => {}) } return true } module.exports = { handleInteraction }