private async Task HandleCommandsAsync(DiscordClient sender, MessageCreateEventArgs e)
        {
            if (e.Author.IsBot) // bad bot
            {
                return;
            }

            if (!this.Config.EnableDms && e.Channel.IsPrivate)
            {
                return;
            }

            var mpos = -1;

            if (this.Config.EnableMentionPrefix)
            {
                mpos = e.Message.GetMentionPrefixLength(this.Client.CurrentUser);
            }

            if (this.Config.StringPrefixes?.Any() == true)
            {
                foreach (var pfix in this.Config.StringPrefixes)
                {
                    if (mpos == -1 && !string.IsNullOrWhiteSpace(pfix))
                    {
                        mpos = e.Message.GetStringPrefixLength(pfix, this.Config.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
                    }
                }
            }

            if (mpos == -1 && this.Config.PrefixResolver != null)
            {
                mpos = await this.Config.PrefixResolver(e.Message).ConfigureAwait(false);
            }

            if (mpos == -1)
            {
                return;
            }

            ReadOnlyMemory <char> pfx = e.Message.Content.AsMemory().Slice(0, mpos);
            ReadOnlyMemory <char> cnt = e.Message.Content.AsMemory().Slice(mpos);

            var __    = 0;
            var fname = cnt.ExtractNextArgument(ref __);

            var cmd = this.FindCommand(cnt, out string args);
            var ctx = this.CreateContext(e.Message, pfx.ToString(), cmd, args);

            if (cmd == null)
            {
                await this._error.InvokeAsync(this, new CommandErrorEventArgs { Context = ctx, Exception = new CommandNotFoundException(fname) }).ConfigureAwait(false);

                return;
            }

            await this.Config.CommandExecutor.ExecuteAsync(ctx).ConfigureAwait(false);
        }
        /// <summary>
        /// Finds a specified command by its qualified name, then separates arguments.
        /// </summary>
        /// <param name="commandString">Qualified name of the command, optionally with arguments.</param>
        /// <param name="rawArguments">Separated arguments.</param>
        /// <returns>Found command or null if none was found.</returns>
        public Command FindCommand(ReadOnlyMemory <char> commandString, out string rawArguments)
        {
            rawArguments = null;
            var ignoreCase = !this.Config.CaseSensitive;
            var pos        = 0;
            var next       = commandString.ExtractNextArgument(ref pos);

            if (next == null)
            {
                return(null);
            }

            if (!this.RegisteredCommands.TryGetValue(next, out var cmd))
            {
                if (!ignoreCase)
                {
                    return(null);
                }

                next = next.ToLowerInvariant();
                var cmdKvp = this.RegisteredCommands.FirstOrDefault(x => x.Key.ToLowerInvariant() == next);
                if (cmdKvp.Value == null)
                {
                    return(null);
                }

                cmd = cmdKvp.Value;
            }

            if (!(cmd is CommandGroup))
            {
                rawArguments = commandString.Span.Slice(pos).Trim().ToString();
                return(cmd);
            }

            while (cmd is CommandGroup)
            {
                var cm2    = cmd as CommandGroup;
                var oldPos = pos;
                next = commandString.ExtractNextArgument(ref pos);
                if (next == null)
                {
                    break;
                }

                if (ignoreCase)
                {
                    next = next.ToLowerInvariant();
                    cmd  = cm2.Children.FirstOrDefault(x => x.Name.ToLowerInvariant() == next || x.Aliases?.Any(xx => xx.ToLowerInvariant() == next) == true);
                }
                else
                {
                    cmd = cm2.Children.FirstOrDefault(x => x.Name == next || x.Aliases?.Contains(next) == true);
                }

                if (cmd == null)
                {
                    cmd = cm2;
                    pos = oldPos;
                    break;
                }
            }

            rawArguments = commandString.Span.Slice(pos).Trim().ToString();
            return(cmd);
        }