示例#1
0
    /// <summary>
    /// Using the given subcommand, retrieve the parsed subcommand information (such as arguments. NOT the values, just what the argument 'is')
    /// </summary>
    /// <param name="subcommand"></param>
    /// <returns></returns>
    public ModuleSubcommandInfo?ParseSubcommandInfo(Table subcommand)
    {
        if (subcommand == null)
        {
            return(null);
        }

        var result = new ModuleSubcommandInfo()
        {
            Arguments    = new List <ModuleArgumentInfo?>(),
            Description  = subcommand.Get(config.DescriptionKey)?.String,
            FunctionName = subcommand.Get(config.SubcommandFunctionKey)?.String
        };

        //Find the args
        var subcmdargs = subcommand.Get(config.ArgumentsKey).Table;

        //Now we can REALLY parse the args!
        if (subcmdargs != null)
        {
            foreach (var arg in subcmdargs.Values.Select(x => x.String))
            {
                if (string.IsNullOrWhiteSpace(arg))
                {
                    throw new InvalidOperationException("Argument specifier was the wrong type! It needs to be a string!");
                }

                var argparts = arg.Split("_".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                if (argparts.Length != 2)
                {
                    throw new InvalidOperationException("Argument specifier not in the right format! name_type");
                }

                //Remember: all we're doing is figuring out the information available from the argument list
                result.Arguments.Add(new ModuleArgumentInfo()
                {
                    name = argparts[0], type = argparts[1]
                });
            }
        }

        return(result);
    }
示例#2
0
    /// <summary>
    /// Using the given argument information, fill (as in actually mutate) the given list of argument values
    /// </summary>
    /// <param name="argumentInfos"></param>
    /// <param name="arglist"></param>
    /// <param name="existingArgs"></param>
    public void ParseArgs(ModuleSubcommandInfo subcommandInfo, string arglist, List <object> existingArgs)
    {
        using var search = dbFactory.CreateSearch();
        var forcedFinal = false;

        foreach (var argInfo in subcommandInfo.Arguments)
        {
            //forcedFinal is specifically for "freeform" arguments, which MUST come at the end! It just
            //eats up the rest of the input
            if (forcedFinal)
            {
                throw new InvalidOperationException("No argument can come after a 'freeform' argument!");
            }

            //Kind of wasteful, but just easier to always start with a clean slate after ripping args out
            arglist = arglist.Trim();

            //Matcher function for generic regex matches. Useful for words, users, etc.
            Action <string, Action <Match> > genericMatch = (r, a) =>
            {
                var regex = new Regex(r, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                var match = regex.Match(arglist);

                //An argument of a given type must ALWAYS be a pure match.
                if (!match.Success)
                {
                    throw new RequestException($"Parse error in argument '{argInfo?.name}', not of type '{argInfo?.type}'");
                }

                //Get rid of the argument from the remaining arglist
                arglist = regex.Replace(arglist, "");

                //Try to do whatever the user wanted (probably adding to existingArgs)
                try
                {
                    a(match);
                }
                catch (Exception ex)
                {
                    throw new RequestException($"{ex.Message} (Parse error in argument '{argInfo?.name}' of type '{argInfo?.type}')", ex);
                }
            };

            //Parse arguments differently based on type
            switch (argInfo?.type)
            {
            case "user":
                genericMatch(@"^([0-9]+)(\([^\s]+\))?", m => {
                    //Check if user exists!
                    var uid = long.Parse(m.Groups[1].Value);
                    //Yes this is BLOCKING, this entire module system is blocking because lua/etc
                    //var users = userSource.SimpleSearchAsync(new UserSearch() { Ids = new List<long>{uid}}).Result;
                    try {
                        var user = search.GetById <UserView>(RequestType.user, uid);
                    }
                    catch (NotFoundException ex) {
                        throw new RequestException($"User not found: {uid}", ex);
                    }
                    existingArgs.Add(uid);
                });
                break;

            case "int":
                // This will throw an exception on error, not telling the user much
                genericMatch(@"^([0-9]+)(\([^\s]+\))?", m => { existingArgs.Add(long.Parse(m.Groups[1].Value)); });
                break;

            case "word":
                genericMatch(@"^([^\s]+)", m => { existingArgs.Add(m.Groups[1].Value); });
                break;

            case "freeform":
                existingArgs.Add(arglist);     //Just append whatever is left
                forcedFinal = true;
                break;

            default:
                throw new InvalidOperationException($"Unknown argument type: {argInfo?.type} ({argInfo?.name})");
            }
        }
    }
        /// <summary>
        /// Run the given argument list (as taken directly from a request) with the given module for the given requester. Parses the arglist,
        /// finds the module, runs the command and returns the output. Things like sending messages to other users is also performed, but
        /// against the database and in the background
        /// </summary>
        /// <param name="module"></param>
        /// <param name="arglist"></param>
        /// <param name="requester"></param>
        /// <returns></returns>
        public string RunCommand(string module, string arglist, Requester requester, long parentId = 0)
        {
            LoadedModule mod = GetModule(module);

            if (mod == null)
            {
                throw new BadRequestException($"No module with name {module}");
            }

            arglist = arglist?.Trim();

            //By DEFAULT, we call the default function with whatever is leftover in the arglist
            var           cmdfuncname = config.DefaultFunction;
            List <object> scriptArgs  = new List <object> {
                requester.userId
            };                                                               //Args always includes the calling user first

            //Can only check for subcommands if there's an argument list!
            if (arglist != null)
            {
                var match = Regex.Match(arglist, @"^\s*(\w+)\s*(.*)$");

                //NOTE: Subcommand currently case sensitive!
                if (match.Success)
                {
                    var newArglist = match.Groups[2].Value.Trim();
                    ModuleSubcommandInfo subcommandInfo = null;
                    mod.subcommands.TryGetValue(match.Groups[1].Value, out subcommandInfo); //ParseSubcommandInfo(mod, match.Groups[1].Value);

                    //Special re-check: sometimes, we can have commands that have NO subcommand name, or the "blank" subcommand. Try that one.
                    if (subcommandInfo == null) //subcommandExists == true)
                    {
                        newArglist = arglist;   //undo the parsing
                        mod.subcommands.TryGetValue("", out subcommandInfo);
                        //subcommandInfo = ParseSubcommandInfo(mod, "");
                    }

                    //There is a defined subcommand, which means we may need to parse the input and call
                    //a different function than the default!
                    if (subcommandInfo != null) //subcommandExists == true) //subcommandInfo != null)
                    {
                        arglist     = newArglist;
                        cmdfuncname = subcommandInfo.FunctionName;

                        //Arguments were defined! From this point on, we're being VERY strict with parsing! This could throw exceptions!
                        if (subcommandInfo.Arguments != null)
                        {
                            ParseArgs(subcommandInfo, arglist, scriptArgs);
                        }
                    }
                }
            }

            if (!mod.script.Globals.Keys.Any(x => x.String == cmdfuncname))
            {
                throw new BadRequestException($"Command function '{cmdfuncname}' not found in module {module}");
            }

            //Oops, didn't fill up the arglist with anything! Careful, this is dangerous!
            if (scriptArgs.Count == 1)
            {
                scriptArgs.Add(arglist);
            }

            //We lock so nobody else can run commands while we're running them. This guarantees thread safety
            //within the modules so they don't have to worry about it.
            lock (moduleLocks.GetOrAdd(module, s => new object()))
            {
                using (mod.dataConnection = new SqliteConnection(config.ModuleDataConnectionString))
                {
                    mod.dataConnection.Open();
                    mod.currentRequester = requester;
                    mod.currentFunction  = cmdfuncname;
                    mod.currentParentId  = parentId;
                    mod.currentArgs      = arglist;
                    DynValue res = mod.script.Call(mod.script.Globals[cmdfuncname], scriptArgs.ToArray());
                    return(res.String);
                }
            }
        }