import { RoleCreateOptions, GuildMember, CommandInteraction, EmbedBuilder, TextChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from 'discord.js'; import { default as parseColor, Color } from 'parse-color'; import { getRoleSeperator, isColorRole } from '../lib/assignableRoles'; import * as log from '../lib/log'; import { Command } from '../types/index'; const PREVIEW_DURATION = 1000 * 60; function removeAllColorRoles(member: GuildMember) { return Promise.all(member.roles.cache.map(role => { if (isColorRole(role.name)) { return member.roles.remove(role); } })); } async function applyColor(member: GuildMember, color: Color) { await removeAllColorRoles(member); const seperator = await getRoleSeperator('color', member.guild); if (!seperator) log.info('no color role seperator found'); const roleSettings: RoleCreateOptions = { name: color.hex, color: color.rgb, hoist: false, mentionable: false, permissions: 0n, }; if (seperator) { roleSettings.position = seperator.position; } const role = member.guild.roles.cache.find(role => role.name === color.hex) || await member.guild.roles.create(roleSettings); await member.roles.add(role); } export default { data: new SlashCommandBuilder() .setName('color') .setDescription('Change your role color.') .addStringOption((option) => option.setName('color') .setDescription('Color to set. Must be a parsable HTML color. Examples include: #ff0000, rgb(50, 0, 0), red, etc.') ) .addBooleanOption((option) => option.setName('preview').setDescription('Preview the color instead of applying it') ) .setDefaultMemberPermissions(0) .setDMPermission(false), execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; const member = interaction.member! as GuildMember; const color = interaction.options.getString('color'); const preview = interaction.options.getBoolean('preview'); if (color) { const parsed = parseColor(color.trim()); if (!parsed.hex) { interaction.reply({ content: 'Invalid color. Try an HTML color like `#ff0000`, `rgb(50, 0, 0)`, `red`, etc.', ephemeral: true }); return; } if (preview) { const embed = new EmbedBuilder(); embed.setImage(`https://dummyimage.com/200x200/${parsed.hex.slice(1)}/${parsed.hex.slice(1)}.png`); embed.setTitle(`Previewing ${parsed.hex}`); const apply = new ButtonBuilder() .setLabel('Apply') .setStyle(ButtonStyle.Primary) .setCustomId(`apply-color-${parsed.hex}`); const row = new ActionRowBuilder() .addComponents(apply); const message = await interaction.reply({ embeds: [embed], components: [row], fetchReply: true }); const collector = (interaction.channel as TextChannel).createMessageComponentCollector({ filter: (i) => i.message.id === message.id, time: PREVIEW_DURATION, }); collector.on('collect', async (i) => { if (i.customId !== `apply-color-${parsed.hex}`) return; if (i.member === member) { collector.stop(); await i.deferReply({ephemeral: false}); row.components[0].setDisabled(true); await message.edit({ embeds: [embed], components: [row] }); await applyColor(member, parsed); await i.followUp({ content: 'Color applied.' }); } else { i.reply({ content: 'You didn\'t run the command!', ephemeral: true }); } }); } else { await interaction.deferReply({ ephemeral: true }); await applyColor(member, parsed); interaction.followUp({ content: 'Your color has been set.' }); } } else { await removeAllColorRoles(member); interaction.reply({ content: 'Removed all colors.', ephemeral: true }); } } } satisfies Command;