private BaseDiscordCommand InstantateDiscordCommand(Type type) { BaseDiscordCommand command = null; IEnumerable <CommandAttribute> attributes = type.GetCustomAttributes <CommandAttribute>(true); if (!attributes.Any()) { command = (BaseDiscordCommand)Activator.CreateInstance(type); } else { List <object> commandParameters = new List <object>(); if (attributes.Any(a => a.GetType() == typeof(HttpClientAttribute))) { commandParameters.Add(_httpClient); } command = (BaseDiscordCommand)Activator.CreateInstance(type, commandParameters.ToArray()); } return(command); }
private void ManageException(DiscordMessage message, DiscordUser author, DiscordChannel channel, Exception ex, BaseDiscordCommand command) { LogMessage?.Invoke(this, $"\n --- Something's f****d up! --- \n{ex.ToString()}\n"); _telemetryClient?.TrackException(ex, new Dictionary <string, string> { { "command", command.GetType().Name } }); if (!(ex is TaskCanceledException) && !(ex is OperationCanceledException)) { new Task(async() => { try { DiscordMessage msg = await channel.SendMessageAsync( $"Something's gone very wrong executing that command, and an {ex.GetType().Name} occured." + "\r\nThis error has been reported, and should be fixed soon:tm:!" + $"\r\nThis message will be deleted in 10 seconds."); await Task.Delay(10_000); await msg.DeleteAsync(); } catch { } }).Start(); } }
private async Task HandleBadRequest(DiscordMessage message, DiscordUser author, DiscordChannel channel, IEnumerable <string> commandSegments, string commandAlias, BaseDiscordCommand command, DateTimeOffset start) { LogMessage?.Invoke(this, $"[{message.Channel.Guild?.Name ?? message.Channel.Name}] {command.Name} does not take {commandSegments.Count()} arguments."); _telemetryClient?.TrackRequest(InternalTools.GetRequestTelemetry(author, channel, command, start, "400", false)); if (command.Usage != null) { await InternalTools.SendTemporaryMessage(message, author, channel, $"```\r\n{commandAlias} usage: {_config.Prefix}{commandAlias} [{command.Usage}]\r\n```"); } }
private async Task ExecuteCommandAsync(DiscordMessage message, DiscordUser author, DiscordChannel channel, IEnumerable <string> commandSegments, string commandAlias, BaseDiscordCommand command, DateTimeOffset start) { try { LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] Found {command.Name} command!")); _processedMessageIds.Add(message.Id); if (command.ArgumentCountPrecidate(commandSegments.Count())) { if (InternalTools.CheckPermissions(_discordClient, channel.Guild?.CurrentMember ?? _discordClient.CurrentUser, channel, command)) { if (InternalTools.CheckPermissions(_discordClient, author, channel, command)) { LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] Running command \"{command.Name}\" asynchronously.")); new Task(async() => await RunCommandAsync(message, author, channel, commandSegments, commandAlias, command, start)).Start(); } else { LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] Attempt to run command without correct user permissions.")); await InternalTools.SendTemporaryMessage(message, author, channel, $"Oi! You're not allowed to run that command! F**k off!"); _telemetryClient?.TrackRequest(InternalTools.GetRequestTelemetry(author, channel, command, start, "401", false)); } } else { LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] Attempt to run command without correct bot permissions.")); await InternalTools.SendTemporaryMessage(message, author, channel, $"Sorry! I don't have permission to run that command in this server! Contact an admin/mod for more info."); _telemetryClient?.TrackRequest(InternalTools.GetRequestTelemetry(author, channel, command, start, "403", false)); } } else { await HandleBadRequest(message, author, channel, commandSegments, commandAlias, command, start); } } catch (Exception ex) { Console.WriteLine(ex); } }
private async Task RunCommandAsync(DiscordMessage message, DiscordUser author, DiscordChannel channel, IEnumerable <string> commandSegments, string commandAlias, BaseDiscordCommand command, DateTimeOffset start) { bool success = true; try { await channel.TriggerTypingAsync(); command = InstantateDiscordCommand(command.GetType()); _config.Happiness.TryGetValue(author.Id, out sbyte h); CommandContext context = new CommandContext(commandSegments.ToArray(), message, _discordClient) { Happiness = h, _logger = _loggerFactory.CreateLogger("Commands") }; context.AdditionalData["botContext"] = this; string[] cmdsegarr = commandSegments.ToArray(); CommandResult result = await command.RunCommand(cmdsegarr, context); LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] \"{command.Name}\" returned ReturnType.{result.ReturnType}.")); if (result.ReturnType != ReturnType.None) { if (result.ReturnType == ReturnType.Text && result.ResultText.Length > 2000) { for (int i = 0; i < result.ResultText.Length; i += 1993) { string str = result.ResultText.Substring(i, Math.Min(1993, result.ResultText.Length - i)); if (result.ResultText.StartsWith("```") && !str.StartsWith("```")) { str = "```" + str; } if (result.ResultText.EndsWith("```") && !str.EndsWith("```")) { str = str + "```"; } LogMessage?.Invoke(this, ($"Chunking message to {str.Length} chars")); await channel.SendMessageAsync(str); await Task.Delay(2000); } } else { if (!string.IsNullOrWhiteSpace(result.Attachment)) { await channel.SendFileAsync(result.Attachment, result.ResultText, embed : result.ResultEmbed); } else if (result.Stream != null || result.ReturnType == ReturnType.File) { if (result.Stream.Length <= 8 * 1024 * 1024) { await channel.SendFileAsync(result.Stream, result.FileName, result.ResultText, false, result.ResultEmbed); } else { await channel.SendMessageAsync("This command has resulted in an attachment that is over 8MB in size and cannot be sent."); } } else { await channel.SendMessageAsync(result.ResultText, embed : result.ResultEmbed); } } } RequestTelemetry request = InternalTools.GetRequestTelemetry(author, channel, command, start, success ? "200" : "500", success); foreach (var pair in result.InsightsData) { request.Properties.Add(pair); } _telemetryClient?.TrackRequest(request); _config.Happiness[author.Id] = (sbyte)((int)(context.Happiness).Clamp(sbyte.MinValue, sbyte.MaxValue)); } catch (BadArgumentsException ex) { Console.WriteLine(ex); await HandleBadRequest(message, author, channel, commandSegments, commandAlias, command, start); } catch (CommandException ex) { Console.WriteLine(ex); await channel.SendMessageAsync(ex.Message); _telemetryClient?.TrackRequest(InternalTools.GetRequestTelemetry(author, channel, command, start, "400", false)); } catch (ArgumentOutOfRangeException ex) { Console.WriteLine(ex); await channel.SendMessageAsync("Hey there! That's gonna cause some issues, no thanks!!"); _telemetryClient?.TrackRequest(InternalTools.GetRequestTelemetry(author, channel, command, start, "400", false)); } catch (Exception ex) { Console.WriteLine(ex); success = false; ManageException(message, author, channel, ex, command); sbyte h = 0; _config.Happiness?.TryGetValue(author.Id, out h); _config.Happiness[author.Id] = (sbyte)((int)h - 1).Clamp(sbyte.MinValue, sbyte.MaxValue); } finally { if (command is IDisposable disp) { disp.Dispose(); } } }
private async Task ProcessMessage(DiscordMessage message, DiscordUser author, DiscordChannel channel) { try { DateTimeOffset startTime = DateTimeOffset.Now; if (!string.IsNullOrWhiteSpace(message?.Content) && !author.IsBot && !author.IsCurrent && !_processedMessageIds.Contains(message.Id)) { if (message.Content.ToLower().StartsWith(_config.Prefix.ToLower())) { LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] Found command prefix, parsing...")); IEnumerable <string> commandSegments = Strings.SplitCommandLine(message.Content.Substring(_config.Prefix.Length)); foreach (IParseExtension extenstion in _parseExtensions) { commandSegments = extenstion.Parse(commandSegments, channel); } if (commandSegments.Any()) { string commandAlias = commandSegments.First().ToLower(); IEnumerable <BaseDiscordCommand> foundCommands = _commands.Where(c => c?.Aliases?.Any(a => a.ToLower() == commandAlias) == true); BaseDiscordCommand commandToRun = foundCommands.FirstOrDefault(); if (commandToRun != null) { if (foundCommands.Count() == 1) { await ExecuteCommandAsync(message, author, channel, commandSegments.Skip(1), commandAlias, commandToRun, startTime); } else if (commandSegments.Count() >= 2) { foundCommands = _assemblyCommands.FirstOrDefault(c => c.Key.Name.ToLowerInvariant() == commandAlias) .Value?.Where(c => c.Aliases.Contains(commandSegments.ElementAt(1).ToLower())); if (foundCommands != null && foundCommands.Count() == 1) { await ExecuteCommandAsync(message, author, channel, commandSegments.Skip(2), commandAlias, foundCommands.First(), startTime); } else { if (commandToRun != null) { await ExecuteCommandAsync(message, author, channel, commandSegments.Skip(1), commandAlias, foundCommands.First(), startTime); } else { LogMessage?.Invoke(this, ($"[{message.Channel.Guild?.Name ?? message.Channel.Name}] Unable to find command with alias \"{commandAlias}\".")); await InternalTools.SendTemporaryMessage(message, author, channel, $"```\r\n{commandAlias}: command not found!\r\n```"); _telemetryClient?.TrackRequest(InternalTools.GetRequestTelemetry(author, channel, null, startTime, "404", false)); } } } else if (commandToRun != null) { await ExecuteCommandAsync(message, author, channel, commandSegments.Skip(1), commandAlias, foundCommands.First(), startTime); } } } } } } catch (Exception ex) { Console.WriteLine(ex); } }
private async Task LoadPluginsAsync() { LogMessage?.Invoke(this, ""); List <string> dlls = new List <string>(); if (!liteMode) { dlls.AddRange(Directory.EnumerateFiles("Plugins").Where(f => Path.GetExtension(f) == ".dll")); dlls.Insert(0, Assembly.GetExecutingAssembly().Location); } foreach (string str in _config.AdditionalPluginDirectories) { dlls.AddRange(Directory.EnumerateFiles(str).Where(f => Path.GetExtension(f) == ".dll" || Path.GetExtension(f) == ".exe")); } LogMessage?.Invoke(this, $"{(_commands.Any() ? "Reloading" : "Loading")} {dlls.Count()} plugins..."); _commands.Clear(); _assemblyCommands.Clear(); _parseExtensions.Clear(); foreach (string dllPath in dlls) { try { Assembly assembly = Assembly.LoadFrom(dllPath); if (assembly != null && assembly.DefinedTypes.Any(t => t.GetInterfaces()?.Contains(typeof(ICommandsAssembly)) == true) && (!liteMode || assembly.FullName != Assembly.GetExecutingAssembly().FullName)) { LogMessage?.Invoke(this, $"Searching {Path.GetFileName(dllPath)} for commands."); ICommandsAssembly asm = null; List <BaseDiscordCommand> asmCommands = new List <BaseDiscordCommand>(); foreach (Type type in assembly.GetTypes()) { if (type.GetInterfaces().Contains(typeof(ICommandsAssembly))) { asm = (ICommandsAssembly)Activator.CreateInstance(type); } if (!liteMode) { if (type.GetInterfaces().Contains(typeof(IParseExtension))) { _parseExtensions.Add((IParseExtension)Activator.CreateInstance(type)); } if (type.GetInterfaces().Contains(typeof(IParamConverter))) { Tools.RegisterPatameterParseExtension((IParamConverter)Activator.CreateInstance(type)); } } if (type.IsSubclassOf(typeof(BaseDiscordCommand)) && !type.IsAbstract) { BaseDiscordCommand command = InstantateDiscordCommand(type); if (command != null) { asmCommands.Add(command); } } } if (asm == null) { LogMessage?.Invoke(this, $"Loaded {asmCommands.Count()} commands from {Path.GetFileName(dllPath)} without command assembly manifest. Categorised help will be unavailable for these commands."); LogMessage?.Invoke(this, "Consider adding an \"ICommandAssembly\" class as soon as possible."); } else { if (asm is IBotStartup s) { await s.Startup(_discordClient); } LogMessage?.Invoke(this, $"Loaded {asmCommands.Count()} plugins from {Path.GetFileName(dllPath)}!"); } _commands.AddRange(asmCommands); if (asm != null) { _assemblyCommands.Add(asm, asmCommands); } } } catch (Exception ex) { LogMessage?.Invoke(this, $"An {ex.GetType().Name} occured while loading from {Path.GetFileName(dllPath)}\n{ex.Message}"); } } LogMessage?.Invoke(this, ""); LogMessage?.Invoke(this, $"Plugins loaded!"); LogMessage?.Invoke(this, $"{_commands.Count} commands available: {string.Join(", ", _commands.Select(c => c.Name))}"); LogMessage?.Invoke(this, $"{_parseExtensions.Count} parse extensions available: {string.Join(", ", _parseExtensions.Select(c => c.Name))}"); LogMessage?.Invoke(this, ""); }
internal static bool CheckPermissions(DiscordClient client, DiscordUser author, DiscordChannel channel, BaseDiscordCommand command) { bool go = true; if (command.HasAttribute <OwnerAttribute>()) { if (author.Id == client.CurrentApplication.Owner.Id || author.Id == client.CurrentUser.Id) { return(true); } else { return(false); } } if (command.HasAttribute <RequiresGuildAttribute>() && channel.IsPrivate) { return(false); } if (author is DiscordMember memb) { Permissions p = memb.PermissionsIn(channel); go = p.HasFlag((memb.Id != client.CurrentUser.Id ? command.UserPermissions : command.BotPermissions) ?? command.RequiredPermissions) || p.HasFlag(Permissions.Administrator); } else { go = true; } return(go); }
internal static RequestTelemetry GetRequestTelemetry(DiscordUser author, DiscordChannel channel, BaseDiscordCommand command, DateTimeOffset start, string code, bool success) { RequestTelemetry tel = new RequestTelemetry(command?.GetType().Name ?? "N/A", start, DateTimeOffset.Now - start, code, success); tel.Properties.Add("invoker", author.Id.ToString()); tel.Properties.Add("channel", channel.Id.ToString()); tel.Properties.Add("guild", channel.Guild?.Id.ToString()); return(tel); }
public Task <CommandResult> Run(string alias = null) { DiscordEmbedBuilder embedBuilder = Context.GetEmbedBuilder(); var botContext = (BotContext)Context.AdditionalData["botContext"]; embedBuilder.WithFooter($"Lovingly made by @{Context.Client.CurrentApplication.Owner.Username}#{Context.Client.CurrentApplication.Owner.Discriminator} using D#+. My current prefix is `{botContext.Config.Prefix}`.", Context.Client.CurrentApplication.Owner.AvatarUrl); if (alias == null) { embedBuilder.WithAuthor("Listing Command Categories - WamBot 3.0.0", icon_url: Context.Client.CurrentApplication.Icon); foreach (var asm in (botContext.AssemblyCommands)) { IEnumerable <BaseDiscordCommand> availableCommands = GetAvailableCommands(asm.Value); if (availableCommands.Any()) { embedBuilder.AddField( $"{asm.Key.Name} Commands", $"{asm.Key.Description}\r\n" + $"`{string.Join(", ", GetAvailableCommands(asm.Value).Select(c => c.Aliases.FirstOrDefault()))}`", false); } } } else { BaseDiscordCommand command = GetAvailableCommands(botContext.Commands).FirstOrDefault(c => c.Aliases.Contains(alias.ToLower().Trim())); if (command != null) { embedBuilder.WithAuthor($"{command.Name} - WamBot 3.0.0", icon_url: Context.Client.CurrentApplication.Icon); embedBuilder.AddField("Description", command.Description, true); embedBuilder.AddField("Aliases", string.Join(", ", command.Aliases), true); if (command.Usage != null) { embedBuilder.AddField("Usage", $"```cs\r\n{botContext.Config.Prefix}{command.Aliases.First()} {command.Usage}\r\n```"); } } else { var asm = botContext.AssemblyCommands.FirstOrDefault(a => a.Key.Name.ToLower().Trim() == alias.ToLower().Trim()); if (asm.Value != null && asm.Key != null) { IEnumerable <BaseDiscordCommand> availableCommands = GetAvailableCommands(asm.Value); embedBuilder.WithAuthor($"{asm.Key.Name} Commands - WamBot 3.0.0", icon_url: Context.Client.CurrentApplication.Icon); embedBuilder.WithDescription(asm.Key.Description ?? "No description provided."); if (availableCommands.Any()) { foreach (BaseDiscordCommand c in availableCommands) { embedBuilder.AddField($"{c.Name} (`{c.Aliases.FirstOrDefault()}`)", c.Description, true); } } else { embedBuilder.AddField("No commands available", "This command group exits, but it either contains no commands, or you don't have access to any of them"); } } else { embedBuilder.AddField("Command not found.", $"That command doesn't seem to exist, or you don't have permission to run it! Run `{botContext.Config.Prefix}help` for a list of all commands!"); } } } return(Task.FromResult <CommandResult>(embedBuilder.Build())); }