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
JavaScript
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;