mirror of
https://github.com/mt-mods/home_workshop_modpack.git
synced 2025-01-25 17:40:23 +01:00
add lua sandbox, tabs, stuff
This commit is contained in:
parent
2593c55ee3
commit
fa1f4894af
133
computers/api.lua
Normal file
133
computers/api.lua
Normal file
@ -0,0 +1,133 @@
|
||||
--[[
|
||||
computers.sandbox code is from https://github.com/kikito/lua-sandbox
|
||||
License MIT 2013 Enrique García Cota
|
||||
additionally modified by wsor to work with lua 5.1/luajit
|
||||
modifications include:
|
||||
* additional protections
|
||||
* tweaking of structure
|
||||
* modifications to the default env
|
||||
]]
|
||||
computers.sandbox = {}
|
||||
computers.api = {}
|
||||
|
||||
--default sandbox enviroment used if none is provided
|
||||
computers.sandbox.default_env = {}
|
||||
|
||||
([[
|
||||
_VERSION assert error ipairs next pairs
|
||||
select tonumber tostring type unpack
|
||||
|
||||
math.abs math.acos math.asin math.atan math.atan2 math.ceil
|
||||
math.cos math.cosh math.deg math.exp math.fmod math.floor
|
||||
math.frexp math.huge math.ldexp math.log math.log10 math.max
|
||||
math.min math.modf math.pi math.pow math.rad math.random
|
||||
math.sin math.sinh math.sqrt math.tan math.tanh
|
||||
|
||||
os.clock os.difftime os.time
|
||||
|
||||
string.byte string.char string.format
|
||||
string.len string.lower string.reverse
|
||||
string.sub string.upper
|
||||
|
||||
table.insert table.maxn table.remove table.sort table.concat
|
||||
]]):gsub('%S+', function(id)
|
||||
local module, method = id:match('([^%.]+)%.([^%.]+)')
|
||||
if module then
|
||||
computers.sandbox.default_env[module] = computers.sandbox.default_env[module] or {}
|
||||
computers.sandbox.default_env[module][method] = _G[module][method]
|
||||
else
|
||||
computers.sandbox.default_env[id] = _G[id]
|
||||
end
|
||||
end)
|
||||
|
||||
--takes a code string, returns a sandboxed function to exicute
|
||||
function computers.sandbox.loadstring(code, options)
|
||||
local defaults = {
|
||||
env = table.copy(computers.sandbox.default_env),
|
||||
quota = 5000
|
||||
}
|
||||
|
||||
options = options or {}
|
||||
for k, v in pairs(defaults) do
|
||||
if not options[k] then options[k] = v end
|
||||
end
|
||||
|
||||
assert(type(code) == "string", "[computers.sandbox]: passed is not a string")
|
||||
|
||||
local env = options.env
|
||||
env._G = env
|
||||
|
||||
if code:byte(1) == 27 then
|
||||
minetest.log("warning", "[computers.sandbox]: attempted bytecode execution termminated")
|
||||
return nil, "bytecode disallowed"
|
||||
end
|
||||
|
||||
local f, err = loadstring(code)
|
||||
if not f then return nil, err end
|
||||
setfenv(f, env)
|
||||
|
||||
if jit then jit.off(f, true) end
|
||||
|
||||
local function timeout()
|
||||
debug.sethook()
|
||||
error("quota exceeded: " .. tostring(options.quota))
|
||||
end
|
||||
|
||||
return function(...)
|
||||
debug.sethook(timeout, "", options.quota)
|
||||
local string_metatable = getmetatable("")
|
||||
assert(string_metatable.__index == string, "[computers.sandbox]: error with the string metatable")
|
||||
string_metatable.__index = env.string
|
||||
|
||||
local status, ret = pcall(f, ...)
|
||||
|
||||
debug.sethook()
|
||||
string_metatable.__index = string
|
||||
|
||||
if not status then error(ret) end
|
||||
return ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--supports string input or table env input with a default env of the base
|
||||
function computers.sandbox.merge_env(nenv, denv)
|
||||
local base_env = table.copy(denv or computers.sandbox.default_env)
|
||||
|
||||
if type(nenv) == "table" then
|
||||
for k, v in pairs(nenv) do
|
||||
base_env[k] = base_env[k] or {}
|
||||
for key, value in pairs(v) do
|
||||
base_env[k][key] = value
|
||||
end
|
||||
end
|
||||
elseif type(nenv) == "string" then
|
||||
nenv:gsub('%S+', function(id)
|
||||
local module, method = id:match('([^%.]+)%.([^%.]+)')
|
||||
if module then
|
||||
base_env[module] = base_env[module] or {}
|
||||
base_env[module][method] = _G[module][method]
|
||||
else
|
||||
base_env[id] = _G[id]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return base_env
|
||||
end
|
||||
|
||||
function computers.api.get_dir_keyed_list(path, is_dir)
|
||||
local files = minetest.get_dir_list(path, is_dir)
|
||||
local keyed = {}
|
||||
for _, file in pairs(files) do
|
||||
keyed[file] = true
|
||||
end
|
||||
|
||||
return keyed
|
||||
end
|
||||
|
||||
function computers.api.chat_send_player(player, msg)
|
||||
local name = player
|
||||
if type(name) == "userdata" then name = player:get_player_name() end
|
||||
minetest.chat_send_player(name, msg)
|
||||
end
|
7
computers/commands/exit/conf.json
Normal file
7
computers/commands/exit/conf.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "exit",
|
||||
"license": "MIT",
|
||||
"version": 0.1,
|
||||
"engine": 0.42,
|
||||
"help": "exits the computer"
|
||||
}
|
7
computers/commands/exit/init.lua
Normal file
7
computers/commands/exit/init.lua
Normal file
@ -0,0 +1,7 @@
|
||||
function exit(pos, input, data)
|
||||
minetest.close_formspec(data.player:get_player_name(), "")
|
||||
|
||||
return "you shouldnt see this"
|
||||
end
|
||||
|
||||
return exit
|
7
computers/commands/expr/conf.json
Normal file
7
computers/commands/expr/conf.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "expr",
|
||||
"license": "MIT",
|
||||
"version": 0.1,
|
||||
"engine": 0.42,
|
||||
"help": "exicutes mathmatical expresions"
|
||||
}
|
14
computers/commands/expr/init.lua
Normal file
14
computers/commands/expr/init.lua
Normal file
@ -0,0 +1,14 @@
|
||||
function expr(pos, input, data)
|
||||
local output
|
||||
local func = computers.sandbox.loadstring("return " .. input, {env={}})
|
||||
|
||||
if func then output = func() end
|
||||
|
||||
if type(output) ~= "number" or output == nil then
|
||||
return "Error: invalid or missing input"
|
||||
end
|
||||
|
||||
return tostring(output)
|
||||
end
|
||||
|
||||
return expr
|
7
computers/commands/nano/conf.json
Normal file
7
computers/commands/nano/conf.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "nano",
|
||||
"license": "MIT",
|
||||
"version": 0.1,
|
||||
"engine": 0.42,
|
||||
"help": "allows editing of files"
|
||||
}
|
113
computers/commands/nano/init.lua
Normal file
113
computers/commands/nano/init.lua
Normal file
@ -0,0 +1,113 @@
|
||||
function nano(pos, input, data)
|
||||
local path = computers.devicepath .. "/" .. minetest.hash_node_position(pos) .. data.element.pwd
|
||||
local files = computers.api.get_dir_keyed_list(path, false)
|
||||
|
||||
if not input then return "no file designated" end
|
||||
if not files[input:split(" ")[1]] then return "file " .. input:split(" ")[1] .. " not found" end
|
||||
if computers.formspec.get_index_by_name(
|
||||
computers.formspec.registered_kast[data.player:get_player_name()],
|
||||
"nano_btn"
|
||||
) then return "nano is already open, please close it" end
|
||||
|
||||
local f = io.open(path .. "/" .. input:split(" ")[1])
|
||||
if not f then return "error reading file" end
|
||||
local file = f:read("*all")
|
||||
f:close()
|
||||
|
||||
--clean up if there exists a legacy nano_ctn
|
||||
local nctn = computers.formspec.get_index_by_name(
|
||||
computers.formspec.registered_kast[data.player:get_player_name()],
|
||||
"nano_ctn"
|
||||
)
|
||||
if nctn then
|
||||
local fs = computers.formspec.registered_kast[data.player:get_player_name()]
|
||||
table.remove(fs, nctn)
|
||||
computers.formspec.registered_kast[data.player:get_player_name()] = fs
|
||||
minetest.chat_send_all(dump(fs))
|
||||
end
|
||||
|
||||
local form = computers.gui.add_tab(data.player, "Nano", {
|
||||
type = "container",
|
||||
name = "nano_ctn",
|
||||
state = 1,
|
||||
x = 0,
|
||||
y = 1,
|
||||
{
|
||||
type = "background",
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = 10,
|
||||
h = 11,
|
||||
texture_name = "[combine:16x16^[noalpha"
|
||||
},
|
||||
{
|
||||
type = "textarea",
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = 10,
|
||||
h = 10,
|
||||
name = "nano_editor",
|
||||
--read_only = 1,
|
||||
--label = "test",
|
||||
default = file,
|
||||
close_on_enter = false,
|
||||
--this breaks the scrollbar
|
||||
--[[ props = {
|
||||
border = false,
|
||||
} ]]
|
||||
},
|
||||
{
|
||||
type = "box",
|
||||
x = 0,
|
||||
y = 10,
|
||||
w = 10,
|
||||
h = 1,
|
||||
color = "#ffffff"
|
||||
},
|
||||
{
|
||||
type = "field",
|
||||
x = 0,
|
||||
y = 10,
|
||||
w = 10,
|
||||
h = 1,
|
||||
name = "nano_commands",
|
||||
close_on_enter = false,
|
||||
pwd = "",
|
||||
props = {
|
||||
border = false,
|
||||
},
|
||||
on_event = function(form, player, element, value, fields)
|
||||
if value == "save" and fields.nano_editor then
|
||||
--minetest.chat_send_all(fields.nano_editor)
|
||||
if #fields.nano_editor < 12000 then --12000 is luacheck line length of 120 * 100 lines
|
||||
minetest.safe_file_write(path .. "/" .. input:split(" ")[1], fields.nano_editor)
|
||||
else
|
||||
computers.api.chat_send_player(player, minetest.colorize("red", "[Nano]: file is to long"))
|
||||
end
|
||||
elseif value == "exit" then
|
||||
local btn = computers.formspec.get_index_by_name(form, "nano_btn")
|
||||
local ctn = computers.formspec.get_index_by_name(form, "nano_ctn")
|
||||
form[ctn].state = 1
|
||||
table.remove(form, btn)
|
||||
--table.remove(form, ctn) cant actually remove it from within itself, therefore we hide it
|
||||
local tindex = 0
|
||||
for index, tab in pairs(form.tabs) do
|
||||
if tab == "nano" then tindex = index end
|
||||
end
|
||||
table.remove(form.tabs, tindex)
|
||||
tindex = computers.formspec.get_index_by_name(form, "terminal_ctn")
|
||||
|
||||
form[tindex].state = 0
|
||||
else
|
||||
computers.api.chat_send_player(player, minetest.colorize("red", "[Nano]: invalid command"))
|
||||
end
|
||||
|
||||
return form
|
||||
end,
|
||||
},
|
||||
})
|
||||
|
||||
return form
|
||||
end
|
||||
|
||||
return nano
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "touch",
|
||||
"license": "MIT",
|
||||
"version": 0.2,
|
||||
"engine": 0.4,
|
||||
"version": 0.3,
|
||||
"engine": 0.42,
|
||||
"help": "creates a file in the current path"
|
||||
}
|
@ -3,11 +3,14 @@ function touch(pos, input, data)
|
||||
--make dir to be safe
|
||||
minetest.mkdir(path)
|
||||
|
||||
if #minetest.get_dir_list(path, false) > 10
|
||||
and not minetest.check_player_privs(data.player, "computers_filesystem") then
|
||||
return "ERROR: you have reached your max file limit"
|
||||
end
|
||||
|
||||
if input and input ~= "" and not input:find("/") then
|
||||
for _, item in pairs(minetest.get_dir_list(path, nil)) do
|
||||
if item == input then
|
||||
return "ERROR: trying to create already existing file/folder"
|
||||
end
|
||||
if computers.api.get_dir_keyed_list(path, nil)[input] then
|
||||
return "ERROR: trying to create already existing file/folder"
|
||||
end
|
||||
minetest.safe_file_write(path .. "/" .. input, "")
|
||||
return "file " .. input .. " created"
|
||||
|
@ -1,5 +1,5 @@
|
||||
local registered_astk = {}
|
||||
computers.formspec = {}
|
||||
computers.formspec.registered_kast = {}
|
||||
|
||||
computers.formspec.get_element_by_name = formspec_ast.get_element_by_name
|
||||
|
||||
@ -83,7 +83,7 @@ function computers.formspec.show_formspec(player, formname, fs)
|
||||
playername = player:get_player_name()
|
||||
end
|
||||
if type(fs) == "table" then
|
||||
registered_astk[playername] = table.copy(fs)
|
||||
computers.formspec.registered_kast[playername] = table.copy(fs)
|
||||
formspec = formspec_ast.unparse(computers.formspec.convert_to_ast(fs))
|
||||
end
|
||||
|
||||
@ -94,16 +94,38 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "computers:gui" then return end
|
||||
local pname = player:get_player_name()
|
||||
|
||||
if fields.quit then registered_astk[pname] = nil return end
|
||||
if fields.quit then computers.formspec.registered_kast[pname] = nil return end
|
||||
|
||||
--[[
|
||||
trying to figure out what a player actually did can be a mess
|
||||
input can validly be nil for a text field sometimes, othertimes it can come in first
|
||||
when a user has actual selected a button. buttons only come in when selected,
|
||||
so we override whatever the first key is if there a button since we are sure it was pressed
|
||||
since we cant get types from fields, we rely on the button being named name_btn with _btn being a suffix
|
||||
]]
|
||||
|
||||
local keys = {}
|
||||
for key, val in pairs(fields) do table.insert(keys, key) end
|
||||
local btn_override
|
||||
for key, val in pairs(fields) do
|
||||
table.insert(keys, key)
|
||||
local split = key:split("_")
|
||||
if #split >= 2 and split[2] == "btn" then btn_override = key end
|
||||
end
|
||||
|
||||
local element = computers.formspec.get_element_by_name(registered_astk[pname], keys[1])
|
||||
local element = computers.formspec.get_element_by_name(
|
||||
computers.formspec.registered_kast[pname],
|
||||
btn_override or keys[1]
|
||||
)
|
||||
|
||||
if element and element.on_event then
|
||||
--on_event(form, player, element)
|
||||
local form = element.on_event(registered_astk[pname], player, element, fields[keys[1]])
|
||||
local form = element.on_event(
|
||||
computers.formspec.registered_kast[pname],
|
||||
player,
|
||||
element,
|
||||
fields[btn_override or keys[1]],
|
||||
fields
|
||||
)
|
||||
|
||||
if form then computers.formspec.show_formspec(player, formname, form) end
|
||||
end
|
||||
|
@ -1,29 +1,31 @@
|
||||
--local usage
|
||||
computers.gui = {}
|
||||
|
||||
local futil = computers.formspec
|
||||
|
||||
function computers.load_gui(pos, node, clicker)
|
||||
--minetest.chat_send_all("test")
|
||||
local function select_btn(form, btn)
|
||||
--to hardcoded
|
||||
for _, obtn in pairs({"terminal", "browser"}) do
|
||||
local cindex = futil.get_index_by_name(form, obtn .. "_btn")
|
||||
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
|
||||
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90"
|
||||
local function select_btn(form, btn)
|
||||
--to hardcoded
|
||||
for _, obtn in pairs(form.tabs) do
|
||||
local cindex = futil.get_index_by_name(form, obtn .. "_btn")
|
||||
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
|
||||
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90"
|
||||
|
||||
cindex = futil.get_index_by_name(form, obtn .. "_ctn")
|
||||
form[cindex].state = 1
|
||||
end
|
||||
|
||||
local cindex = futil.get_index_by_name(form, btn .. "_btn")
|
||||
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff20"
|
||||
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff40"
|
||||
|
||||
local aindex = futil.get_index_by_name(form, btn .. "_ctn")
|
||||
form[aindex].state = 0
|
||||
cindex = futil.get_index_by_name(form, obtn .. "_ctn")
|
||||
form[cindex].state = 1
|
||||
end
|
||||
|
||||
local cindex = futil.get_index_by_name(form, btn .. "_btn")
|
||||
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff20"
|
||||
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff40"
|
||||
|
||||
local aindex = futil.get_index_by_name(form, btn .. "_ctn")
|
||||
form[aindex].state = 0
|
||||
end
|
||||
|
||||
function computers.gui.load(pos, node, clicker)
|
||||
|
||||
local formspec = {
|
||||
formspec_version = 4,
|
||||
tabs = {"terminal", "browser"},
|
||||
{
|
||||
type = "size",
|
||||
w = 10,
|
||||
@ -150,6 +152,8 @@ function computers.load_gui(pos, node, clicker)
|
||||
local output = computers.registered_commands[cdata[1]](pos, cdata[2], pass_table)
|
||||
if output and type(output) == "string" then
|
||||
form[cindex][eindex].default = text .. output .. "\n" .. "user:~" .. element.pwd .."$" .." "
|
||||
elseif output and type(output) == "table" then
|
||||
form = output
|
||||
end
|
||||
else
|
||||
form[cindex][eindex].default = text .. value ..
|
||||
@ -188,10 +192,31 @@ function computers.load_gui(pos, node, clicker)
|
||||
h = 2,
|
||||
name = "test_btn",
|
||||
label = "test btn",
|
||||
on_event = function(form, player, element)
|
||||
local cindex = futil.get_index_by_name(form, "browser_ctn")
|
||||
local eindex = futil.get_index_by_name(form[cindex], "test_btn")
|
||||
form[cindex][eindex] = {type = "label", x=1, y=3, label = "test button label"}
|
||||
on_event = function(_, player, element)
|
||||
--local cindex = futil.get_index_by_name(form, "browser_ctn")
|
||||
--local eindex = futil.get_index_by_name(form[cindex], "test_btn")
|
||||
--form[cindex][eindex] = {type = "label", x=1, y=3, label = "test button label"}
|
||||
local form = computers.gui.add_tab(player, "Tname", {
|
||||
type = "container",
|
||||
name = "tname_ctn",
|
||||
state = 1,
|
||||
x = 0,
|
||||
y = 1,
|
||||
{
|
||||
type = "background",
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = 10,
|
||||
h = 11,
|
||||
texture_name = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
x = 1,
|
||||
y = 1.5,
|
||||
label = "Tname pane",
|
||||
},
|
||||
})
|
||||
|
||||
return form
|
||||
end,
|
||||
@ -206,4 +231,65 @@ function computers.load_gui(pos, node, clicker)
|
||||
}
|
||||
|
||||
futil.show_formspec(clicker, "computers:gui", formspec)
|
||||
end
|
||||
|
||||
--legacy compat
|
||||
computers.load_gui = computers.gui.load
|
||||
|
||||
--note you can create to many pages thuse overflowing the formspec, need to be fixed
|
||||
function computers.gui.add_tab(player, tname, tab)
|
||||
local name = player
|
||||
if type(player) == "userdata" then name = player:get_player_name() end
|
||||
|
||||
assert(tab, "[computers.sandbox]: new tab for " .. name .. " not found")
|
||||
assert(tab.type == "container", "[computers.sandbox]: invalid new tab format for " .. name)
|
||||
assert(tab.name:split("_")[2] == "ctn", "[computers.sandbox]: invalid tab name for " .. name)
|
||||
assert(tab.x == 0 and tab.y == 1, "[computers.sandbox]: invalid tab name for " .. name)
|
||||
|
||||
|
||||
local formspec = table.copy(computers.formspec.registered_kast[name])
|
||||
local fs = {}
|
||||
local btn
|
||||
|
||||
for key, val in pairs(formspec) do
|
||||
if type(key) == "string" then
|
||||
fs[key] = val
|
||||
elseif type(val) == "table" then
|
||||
if val.name and #val.name:split("_") >= 2 and val.name:split("_")[2] == "btn" then
|
||||
btn = 1
|
||||
elseif btn and btn == 1 then
|
||||
table.insert(fs, {
|
||||
type = "button",
|
||||
x = #formspec.tabs*2,
|
||||
y = 0,
|
||||
w = 2,
|
||||
h = 1,
|
||||
name = tname:lower() .. "_btn",
|
||||
label = tname,
|
||||
on_event = function(form, _, element)
|
||||
select_btn(form, tname:lower())
|
||||
|
||||
return form
|
||||
end,
|
||||
props = {
|
||||
border = false,
|
||||
bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70",
|
||||
bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90",
|
||||
bgimg_middle = "4,4",
|
||||
}
|
||||
})
|
||||
|
||||
btn = nil
|
||||
end
|
||||
|
||||
table.insert(fs, val)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
table.insert(fs, tab)
|
||||
table.insert(fs.tabs, tname:lower())
|
||||
|
||||
return fs
|
||||
end
|
@ -8,7 +8,7 @@ minetest.mkdir(computers.devicepath) --make sure it exists
|
||||
minetest.mkdir(computers.networkpath) --make sure it exists
|
||||
|
||||
computers.os = {
|
||||
version = 0.41,
|
||||
version = 0.42,
|
||||
name = "kuto",
|
||||
authors = {"wsor", "luk3yx"},
|
||||
license = "MIT",
|
||||
@ -16,6 +16,7 @@ computers.os = {
|
||||
|
||||
dofile(computers.modpath .. "/old_stuff/init.lua")
|
||||
|
||||
dofile(computers.modpath .. "/api.lua")
|
||||
dofile(computers.modpath .. "/formspec.lua")
|
||||
dofile(computers.modpath .. "/commands.lua")
|
||||
dofile(computers.modpath .. "/gui.lua")
|
||||
|
Loading…
Reference in New Issue
Block a user