------------------------------------------------------------------ -- VEAF remote callback functions for DCS World -- By zip (2020) -- -- Features: -- --------- -- * This module offers support for calling script from a web server or a server hook -- -- See the documentation : https://veaf.github.io/documentation/ ------------------------------------------------------------------ veafRemote = {} ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Global settings. Stores the script constants ------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Identifier. All output in DCS.log will start with this. veafRemote.Id = "REMOTE" --- Version. veafRemote.Version = "2.3.0" -- trace level, specific to this module --veafRemote.LogLevel = "trace" veaf.loggers.new(veafRemote.Id, veafRemote.LogLevel) -- if false, SLMOD will not be called for regular commands veafRemote.USE_SLMOD = false -- if false, SLMOD will never be called veafRemote.USE_SLMOD_FOR_SPECIAL_COMMANDS = false veafRemote.CommandStarter = "_remote" veafRemote.MIN_LEVEL_FOR_MARKER = 10 ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Do not change anything below unless you know what you are doing! ------------------------------------------------------------------------------------------------------------------------------------------------------------- veafRemote.monitoredCommands = {} veafRemote.remoteUsers = {} veafRemote.remoteUnitsPilots = {} ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Utility methods ------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- NIOD callbacks ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRemote.addNiodCallback(name, parameters, code) if niod then veaf.loggers.get(veafRemote.Id):info("Adding NIOD function "..name) niod.functions[name] = function(payload) -- start of inline function veaf.loggers.get(veafRemote.Id):debug(string.format("niod callback [%s] was called with payload %s", veaf.p(name), veaf.p(payload))) local errors = {} -- check mandatory parameters presence for parameterName, parameterData in pairs(parameters) do veaf.loggers.get(veafRemote.Id):trace(string.format("checking if parameter [%s] is mandatory", veaf.p(parameterName))) if parameterData and parameterData.mandatory then if not (payload and payload[parameterName]) then local text = "missing mandatory parameter "..parameterName veaf.loggers.get(veafRemote.Id):trace(text) table.insert(errors, text) end end end -- check parameters type if payload then for parameterName, value in pairs(payload) do local parameter = parameters[parameterName] if not parameter then table.insert(errors, "unknown parameter "..parameterName) elseif value and not(type(value) == parameter.type) then local text = string.format("parameter %s should have type %s, has %s ", parameterName, parameter.type, type(value)) veaf.loggers.get(veafRemote.Id):trace(text) table.insert(errors, text) end end end -- stop on error if #errors > 0 then local errorMessage = "" for _, error in pairs(errors) do errorMessage = errorMessage .. "\n" .. error end veaf.loggers.get(veafRemote.Id):error(string.format("niod callback [%s] was called with incorrect parameters :", veaf.p(name), errorMessage)) return errorMessage else veaf.loggers.get(veafRemote.Id):trace(string.format("payload = %s", veaf.p(payload))) veaf.loggers.get(veafRemote.Id):trace(string.format("unpacked payload = %s", veaf.p(veaf.safeUnpack(payload)))) local status, retval = pcall(code,veaf.safeUnpack(payload)) if status then return retval else return "an error occured : "..veaf.p(status) end end end -- of inline function else veaf.loggers.get(veafRemote.Id):error("NIOD is not loaded !") end end function veafRemote.addNiodCommand(name, command) veafRemote.addNiodCallback( name, { parameters={ mandatory=false, type="string"}, x={ mandatory=false, type="number"}, y={ mandatory=false, type="number"}, z={ mandatory=false, type="number"}, silent={ mandatory=false, type="boolean"} }, function(parameters, x, y, z, silent) veaf.loggers.get(veafRemote.Id):debug(string.format("niod->command %s (%s, %s, %s, %s, %s)", veaf.p(parameters), veaf.p(x), veaf.p(y), veaf.p(z), veaf.p(silent))) return veafRemote.executeCommand({x=x or 0, y=y or 0, z=z or 0}, command..parameters) end ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- default endpoints list ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRemote.buildDefaultList() local TEST = false if TEST then -- test veafRemote.addNiodCallback( "test", { param1S_M={ mandatory=true, type="string"}, param2S={ mandatory=false, type="string"}, param3N={ mandatory=false, type="number"}, param4B={ mandatory=false, type="boolean"}, }, function(param1S_M, param2S, param3N, param4B) local text = string.format("niod.test(%s, %s, %s, %s)", veaf.p(param1S_M), veaf.p(param2S), veaf.p(param3N), veaf.p(param4B)) veaf.loggers.get(veafRemote.Id):debug(text) trigger.action.outText(text, 15) end ) -- login veafRemote.addNiodCallback( "login", { password={ mandatory=true, type="string"}, timeout={ mandatory=false, type="number"}, silent={ mandatory=false, type="boolean"} }, function(password, timeout, silent) veaf.loggers.get(veafRemote.Id):debug(string.format("niod.login(%s, %s, %s)",veaf.p(password), veaf.p(timeout),veaf.p(silent))) -- TODO remove password from log if veafSecurity.checkPassword_L1(password) then veafSecurity.authenticate(timeout) return "Mission is unlocked" else return "wrong password" end end ) end end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Event handler functions. ------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Function executed when a mark has changed. This happens when text is entered or changed. function veafRemote.onEventMarkChange(eventPos, event) if veafRemote.executeCommand(eventPos, event.text) then -- Delete old mark. veaf.loggers.get(veafRemote.Id):trace(string.format("Removing mark # %d.", event.idx)) trigger.action.removeMark(event.idx) end end function veafRemote.executeCommand(eventPos, eventText) veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.executeCommand(eventText=[%s])", tostring(eventText))) -- Check if marker has a text and the veafRemote.CommandStarter keyphrase. if eventText ~= nil and eventText:lower():find(veafRemote.CommandStarter) then -- Analyse the mark point text and extract the keywords. local command, password = veafRemote.markTextAnalysis(eventText) if command then -- do the magic return veafRemote.executeRemoteCommand(command, password) end end end --- Extract keywords from mark text. function veafRemote.markTextAnalysis(text) veaf.loggers.get(veafRemote.Id):trace(string.format("veafRemote.markTextAnalysis(text=[%s])", tostring(text))) if text then -- extract command and password local password, command = text:match(veafRemote.CommandStarter.."#?([^%s]*)%s+(.+)") if command then veaf.loggers.get(veafRemote.Id):trace(string.format("command = [%s]", command)) return command, password end end return nil end -- execute a command function veafRemote.executeRemoteCommand(command, password) local command = command or "" local password = password or "" veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.executeRemoteCommand([%s])",command)) if not(veafSecurity.checkPassword_L1(password)) then veaf.loggers.get(veafRemote.Id):error(string.format("veafRemote.executeRemoteCommand([%s]) - bad or missing password",command)) trigger.action.outText("Bad or missing password",5) return false end local commandData = veafRemote.monitoredCommands[command:lower()] if commandData then local scriptToExecute = commandData.script veaf.loggers.get(veafRemote.Id):trace(string.format("found script [%s] for command [%s]", scriptToExecute, command)) local authorized = (not(commandData.requireAdmin)) or (veafSecurity.checkSecurity_L9(password)) if not authorized then return false else local result, err = mist.utils.dostring(scriptToExecute) if result then veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.executeRemoteCommand() - lua code was successfully called for script [%s]", scriptToExecute)) return true else veaf.loggers.get(veafRemote.Id):error(string.format("veafRemote.executeRemoteCommand() - error [%s] calling lua code for script [%s]", err, scriptToExecute)) return false end end else veaf.loggers.get(veafRemote.Id):warn(string.format("veafRemote.executeRemoteCommand : cannot find command [%s]",command or "")) end return false end -- execute command from the remote interface (see VEAF-server-hook.lua) function veafRemote.executeCommandFromRemote(username, level, unitName, veafModule, command) veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.executeCommandFromRemote([%s], [%s], [%s], [%s], [%s])", veaf.p(username), veaf.p(level), veaf.p(unitName), veaf.p(veafModule), veaf.p(command))) --local _user = veafRemote.getRemoteUser(username) --veaf.loggers.get(veafRemote.Id):trace(string.format("_user = [%s]",veaf.p(_user))) --if not _user then -- return false --end if not veafModule or not username or not command then return false end local _user = { name = username, level = tonumber(level or "-1")} local _parameters = { _user, username, unitName, command } local _status, _retval local _module = veafModule:lower() if _module == "air" then veaf.loggers.get(veafRemote.Id):debug(string.format("running veafCombatMission.executeCommandFromRemote")) _status, _retval = pcall(veafCombatMission.executeCommandFromRemote, _parameters) elseif _module == "point" then veaf.loggers.get(veafRemote.Id):debug(string.format("running veafNamedPoints.executeCommandFromRemote")) _status, _retval = pcall(veafNamedPoints.executeCommandFromRemote, _parameters) elseif _module == "atis" or _module == "atc" or _module == "weather" then veaf.loggers.get(veafRemote.Id):debug(string.format("running veafWeather.executeCommandFromRemote")) _status, _retval = pcall(veafWeather.executeCommandFromRemote, _parameters) elseif _module == "alias" then veaf.loggers.get(veafRemote.Id):debug(string.format("running veafShortcuts.executeCommandFromRemote")) _status, _retval = pcall(veafShortcuts.executeCommandFromRemote, _parameters) elseif _module == "carrier" then veaf.loggers.get(veafRemote.Id):debug(string.format("running veafShortcuts.executeCommandFromRemote")) _status, _retval = pcall(veafCarrierOperations.executeCommandFromRemote, _parameters) elseif _module == "secu" then veaf.loggers.get(veafRemote.Id):debug(string.format("running veafSecurity.executeCommandFromRemote")) _status, _retval = pcall(veafSecurity.executeCommandFromRemote, _parameters) else veaf.loggers.get(veafRemote.Id):error(string.format("Module not found : [%s]", veaf.p(veafModule))) return false end veaf.loggers.get(veafRemote.Id):trace(string.format("_status = [%s]",veaf.p(_status))) veaf.loggers.get(veafRemote.Id):trace(string.format("_retval = [%s]",veaf.p(_retval))) if not _status then veaf.loggers.get(veafRemote.Id):error(string.format("Error when [%s] tried running [%s] in module [%s]; it returned %s", veaf.p(_user.name), veaf.p(_parameters), veaf.p(veafModule), veaf.p(_retval))) else veaf.loggers.get(veafRemote.Id):info(string.format("[%s] ran [%s] in module [%s]; it returned %s", veaf.p(_user.name), veaf.p(_parameters), veaf.p(veafModule), veaf.p(_retval))) end return _status end -- register a user from the server function veafRemote.registerUser(username, userpower, ucid) veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.registerUser([%s], [%s], [%s])",veaf.p(username), veaf.p(userpower), veaf.p(ucid))) if not username or not ucid then return false end veafRemote.remoteUsers[username:lower()] = { name = username, level = tonumber(userpower or "-1"), ucid = ucid } end -- register a user slot from the server; called when the player changes slot function veafRemote.registerUserSlot(username, ucid, unitName) veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.registerUserSlot([%s], [%s], [%s])",veaf.p(username), veaf.p(ucid), veaf.p(unitName))) if not username or not unitName then return false end local remoteUser = veafRemote.remoteUsers[username:lower()] if not remoteUser then remoteUser = { name = username, ucid = ucid} end local previousUnit = remoteUser.unitName remoteUser.unitName = unitName -- can be nil if the player got out of the unit -- unregister the previous unit, if any if previousUnit then veafRemote.remoteUnitsPilots[previousUnit] = nil end -- register the current unit, if any if unitName then veafRemote.remoteUnitsPilots[unitName] = remoteUser end end -- return a user from the server table function veafRemote.getRemoteUser(username) veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.getRemoteUser([%s])",veaf.p(username))) veaf.loggers.get(veafRemote.Id):trace(string.format("veafRemote.remoteUsers = [%s]",veaf.p(veafRemote.remoteUsers))) if not username then return nil end return veafRemote.remoteUsers[username:lower()] end -- return a user from the server units table function veafRemote.getRemoteUserFromUnit(unitName) veaf.loggers.get(veafRemote.Id):debug(string.format("veafRemote.getRemoteUserFromUnit([%s])",veaf.p(unitName))) veaf.loggers.get(veafRemote.Id):trace(string.format("veafRemote.remoteUnitsPilots = [%s]",veaf.p(veafRemote.remoteUnitsPilots))) if not unitName then return nil end return veafRemote.remoteUnitsPilots[unitName] end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- initialisation ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRemote.initialize() veaf.loggers.get(veafRemote.Id):info("Initializing module") veafRemote.buildDefaultList() veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafRemote.onEventMarkChange) end veaf.loggers.get(veafRemote.Id):info(string.format("Loading version %s", veafRemote.Version))