From efd5472eedda52f77d43ca7b7e51fbc65a9cfa76 Mon Sep 17 00:00:00 2001 From: Chris Ham <431647+greenham@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:49:01 -0700 Subject: [PATCH] Add smart contextual autocomplete for role management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add context-aware autocomplete for /role add and /role remove commands - /role add only shows roles user doesn't have that are self-assignable - /role remove only shows roles user currently has that are self-manageable - Filter autocomplete based on user input with 25 result limit - Convert role options from RoleOption to StringOption with autocomplete - Prevent users from seeing irrelevant role choices User Experience Improvements: - Smart role suggestions based on current user state - No more trying to add roles you already have - No more seeing roles you can't manage - Clean, focused autocomplete interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/commands/slash/role.js | 76 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/src/commands/slash/role.js b/src/commands/slash/role.js index d50f816..7473265 100644 --- a/src/commands/slash/role.js +++ b/src/commands/slash/role.js @@ -9,20 +9,22 @@ module.exports = { subcommand .setName('add') .setDescription('Add a role to yourself') - .addRoleOption(option => + .addStringOption(option => option.setName('role') .setDescription('The role to add') .setRequired(true) + .setAutocomplete(true) ) ) .addSubcommand(subcommand => subcommand .setName('remove') .setDescription('Remove a role from yourself') - .addRoleOption(option => + .addStringOption(option => option.setName('role') .setDescription('The role to remove') .setRequired(true) + .setAutocomplete(true) ) ) .addSubcommand(subcommand => @@ -91,7 +93,19 @@ module.exports = { } // Handle add/remove subcommands - const targetRole = interaction.options.getRole('role'); + const roleName = interaction.options.getString('role'); + + // Find the role by name + const targetRole = interaction.guild.roles.cache.find(r => + r.name.toLowerCase() === roleName.toLowerCase() + ); + + if (!targetRole) { + return interaction.reply({ + content: `❌ Role **${roleName}** not found on this server.`, + flags: [MessageFlags.Ephemeral] + }); + } // Check if the role is in the allowed list if (!allowedRoleIds.includes(targetRole.id)) { @@ -178,5 +192,61 @@ module.exports = { flags: [MessageFlags.Ephemeral] }); } + }, + + async autocomplete(interaction, guildConfig) { + const subcommand = interaction.options.getSubcommand(); + const focusedValue = interaction.options.getFocused().toLowerCase(); + const databaseService = configManager.databaseService; + + if (!databaseService) { + return interaction.respond([]); + } + + // Get allowed role IDs for this guild + const allowedRoleIds = databaseService.getAllowedRoleIds(interaction.guild.id); + + if (allowedRoleIds.length === 0) { + return interaction.respond([]); + } + + let availableRoles = []; + + if (subcommand === 'add') { + // For add: show roles user doesn't have that are self-assignable + for (const roleId of allowedRoleIds) { + try { + const role = await interaction.guild.roles.fetch(roleId); + if (role && !interaction.member.roles.cache.has(roleId)) { + availableRoles.push(role); + } + } catch (error) { + // Role doesn't exist anymore, skip + } + } + } else if (subcommand === 'remove') { + // For remove: show roles user currently has that are self-assignable + for (const roleId of allowedRoleIds) { + try { + const role = await interaction.guild.roles.fetch(roleId); + if (role && interaction.member.roles.cache.has(roleId)) { + availableRoles.push(role); + } + } catch (error) { + // Role doesn't exist anymore, skip + } + } + } + + // Filter based on what user has typed and limit to 25 + const filtered = availableRoles + .filter(role => role.name.toLowerCase().includes(focusedValue)) + .slice(0, 25) + .map(role => ({ + name: role.name, + value: role.name + })); + + await interaction.respond(filtered); } }; \ No newline at end of file