You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
3.1 KiB
100 lines
3.1 KiB
-- Lua Console module — execute Lua code in the game's main thread
|
|
local M = {}
|
|
|
|
-- Serialize a Lua value into a readable string
|
|
local function serialize(val, depth)
|
|
depth = depth or 0
|
|
if depth > 4 then return "..." end
|
|
|
|
local t = type(val)
|
|
if t == "nil" then
|
|
return "nil"
|
|
elseif t == "string" then
|
|
if #val > 200 then
|
|
return '"' .. val:sub(1, 200):gsub('[\n\r]', '\\n') .. '..." [' .. #val .. ' chars]'
|
|
end
|
|
return '"' .. val:gsub('[\n\r]', '\\n'):gsub('"', '\\"') .. '"'
|
|
elseif t == "number" or t == "boolean" then
|
|
return tostring(val)
|
|
elseif t == "table" then
|
|
local items = {}
|
|
local n = 0
|
|
-- Array part
|
|
for i, v in ipairs(val) do
|
|
if n >= 20 then
|
|
items[#items + 1] = "... +" .. (#val - n) .. " more"
|
|
break
|
|
end
|
|
items[#items + 1] = serialize(v, depth + 1)
|
|
n = n + 1
|
|
end
|
|
-- Hash part
|
|
for k, v in pairs(val) do
|
|
if type(k) ~= "number" or k < 1 or k > #val or k ~= math.floor(k) then
|
|
if n >= 20 then
|
|
items[#items + 1] = "..."
|
|
break
|
|
end
|
|
local ks = type(k) == "string" and k or "[" .. tostring(k) .. "]"
|
|
items[#items + 1] = ks .. " = " .. serialize(v, depth + 1)
|
|
n = n + 1
|
|
end
|
|
end
|
|
if #items == 0 then return "{}" end
|
|
return "{ " .. table.concat(items, ", ") .. " }"
|
|
elseif t == "function" then
|
|
return "function: " .. tostring(val)
|
|
else
|
|
return tostring(val)
|
|
end
|
|
end
|
|
|
|
function M.init(fw)
|
|
local static_dir = fw.modules_dir .. "/console/static"
|
|
fw.register_module("console", static_dir)
|
|
|
|
fw.on_api("console", "exec", function(body)
|
|
local ok, data = pcall(fw.json_decode, body)
|
|
if not ok or not data or not data.code then
|
|
return '{"ok":false,"error":"invalid body"}'
|
|
end
|
|
|
|
local code = data.code
|
|
if #code == 0 then
|
|
return '{"ok":false,"error":"empty code"}'
|
|
end
|
|
|
|
-- Try as expression first (return value), then as statement
|
|
local fn, err = loadstring("return " .. code)
|
|
if not fn then
|
|
fn, err = loadstring(code)
|
|
end
|
|
if not fn then
|
|
return fw.json_encode({ok = false, error = "Compile: " .. tostring(err)})
|
|
end
|
|
|
|
local results = {pcall(fn)}
|
|
local success = table.remove(results, 1)
|
|
|
|
if not success then
|
|
return fw.json_encode({ok = false, error = tostring(results[1])})
|
|
end
|
|
|
|
-- Format results
|
|
local parts = {}
|
|
for i, v in ipairs(results) do
|
|
parts[i] = serialize(v, 0)
|
|
end
|
|
|
|
local output = table.concat(parts, ", ")
|
|
if #parts == 0 then output = "nil" end
|
|
|
|
fw.log("INFO", "CONSOLE", "Exec: " .. code:sub(1, 80) .. " => " .. output:sub(1, 80))
|
|
return fw.json_encode({ok = true, result = output, count = #parts})
|
|
end)
|
|
|
|
fw.log("INFO", "CONSOLE", "Module loaded")
|
|
end
|
|
|
|
return M
|