/* eslint-disable */ // Copyright 2018-2019 Campbell Crowley. All rights reserved. // Author: Campbell Crowley (dev@campbellcrowley.com) const fs = require('fs'); const mkdirp = require('mkdirp'); require('./subModule.js').extend(RoleManager); // Extends the SubModule class. /** * @classdesc Manages advanced role controls and features. * @class * @augments SubModule * @listens Discord#message * @listens Command#chat */ function RoleManager() { const self = this; /** @inheritdoc */ this.myName = 'Role Manager'; /** @inheritdoc */ this.initialize = function() { cmdRoleAdd = new self.command.SingleCommand( ['add', 'give'], commandRoleAdd, new self.command.CommandSetting({ validOnlyInGuild: true, defaultDisabled: true, permissions: self.Discord.PermissionsBitField.Flags.ManageRoles | self.Discord.PermissionsBitField.Flags.ManageGuild, })); cmdRoleRemove = new self.command.SingleCommand( ['remove', 'delete', 'take'], commandRoleRemove, new self.command.CommandSetting({ validOnlyInGuild: true, defaultDisabled: true, permissions: self.Discord.PermissionsBitField.Flags.ManageRoles | self.Discord.PermissionsBitField.Flags.ManageGuild, })); self.command.on( new self.command.SingleCommand( ['role', 'roles'], commandRole, new self.command.CommandSetting({ validOnlyInGuild: true, defaultDisabled: true, permissions: self.Discord.PermissionsBitField.Flags.ManageRoles | self.Discord.PermissionsBitField.Flags.ManageGuild, })), [ new self.command.SingleCommand( [ 'manage', 'edit', 'modify', 'change', 'config', 'configure', 'settings', 'setting', 'options', 'option', 'opt', ], commandRoleManage, new self.command.CommandSetting({ validOnlyInGuild: true, defaultDisabled: true, permissions: self.Discord.PermissionsBitField.Flags.ManageRoles | self.Discord.PermissionsBitField.Flags.ManageGuild, })), cmdRoleAdd, cmdRoleRemove, ]); }; /** @inheritdoc */ this.shutdown = function() { self.command.deleteEvent('role'); }; /** * @override * @inheritdoc */ this.save = function(opt) { Object.entries(guildPerms).forEach((el) => { const id = el[0]; const data = el[1]; const dir = self.common.guildSaveDir + id; const filename = dir + saveFile; const saveStartTime = Date.now(); if (opt == 'async') { self.common.mkAndWrite(filename, dir, JSON.stringify(data), (err) => { if (err) { self.error(`Failed to save HG data for ${filename}`); console.error(err); } else if (el[1].accessTimestamp - saveStartTime < -15 * 60 * 1000) { delete guildPerms[id]; self.debug(`Purged ${id}`); } }); } else { self.common.mkAndWriteSync(filename, dir, JSON.stringify(data)); if (el[1].accessTimestamp - Date.now() < -15 * 60 * 1000) { delete guildPerms[id]; self.debug('Purged ' + id); } } }); }; /** * The SingleCommand storing permissions for adding roles. * @private * * @type {Command~SingleCommand} */ let cmdRoleAdd; /** * The SingleCommand storing permissions for removing roles. * @private * * @type {Command~SingleCommand} */ let cmdRoleRemove; /** * The roles that each user is allowed to give. Mapped by guild id, then user * id, then role id. Cached. Use {@link RoleManager~find} to access the data. * @private * * @type {Object.<Object.<Object.<boolean>>>} */ const guildPerms = {}; /** * The delay after failing to find a guild's data to look for it again. * * @private * @type {number} * @constant * @default 15 Seconds */ const findDelay = 15000; /** * The file path to save current state for a specific guild relative to * Common~guildSaveDir. * @see {@link Common~guildSaveDir} * * @private * @type {string} * @constant * @default */ const saveFile = '/rolePerms.json'; /** * Manage the basic fallback for the role command. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#role */ function commandRole(msg) { self.common.reply( msg, 'Please specify an action.', '(Ex: Add, remove, manage, etc.'); } /** * Handle the user configuring permissions. * @TODO: Implement. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#roleManage */ function commandRoleManage(msg) { let text = ''; let roles = []; let users = []; if (!msg.text || msg.text.length == 0) { self.common.reply( msg, 'Please specify a role or user to modify, and an action to perform.', '`' + msg.prefix + 'role manage @Trusted allow @Role1 @Role2`'); return; } const giveActions = ['allow', 'grant', 'give', 'permit']; const takeActions = ['deny', 'revoke', 'take', 'remove']; const actions = giveActions.concat(takeActions); const gRoleRegex = self.Discord.MessageMentions.ROLES_PATTERN; const sRoleRegex = new RegExp(gRoleRegex.source); const gUserRegex = self.Discord.MessageMentions.USERS_PATTERN; const sUserRegex = new RegExp(gUserRegex.source); const sRegex = new RegExp(gRoleRegex.source + '|' + gUserRegex); const gRegex = new RegExp(gRoleRegex.source + '|' + gUserRegex, 'g'); const cmdRegex = new RegExp( '(' + sRegex.source + ')\s*(' + actions.join('|') + ')\s*' + sRoleRegex.source); const cmdMatch = msg.text.match(cmdRegex); if (!cmdMatch) { self.common.reply( msg, 'Please specify a role or user to modify, and an action to perform.', '`' + msg.prefix + 'role manage @Trusted allow @Role1 @Role2`'); return; } roles = msg.text.match(gRoleRegex); users = msg.text.match(gUserRegex); text = msg.text.replace(self.Discord.MessageMentions.ROLES_PATTERN, '') .replace(/\s+/g, ' '); if (text) { text.split(/\s/).forEach((el) => { const str = el.toLowerCase(); }); } } /** * Give a guild member a role. * @public * * @param {string|number|Discord~Guild} guild Guild object, or ID. * @param {string|number|Discord~GuildMember} member Guild Member object, ID * or name (username, nickname or tag) to lookup. * @param {string|number|Discord~Role} role Guild Role object, ID or name to * lookup. * @return {?string} Null if success, string if error. */ this.giveRole = function(guild, member, role) { }; /** * Handle the user attempting to add a role. * @TODO: Implement. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#roleAdd */ function commandRoleAdd(msg) { } /** * Handle the user attempting to remove a role. * @TODO: Implement. * * @private * @type {commandHandler} * @param {Discord~Message} msg Message that triggered command. * @listens Command#roleRemove */ function commandRoleRemove(msg) { } /** * Returns a guild's data. Returns cached version if that exists, or searches * the file system for saved data. Data will only be checked from disk at most * once every `RoleManager~findDelay` milliseconds. Returns `null` if data * could not be found, or an error occurred. * * @private * @param {number|string} id The guild id to get the data for. * @return {?Object} The role data, or null if no data could be loaded. */ function find(id) { if (guildPerms[id]) return guildPerms[id]; if (Date.now() - guildPerms[id].accessTimestamp < findDelay) return null; guildPerms[id].accessTimestamp = Date.now(); try { const tmp = fs.readFileSync(self.common.guildSaveDir + id + saveFile); try { guildPerms[id] = JSON.parse(tmp); if (self.initialized) self.debug('Loaded roles from file ' + id); } catch (e2) { self.error('Failed to parse roles data for guild ' + id); return null; } } catch (e) { if (e.code !== 'ENOENT') { self.debug('Failed to load role data for guild:' + id); console.error(e); } return null; } return guildPerms[id]; } } module.exports = new RoleManager();