/// <summary> /// Gathers the data for all commands found in the command modules. /// </summary> private IEnumerable <HelpDataAttribute> GetCommandDataCollection(UserType userType) { var commandList = new List <HelpDataAttribute>(); // Iterate over every installed command foreach (ModuleInfo module in _commands.Modules) { foreach (CommandInfo command in module.Commands) { PermissionRestrictionAttribute pra = command.Attributes.FirstOrDefault(a => a is PermissionRestrictionAttribute) as PermissionRestrictionAttribute; // If the command is permission restricted, check if the user is allowed to see it if (pra != null) { // If the user isn't allowed to see the command, go to the next one if (!pra.AllowedRoles.HasFlag(userType)) { continue; } } else if (userType.HasFlag(UserType.Staff)) { // Make sure normal commands don't show up in the staff menu continue; } HelpDataAttribute helpData = command.Attributes.FirstOrDefault(a => a is HelpDataAttribute) as HelpDataAttribute; // If the command has a help data attribute, include it in the collection if (helpData != null) { commandList.Add(helpData); } } } return(commandList .OrderBy(data => data.ListOrder) .ThenBy(data => data.Usage)); }
/// <summary> /// Handles a command, represented in the given message. /// </summary> private async Task HandleCommand(SocketMessage s, DiscordSocketClient client) { if (!(s is SocketUserMessage msg)) { return; } int argPos = 0; if (!msg.HasStringPrefix(CommandPrefix, ref argPos)) { return; } IGuildUser author = s.Author as IGuildUser; CommandContext context = new CommandContext(client, msg); Log.WriteLine($"{msg.Author} attempted to invoke \"{msg.Content}\"."); if (context.IsPrivate) { // Don't allow bot usage in private messages await context.Channel.SendMessageAsync("I'm sorry but you can't use commands here since they don't work in DMs (not my fault, I swear :eyes:). Please run the commands in our server! :smile:"); return; } CommandInfo executedCommand = null; try { executedCommand = _commandService.Search(context, argPos).Commands.First().Command; } catch { // The executed command wasnt found in the modules, therefore look if any custom commands are registered. string command = msg.Content.Substring(argPos).Split(' ')[0].ToLowerInvariant(); if (_data.CustomCommands.Has(command)) { string message = _data.CustomCommands.Get(command); await context.Channel.SendMessageAsync(message); } else { // Also no custom command was found? Check all commands and custom commands if there was a close match somewhere const int LEVENSHTEIN_TOLERANCE = 2; IEnumerable <string> commandNames = _commandService.Commands .Where(c => // Make sure that only commands that can be used by the user get listed { PermissionRestrictionAttribute pra = c.Attributes.FirstOrDefault(a => a is PermissionRestrictionAttribute) as PermissionRestrictionAttribute; if (pra != null) { UserType roles = pra.AllowedRoles; // Allow the command usage if anyone can use it, or the user is a staff member if (roles.HasFlag(UserType.Everyone) || author.HasStaffRole()) { return(true); } // If the command is for gurus, check if the user has the guru role if (roles.HasFlag(UserType.Guru)) { return(author.HasGuruRole()); } return(false); } return(true); }) .Select(c => c.Name) .Concat(_data.CustomCommands.CommandNames) .Distinct(); string closeMatch = commandNames .ToDictionary(l => l, l => command.ToLowerInvariant().ComputeLevenshtein(l.ToLowerInvariant())) // Use lower casing to avoid too high intolerance .Where(l => l.Value <= LEVENSHTEIN_TOLERANCE) .OrderBy(l => l.Value) .FirstOrDefault().Key; if (closeMatch != null) { // A close match was found! Notify the user. EmbedBuilder eb = new EmbedBuilder() .WithColor(Color.Red) .WithTitle("Error") .WithDescription($"The command \"{command}\" could not be found. Did you mean \"{closeMatch}\"?"); await context.Channel.SendMessageAsync(string.Empty, false, eb); return; } else { // The entered command was just nonsense. Just ignore it. return; } } return; } PermissionRestrictionAttribute restriction = executedCommand.Attributes.FirstOrDefault(a => a is PermissionRestrictionAttribute) as PermissionRestrictionAttribute; if (restriction != null) { EmbedBuilder eb = new EmbedBuilder() .WithTitle("Insufficient permissions") .WithDescription("You don't have the required permissions to run that command.") .WithColor(Color.Red); bool denyInvokation = true; UserType roles = restriction.AllowedRoles; // Allow the command usage if anyone can use it, or the user is a staff member if (roles.HasFlag(UserType.Everyone) || author.HasStaffRole()) { denyInvokation = false; } // If the command is for gurus, check if the user has the guru role if (roles.HasFlag(UserType.Guru) && author.HasGuruRole()) { denyInvokation = false; } if (denyInvokation) { var message = await context.Channel.SendMessageAsync(string.Empty, false, eb); _ = message.TimedDeletion(5000); Log.WriteLine($"The command \"{msg.Content}\" failed with the reason InsufficientPermissions."); return; } } bool cooldownCommand = CheckIfCommandHasCooldown(executedCommand.Name.ToLower()); bool sameParamCommand = false; bool cooldownExpired = false; string parameters = ""; if (cooldownCommand && !UserHelper.HasStaffRole(s.Author as IGuildUser)) { sameParamCommand = CheckIfSameParameterCommand(executedCommand.Name.ToLower()); parameters = s.ToString().Remove(0, s.ToString().IndexOf(' ') + 1); cooldownExpired = HasCooldownExpired(executedCommand.Name, s.Author as IGuildUser, sameParamCommand, parameters); if (!cooldownExpired) { TimeSpan ts = GetTimeUntilCooldownHasExpired(executedCommand.Name.ToLower(), s.Author as IGuildUser, sameParamCommand, parameters); Embed eb = new EmbedBuilder() .WithTitle("Cooldown hasn't expired yet") .WithDescription($"{s.Author.Mention}, you can't run this command yet. Please wait {ts.Hours} hours, {ts.Minutes} minutes and {ts.Seconds} seconds.") .WithColor(Color.Orange); await context.Channel.SendMessageAsync(string.Empty, false, eb); return; } } IResult result = await _commandService.ExecuteAsync(context, argPos, ServiceProvider); if (!result.IsSuccess) { Log.WriteLine($"The command \"{msg.Content}\" failed with the reason {result.Error.Value}: \"{result.ErrorReason}\""); if (result.Error == CommandError.UnknownCommand || result.Error == CommandError.BadArgCount || result.Error == CommandError.ParseFailed) { return; } EmbedBuilder builder = new EmbedBuilder() .WithTitle("Error") .WithDescription(result.ErrorReason) .WithColor(Color.Red); IMessage errorMsg = await context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } else { if (cooldownCommand && !UserHelper.HasStaffRole(s.Author as IGuildUser)) { AddUserToCooldown(executedCommand.Name, s.Author as IGuildUser, sameParamCommand, parameters); } string command = executedCommand.Name; if (_data.Statistics.Has(command)) { _data.Statistics.Set(command, _data.Statistics.Get(command) + 1); } else { _data.Statistics.Add(command, 1); } } }