Example #1
0
        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);
        }
Example #2
0
        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();
            }
        }
Example #3
0
        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```");
            }
        }
Example #4
0
        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);
            }
        }
Example #5
0
        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();
                }
            }
        }
Example #6
0
        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);
            }
        }
Example #7
0
        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, "");
        }
Example #8
0
        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);
        }
Example #9
0
        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);
        }
Example #10
0
        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()));
        }