UNPKG

permify

Version:

An enterprise-grade utility for simplified Discord permission management with built-in support for multiple storage backends.

199 lines (184 loc) 8.61 kB
const { PermissionsBitField } = require('discord.js'); const InMemoryStorage = require('./lib/storages/inMemoryStorage'); const SQLiteStorage = require('./lib/storages/sqliteStorage'); const MySQLStorage = require('./lib/storages/mysqlStorage'); const MongoStorage = require('./lib/storages/mongoStorage'); /** * A robust utility for handling permissions and custom access rules in Discord.js. */ class PermissionsManager { /** * @param {object} [options] - Optional configuration for the manager. * @param {string} [options.storage='inmemory'] - The storage provider to use. Supported values: 'inmemory', 'sqlite', 'mysql', 'mongodb'. * @param {object} [options.storageOptions] - Additional options for the chosen storage provider. * @param {boolean} [options.ephemeralResponse=true] - Whether the default permission denial response should be ephemeral. */ constructor(options = {}) { this.ephemeralResponse = options.ephemeralResponse !== false; switch (options.storage) { case 'sqlite': this.storage = new SQLiteStorage(options.storageOptions?.path); break; case 'mysql': this.storage = new MySQLStorage(options.storageOptions); break; case 'mongodb': this.storage = new MongoStorage(options.storageOptions); break; case 'inmemory': default: this.storage = new InMemoryStorage(); break; } } /** * Initializes the permissions manager by connecting to the specified storage provider. * @returns {Promise<void>} */ async initialize() { return this.storage.connect(); } /** * Checks if a member has all the required permissions. * @param {GuildMember} member - The member to check. * @param {string | string[] | bigint | bigint[]} permissions - A single permission or an array of permissions to check for. * @returns {boolean} - True if the member has all permissions, false otherwise. */ hasAllPermissions(member, permissions) { if (!member || !member.permissions) { return false; } try { const perms = new PermissionsBitField(permissions); return member.permissions.has(perms); } catch (error) { console.error('Error in hasAllPermissions:', error); return false; } } /** * Checks if a member has any of the required permissions. * @param {GuildMember} member - The member to check. * @param {string[] | bigint[]} permissions - An array of permissions to check for. * @returns {boolean} - True if the member has at least one permission, false otherwise. */ hasAnyPermission(member, permissions) { if (!member || !member.permissions) { return false; } try { return permissions.some(p => member.permissions.has(new PermissionsBitField(p))); } catch (error) { console.error('Error in hasAnyPermission:', error); return false; } } /** * Identifies which permissions are missing for a member. * @param {GuildMember} member - The member to check. * @param {string[] | bigint[]} requiredPermissions - The permissions that are required. * @returns {string[]} - An array of the names of the permissions that the member is missing. */ getMissingPermissions(member, requiredPermissions) { if (!member || !member.permissions) { return requiredPermissions; } return requiredPermissions.filter(p => !member.permissions.has(new PermissionsBitField(p))); } /** * A utility function to check permissions and send an immediate response if they are missing. * @param {Interaction} interaction - The interaction object from a command. * @param {string | string[] | bigint | bigint[]} requiredPermissions - The permissions needed for the command. * @param {string} [customMessage] - An optional custom message to send if permissions are denied. * @returns {Promise<boolean>} - Resolves to true if permissions are granted, false otherwise. */ async checkAndRespond(interaction, requiredPermissions, customMessage) { if (!interaction.inGuild()) { await interaction.reply({ content: 'This command can only be used in a server.', ephemeral: true }); return false; } const permsArray = Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions]; const missingPermissions = this.getMissingPermissions(interaction.member, permsArray); if (missingPermissions.length > 0) { const permissionNames = missingPermissions.map(p => { const bitField = new PermissionsBitField(p); for (const [name, bit] of Object.entries(PermissionsBitField.Flags)) { if (bitField.has(bit)) return name; } return p.toString(); }); const message = customMessage || `You are missing the following permissions to use this command: \`${permissionNames.join(', ')}\``; await interaction.reply({ content: message, ephemeral: this.ephemeralResponse }); return false; } return true; } /** * Adds a custom permission rule for a specific entity (user or role) for a command. * @param {string} commandName - The name of the command to grant access to. * @param {string} entityId - The ID of the user or role. * @returns {Promise<void>} */ async addCustomPermission(commandName, entityId) { try { await this.storage.addCustomPermission(commandName, entityId); } catch (error) { console.error(`Failed to add custom permission for command '${commandName}' and entity '${entityId}':`, error); } } /** * Removes a custom permission rule. * @param {string} commandName - The name of the command to revoke access from. * @param {string} entityId - The ID of the user or role. * @returns {Promise<boolean>} - True if the permission was removed, false otherwise. */ async removeCustomPermission(commandName, entityId) { try { return await this.storage.removeCustomPermission(commandName, entityId); } catch (error) { console.error(`Failed to remove custom permission for command '${commandName}' and entity '${entityId}':`, error); return false; } } /** * Checks if a member has a custom permission for a command, either by user ID or by role. * @param {GuildMember} member - The member to check. * @param {string} commandName - The name of the command. * @returns {Promise<boolean>} - True if the member has the custom permission, false otherwise. */ async hasCustomPermission(member, commandName) { try { if (!member) { return false; } // Check for user-specific custom permission. if (await this.storage.hasCustomPermission(commandName, member.id)) { return true; } // Check for role-specific custom permissions. for (const roleId of member.roles.cache.keys()) { if (await this.storage.hasCustomPermission(commandName, roleId)) { return true; } } return false; } catch (error) { console.error(`Failed to check custom permission for command '${commandName}' and member '${member.id}':`, error); return false; } } /** * Retrieves all custom permissions for a specific command. * @param {string} commandName - The name of the command. * @returns {Promise<string[]>} - An array of user/role IDs with the permission. */ async getCustomPermissionsForCommand(commandName) { try { return await this.storage.getCustomPermissions(commandName); } catch (error) { console.error(`Failed to get custom permissions for command '${commandName}':`, error); return []; } } } module.exports = PermissionsManager;