From eb4ffae1730670a90512aeb057fad6a438595589 Mon Sep 17 00:00:00 2001 From: Minzkraut Date: Fri, 19 Aug 2022 19:18:01 +0200 Subject: [PATCH] Add command permission checks Level 0: Every user - Public commands Level 1: Guild owners or members with respective admin role - Elevated guild commands Level 2: Global admins - Every command including levels below --- commands/debug.js | 9 +++++- commands/debugDrop.js | 2 +- commands/guildSettings.js | 61 +++++++++++++++++++++++++++++++++++++ commands/ping.js | 1 + events/interactionCreate.js | 14 ++++++++- util/general.js | 6 ++++ util/guilds.js | 23 ++++++++++++++ util/users.js | 35 ++++++++++++++++++++- 8 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 commands/guildSettings.js create mode 100644 util/guilds.js diff --git a/commands/debug.js b/commands/debug.js index 4a650b8..3ef791d 100644 --- a/commands/debug.js +++ b/commands/debug.js @@ -13,7 +13,7 @@ module.exports = { .setDescription("The command to debug") .setRequired(false) ), - + permissionLevel: 2, async execute(interaction) { const identifier = CardUtils.generateIdentifier(); let user = await UserUtils.getUserByDiscordId(interaction.member.id); @@ -70,6 +70,13 @@ module.exports = { content: `Reset cooldowns`, ephemeral: false }); + break; + default: + interaction.reply({ + content: `Your permission level is ${await UserUtils.getPermissionLevel(interaction.member)}`, + ephemeral: false + }); + break; } } } \ No newline at end of file diff --git a/commands/debugDrop.js b/commands/debugDrop.js index 3eacf58..990ee07 100644 --- a/commands/debugDrop.js +++ b/commands/debugDrop.js @@ -12,7 +12,7 @@ module.exports = { .setDescription("The id of the character to drop") .setRequired(true) ), - + permissionLevel: 2, async execute(interaction) { //get user id from database given the userID const user = await User.findOne({ diff --git a/commands/guildSettings.js b/commands/guildSettings.js new file mode 100644 index 0000000..e107d7c --- /dev/null +++ b/commands/guildSettings.js @@ -0,0 +1,61 @@ +const { SlashCommandBuilder, ComponentType, ActionRowBuilder, ButtonBuilder, ButtonStyle, SelectMenuBuilder } = require("discord.js"); +const { GeneralUtils, GuildUtils } = require("../util"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("settings") + .setDescription("Change Guild Settings"), + permissionLevel: 1, + + async execute(interaction) { + let reply = "Guild settings:\n"; + const row = new ActionRowBuilder(); + row.addComponents( + new ButtonBuilder() + .setCustomId(`set-admin-role`) + .setLabel('Set Admin Role') + .setStyle(ButtonStyle.Primary) + ); + const message = await interaction.reply({ content: reply, components: [row], fetchReply: true }); + + const filter = (m) => m.author.id === message.author.id; + const collector = message.createMessageComponentCollector(filter, { componentType: ComponentType.Button, time: 120000 }); + + //BUGBUG: end callback does not trigger + collector.on('end', collected => { + console.log(`Collected ${collected.size} interactions.`); + message.edit({ components: [] }); + }); + + collector.on('collect', async m => { + switch (m.customId) { + case 'set-admin-role': + //Build select menu with every role in the guild + const row = new ActionRowBuilder() + .addComponents( + new SelectMenuBuilder() + .setCustomId('select') + .setPlaceholder('Nothing selected') + .addOptions( + m.guild.roles.cache.map(role => ({ + label: role.name, + description: role.id, + value: role.id + })) + ), + ); + const message = await m.reply({ content: 'Select a role', components: [row], fetchReply: true }); + + const filter = (m) => m.author.id === message.author.id; + const replyCollector = message.createMessageComponentCollector(filter, { componentType: ComponentType.SelectMenu, time: 25000 }); + replyCollector.on('collect', async r => { + const role = m.guild.roles.cache.find(role => role.id === r.values[0]); + m.deleteReply(); //Delete select menu message + GuildUtils.setProperty(m.guild.id, 'adminRoleId', role.id); + r.reply({ content: `Selected role: ${role.name} \n(${role.id})`, components: [] }); + }); + break; + } + }); + } +} \ No newline at end of file diff --git a/commands/ping.js b/commands/ping.js index 618213d..b6dbb5b 100644 --- a/commands/ping.js +++ b/commands/ping.js @@ -4,6 +4,7 @@ module.exports = { data: new SlashCommandBuilder() .setName("ping") .setDescription("Ping, yes"), + permissionLevel: 1, async execute(interaction) { interaction.reply({ content: "Pong!", diff --git a/events/interactionCreate.js b/events/interactionCreate.js index e1e5ae0..d06e939 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -35,8 +35,20 @@ module.exports = { }); } - + const command = interaction.client.commands.get(interaction.commandName); + + //check if user has permissions to run the command + //TODO: pass down this user object to avoid duplicate queries + let user = await UserUtils.getUserByDiscordId(interaction.member.id); + let permissionLevel = await UserUtils.getPermissionLevel(interaction.member); + if (command.permissionLevel > permissionLevel) { + interaction.reply({ + content: `You do not have permission to run this command`, + ephemeral: true + }); + return; + } if (!command) return; diff --git a/util/general.js b/util/general.js index f9a218f..bf4549a 100644 --- a/util/general.js +++ b/util/general.js @@ -5,5 +5,11 @@ module.exports = { getBotProperty: async function(property) { let bot = await Bot.findOne(); return property ? bot[property] : bot; + }, + + setBotProperty: async function(property, value) { + let bot = await Bot.findOne(); + bot[property] = value; + await bot.save(); } } diff --git a/util/guilds.js b/util/guilds.js new file mode 100644 index 0000000..986cbc3 --- /dev/null +++ b/util/guilds.js @@ -0,0 +1,23 @@ +const { Guild } = require("../models"); + +module.exports = { + name: "GuildUtils", + getProperty: async function(guildId, property) { + let guild = await Guild.findOne({ + where: { + guildId: guildId + } + }); + return property ? guild[property] : guild; + }, + + setProperty: async function(guildId, property, value) { + let guild = await Guild.findOne({ + where: { + guildId: guildId + } + }); + guild[property] = value; + await guild.save(); + } +} diff --git a/util/users.js b/util/users.js index e73d30e..73b617c 100644 --- a/util/users.js +++ b/util/users.js @@ -1,4 +1,5 @@ -const { User } = require("../models"); +const { User, Guild } = require("../models"); +const GeneralUtils = require("./general"); module.exports = { name: "UserUtils", @@ -65,5 +66,37 @@ module.exports = { let newCooldown = new Date(new Date().getTime() + cooldown); user[`next${cooldownType[0].toUpperCase() + cooldownType.slice(1)}`] = newCooldown; await user.save(); + }, + + getPermissionLevel: async function(user) { + /* THIS FUNCTION EXPECTS A DISCORD USER INSTANCE! + * Returns the permission level of the user + * 0 - no permissions + * 1 - guild permissions + * 2 - admin permissions + */ + let guild = await Guild.findOne({ + where: { + guildId: user.guild.id + } + }); + + //Global Admin + let adminIDs = await GeneralUtils.getBotProperty("adminIDs"); + if (adminIDs.includes(user.id)) { + return 2; + } + + //Guild Admin if role is present + if(user._roles.includes(String(guild.adminRoleId))) { + return 1; + } + //or if user is owner + if(user.guild.ownerId === user.id) { + return 1; + } + + //Regular User + return 0; } }