addActivityRole.ts
1 import { __h_dc } from './../messages'; 2 import { 3 Role, 4 CommandInteraction, 5 PermissionsBitField, 6 StringSelectMenuInteraction, 7 InteractionType, 8 EmbedBuilder, 9 ActionRowBuilder, 10 StringSelectMenuBuilder, 11 APIRole, 12 SlashCommandBuilder, 13 ComponentType, 14 Colors, 15 ChannelType 16 } from 'discord.js'; 17 18 import { Command } from '../commandHandler'; 19 import config from '../config'; 20 import { Locale, log, __ } from '../messages'; 21 import { prepare, getLang } from '../db'; 22 23 export default { 24 data: new SlashCommandBuilder() 25 .setName('addactivityrole') 26 .setDescription('Adds an activity role to your guild.') 27 .setDescriptionLocalizations(__h_dc('Adds an activity role to your guild.')) 28 .setDefaultMemberPermissions(PermissionsBitField.Flags.ManageRoles) 29 .setDMPermission(false) 30 .addStringOption(option => 31 option 32 .setName('activity') 33 .setDescription('the name of the discord activity') 34 .setDescriptionLocalizations(__h_dc('the name of the discord activity')) 35 .setRequired(true) 36 ) 37 .addRoleOption(option => 38 option 39 .setName('role') 40 .setDescription( 41 'If not provided, the bot will look for roles with the same name or create a new one' 42 ) 43 .setDescriptionLocalizations( 44 __h_dc( 45 'If not provided, the bot will look for roles with the same name or create a new one' 46 ) 47 ) 48 .setRequired(false) 49 ) 50 .addBooleanOption(option => 51 option 52 .setName('exact_activity_name') 53 .setDescription( 54 "If false, the activity name 'Chrome' would also trigger for 'Google Chrome'" 55 ) 56 .setDescriptionLocalizations( 57 __h_dc("If false, the activity name 'Chrome' would also trigger for 'Google Chrome'") 58 ) 59 .setRequired(false) 60 ) 61 .addBooleanOption(option => 62 option 63 .setName('permanent') 64 .setDescription('the role will not be removed again if set to true') 65 .setDescriptionLocalizations( 66 __h_dc('the role will not be removed again if set to true') 67 ) 68 .setRequired(false) 69 ), 70 execute: async interaction => { 71 const locale = getLang(interaction); 72 if (!interaction.channel) return; 73 if (interaction.channel.type !== ChannelType.GuildText) { 74 await interaction.reply(__({ phrase: 'This command can only be used in text channels.', locale })); 75 return; 76 } 77 78 const activityName = interaction.options.get('activity', true)?.value as string; 79 if (activityName.length > 1024) { 80 await interaction.reply({ 81 content: __({ 82 phrase: 'The activity name is too long! Maximum is 1024 characters.', 83 locale 84 }), 85 ephemeral: true 86 }); 87 return; 88 } 89 90 const exactActivityName = (interaction.options.get('exact_activity_name', false)?.value as boolean | undefined) ?? false; 91 const permanent = (interaction.options.get('permanent', false)?.value as boolean | undefined) ?? false; 92 93 let role = interaction.options.get('role', false)?.role; 94 if (!role) { 95 // role not provided 96 const possibleRoles = interaction.guild?.roles.cache.filter(role => { 97 return role.name.toLowerCase().includes(activityName.toLowerCase()); 98 }); 99 if (!possibleRoles || possibleRoles.size === 0) { 100 // create role 101 role = await createRole(interaction, activityName); 102 process(interaction, role, activityName, exactActivityName, permanent, locale); 103 } else if (possibleRoles.size === 1) { 104 // use role 105 role = possibleRoles.first()!; 106 process(interaction, role, activityName, exactActivityName, permanent, locale); 107 } else { 108 // select role 109 const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( 110 new StringSelectMenuBuilder() 111 .setCustomId('addactivityrole:roleSelector') 112 .setPlaceholder(__({ phrase: "Please select a role for '%s'", locale }, activityName)) 113 .addOptions([ 114 ...possibleRoles.map(role => { 115 return { 116 label: role.name, 117 description: role.id, 118 value: role.id 119 }; 120 }), 121 { 122 label: __({ phrase: 'Create %s', locale }, activityName), 123 description: __( 124 { phrase: "Create a new role with the name '%s'", locale }, 125 activityName 126 ), 127 value: 'create' 128 } 129 ]) 130 ); 131 interaction.channel 132 .createMessageComponentCollector({ 133 componentType: ComponentType.StringSelect, 134 filter: componentInteraction => 135 componentInteraction.customId === 'addactivityrole:roleSelector', 136 time: 60000, 137 max: 1 138 }) 139 .once('collect', async selectMenuInteraction => { 140 if (selectMenuInteraction.user.id !== interaction.user.id) return; 141 if (!selectMenuInteraction.isStringSelectMenu()) return; 142 if (selectMenuInteraction.customId !== 'addactivityrole:roleSelector') return; 143 if (selectMenuInteraction.values[0] === 'create') { 144 role = await createRole(interaction, activityName); 145 } else { 146 role = 147 selectMenuInteraction.guild!.roles.cache.get(selectMenuInteraction.values[0]) || 148 null; 149 } 150 if (role) { 151 process(selectMenuInteraction, role, activityName, exactActivityName, permanent, locale); 152 } 153 }); 154 interaction.reply({ 155 components: [row], 156 ephemeral: true 157 }); 158 } 159 } else { 160 process(interaction, role, activityName, exactActivityName, permanent, locale); 161 } 162 } 163 } as Command; 164 165 async function createRole(interaction: CommandInteraction, activityName: string) { 166 return await interaction.guild!.roles.create({ 167 name: activityName, 168 color: config.COLOR, 169 mentionable: true 170 }); 171 } 172 173 function reply( 174 interaction: CommandInteraction | StringSelectMenuInteraction, 175 content?: string, 176 embeds?: EmbedBuilder[] 177 ) { 178 if (interaction.type === InteractionType.ApplicationCommand) { 179 interaction.reply({ content, embeds, ephemeral: true }); 180 } else if (interaction.isStringSelectMenu()) { 181 interaction.update({ content, embeds, components: [] }); 182 } 183 } 184 185 function process( 186 interaction: CommandInteraction | StringSelectMenuInteraction, 187 role: Role | APIRole, 188 activityName: string, 189 exactActivityName: boolean, 190 permanent: boolean, 191 locale: Locale 192 ) { 193 if (!role) reply(interaction, __({ phrase: ':x: That role does not exist! :x:', locale })); 194 if (role.name === '@everyone') { 195 reply(interaction, __({ phrase: "You can't use \\@everyone as an activity role.", locale })); 196 return; 197 } 198 if ( 199 !interaction.guild?.members.me?.roles.highest?.position || 200 !interaction.guild.members.me.roles.highest.id 201 ) 202 return; 203 if ( 204 role.position && 205 interaction.guild.members.me?.roles.highest?.position && 206 role.position >= interaction.guild.members.me.roles.highest.position 207 ) { 208 reply(interaction, undefined, [ 209 new EmbedBuilder() 210 .setColor(Colors.Red) 211 .setDescription( 212 __({ 213 phrase: 214 'To assign roles, my highest role needs to be higher than the role I am assigning.\nMove any of my roles higher than the role I should manage.', 215 locale 216 }) 217 ) 218 .addFields( 219 { 220 name: __({ phrase: 'My highest role:', locale }), 221 value: 222 `<@&${interaction.guild.members.me.roles.highest.id}> ` + 223 __( 224 { phrase: '(position #%s)', locale }, 225 interaction.guild.members.me.roles.highest.position.toString() 226 ) 227 }, 228 { 229 name: __({ phrase: 'the activity role:', locale }), 230 value: 231 `<@&${role.id}> ` + __({ phrase: '(position #%s)', locale }, role.position.toString()) 232 } 233 ) 234 ]); 235 return; 236 } 237 if ( 238 prepare('SELECT * FROM activityRoles WHERE guildID = ? AND roleID = ? AND activityName = ?') 239 .get(interaction.guild!.id, role.id, activityName) 240 ) { 241 reply( 242 interaction, 243 __({ phrase: ':x: That activity role already exists in this guild! :x:', locale }) 244 ); 245 return; 246 } else { 247 prepare('INSERT INTO activityRoles VALUES (?, ?, ?, ?, ?)').run( 248 interaction.guild!.id, 249 activityName, 250 role.id, 251 Number(exactActivityName), 252 Number(!permanent) 253 ); 254 log.info( 255 `New activity role added: in guild ${interaction.guild.name} (${interaction.guild.id}) role: ${role.name} (${role.id}) activityName: ${activityName}, exactActivityName: ${exactActivityName}, permanent: ${permanent}` 256 ); 257 reply(interaction, undefined, [ 258 new EmbedBuilder() 259 .setColor(config.COLOR) 260 .setTitle(__({ phrase: 'Success!', locale })) 261 .addFields( 262 { name: __({ phrase: 'Activity', locale }), value: activityName }, 263 { name: __({ phrase: 'Role', locale }), value: `<@&${role.id}>` }, 264 { 265 name: __({ phrase: 'Exact Activity Name', locale }), 266 value: exactActivityName ? __('Yes') : __('No') 267 }, 268 { 269 name: __({ phrase: 'Permanent', locale }), 270 value: permanent ? __({ phrase: 'Yes', locale }) : __({ phrase: 'No', locale }) 271 } 272 ) 273 ]); 274 } 275 }