Auke Kok 3099dd6228 Attempt to allow empty lines in telex/files.
These were previously filtered out. Unfortunately now there's some
awkward emtpy lines in various places, but, I prefer having empty
lines show since that makes typing much simpler.
2019-10-04 22:15:06 -07:00

608 lines
16 KiB
Lua

--[[
ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
MA 02111-1307 USA
]]--
--[[
terminal - an interactive terminal
]]--
local term = {}
local function get_cmd_params(line)
local cmd = ""
local params = ""
for w in line:gmatch("%w+") do
if cmd == "" then
cmd = w
elseif params == "" then
params = w
else
params = params .. " " .. w
end
end
return cmd, params
end
term.help = {
append = "append text to a file",
clear = "clear the output",
echo = "echoes the input back to you",
help = "display help information for commands",
list = "list available files",
lock = "lock the terminal",
read = "read the content of a file",
remove = "removes a file",
unlock = "unlocks the terminal",
write = "write text to a file",
edit = "edits a file in an editor",
telex = "run the telex command - send and receive messages"
}
term.telex_help = {
help = "display help information for subcommands",
list = "list received telex messages",
draft = "create a new, or edit an outgoing telex message",
discard = "discard the current telex draft message",
send = "send the current draft telex message to a recipient",
remove = "remove a received telex message by number",
read = "read a received telex message by number",
reply = "create a draft reply to a message by number"
}
local function make_formspec(output, prompt)
local f =
"size[12,8]" ..
"field_close_on_enter[input;false]" ..
"textlist[0.4,0.5;11,6;output;"
local c = 1
if output then
for part in output:gmatch("([^\r\n]*)\n?") do
f = f .. minetest.formspec_escape(part) .. ","
c = c + 1
end
end
f = f .. minetest.formspec_escape(prompt) .. ";" .. c .. ";false]"
f = f .. "field[0.7,7;11.2,1;input;;]"
return f
end
term.commands = {
clear = function(output, params, c)
return ""
end,
append = function(output, params, c)
if not c.rw then
return output .. "\nError: No write access"
end
local what, _ = get_cmd_params(params)
if what == "" then
return output .. "\nError: Missing file name"
end
c.writing = what
return output .. "\nWriting \"" .. what .. "\". Enter STOP on a line by itself to finish"
end,
write = function(output, params, c)
if not c.rw then
return output .. "\nError: No write access"
end
local what, _ = get_cmd_params(params)
if what == "" then
return output .. "\nError: Missing file name"
end
c.writing = what
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
if meta_files and meta_files ~= "" then
local files = minetest.parse_json(meta_files) or {}
if files and files[what] then
files[what] = ""
meta:set_string("files", minetest.write_json(files))
meta:mark_as_private("files")
end
end
return output .. "\nWriting \"" .. what .. "\". Enter STOP on a line by itself to finish"
end,
edit = function(output, params, c)
if not c.rw then
return output .. "\nError: No write access"
end
local what, _ = get_cmd_params(params)
if what == "" then
return output .. "\nError: Missing file name"
end
c.what = what
c.output = output
local text = ""
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
if meta_files and meta_files ~= "" then
local files = minetest.parse_json(meta_files) or {}
if files and files[what] then
text = files[what]
end
end
fsc.show(c.name, "size[12,8]" ..
"textarea[0.5,0.5;11.5,7.0;text;text;" ..
minetest.formspec_escape(text) .. "]" ..
"button[5.2,7.2;1.6,0.5;exit;Save]",
c,
term.edit)
return false
end,
remove = function(output, params, c)
if not c.rw then
return output .. "\nError: No write access"
end
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
if not meta_files or meta_files == "" then
return output .. "\nError: No such file"
end
local files = minetest.parse_json(meta_files) or {}
local first, _ = get_cmd_params(params)
if files[first] then
files[first] = nil
else
return output .. "\nError: No such file"
end
meta:set_string("files", minetest.write_json(files))
meta:mark_as_private("files")
return output .. "\nRemoved \"" .. first .. "\""
end,
list = function(output, params, c)
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
local files
if not meta_files or meta_files == "" then
return output .. "\nError: No files found"
end
files = minetest.parse_json(meta_files) or {}
if not files then
return output .. "\nError: No files found"
end
for k, _ in pairs(files) do
output = output .. "\n" .. k
end
return output
end,
echo = function(output, params, c)
return output .. "\n" .. params
end,
read = function(output, params, c)
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
if not meta_files or meta_files == "" then
return output .. "\nError: No such file"
end
local files = minetest.parse_json(meta_files) or {}
local first, _ = get_cmd_params(params)
if files[first] then
if first == "rules" then
rules.show(c.name, "player")
return false
end
return output .. "\n" .. files[first]
else
return output .. "\nError: No such file"
end
end,
lock = function(output, params, c)
if not c.rw then
return output .. "\nError: no write access"
end
local meta = minetest.get_meta(c.pos)
meta:set_int("locked", 1)
meta:mark_as_private("locked")
return output .. "\n" .. "Terminal locked"
end,
unlock = function(output, params, c)
return output .. "\n" .. "Error: unable to connect to authentication service"
end,
telex = function(output, params, c)
if params ~= "" then
local h, p = get_cmd_params(params)
if h == "help" then
if p == "" then
local o = ""
local ot = {}
for k, _ in pairs(term.telex_help) do
ot[#ot + 1] = k
end
table.sort(ot)
for _, v in ipairs(ot) do
o = o .. " " .. v .. "\n"
end
return output .. "\n" ..
"Available subcommands:\n" ..
o ..
"Type `telex help <subcommand>` for more help about that command"
elseif term.telex_help[p] then
return output .. "\n" .. term.telex_help[p]
else
return output .. "\nError: No help for \"" .. h .. "\""
end
elseif h == "list" then
local player = minetest.get_player_by_name(c.name)
return output .. "\n" .. table.concat(telex.list(player), "\n")
elseif h == "draft" then
local player = minetest.get_player_by_name(c.name)
local meta = player:get_meta()
local text = meta:get_string("telex_draft")
local subject = meta:get_string("telex_subject")
fsc.show(c.name, "size[12,8]" ..
"field[0.5,0.5;11.5,1;subject;subject;" ..
minetest.formspec_escape(subject) .. "]" ..
"textarea[0.5,1.5;11.5,7.0;text;text;" ..
minetest.formspec_escape(text) .. "]" ..
"button[5.2,7.7;1.6,0.5;exit;Save]",
c,
term.draft)
return false
elseif h == "discard" then
local player = minetest.get_player_by_name(c.name)
local meta = player:get_meta()
meta:set_string("telex_draft", "")
meta:set_string("telex_subject", "")
return output .. "\n" .. "Draft erased."
elseif h == "send" then
if p == "" then
return output .. "\n" .. "Need recipient name to send draft message to."
end
local player = minetest.get_player_by_name(c.name)
local meta = player:get_meta()
local text = meta:get_string("telex_draft")
local subject = meta:get_string("telex_subject")
if text == "" then
return output .. "\n" .. "No draft exists. Cannot send. run `telex draft` to create a draft."
end
if subject == "" then
return output .. "\n" .. "No draft subject. Edit your draft and enter a subject."
end
local msg = {
to = p,
subject = subject,
from = c.name,
content = string.split(text, "\n")
}
telex.send(msg)
return output .. "\n" .. "Mail sent to <" .. p .. ">."
elseif h == "read" then
local player = minetest.get_player_by_name(c.name)
return output .. "\n" .. table.concat(telex.read(player, tonumber(p)), "\n")
elseif h == "remove" then
local player = minetest.get_player_by_name(c.name)
return output .. "\n" .. table.concat(telex.delete(player, tonumber(p)), "\n")
elseif h == "reply" then
local player = minetest.get_player_by_name(c.name)
local msg = telex.get(player, tonumber(p))
if not msg.subject then
return output .. "\n" .. table.concat(msg, "\n")
end
fsc.show(c.name, "size[12,8]" ..
"field[0.5,0.5;11.5,1;subject;subject;" ..
minetest.formspec_escape("Re: " .. msg.subject) .. "]" ..
"textarea[0.5,1.5;11.5,7.0;text;text;" ..
"\n\n> " ..
minetest.formspec_escape(table.concat(msg.content, "\n> ")) .. "]" ..
"button[5.2,7.7;1.6,0.5;exit;Save]",
c,
term.draft)
return false
else
return output .. "\n" .. "Invalid command passed. Use `telex help` to list available commands."
end
else
return output .. "\n" ..
"Type `telex help` for more help about the telex command"
end
end,
help = function(output, params, c)
if params ~= "" then
local h, _ = get_cmd_params(params)
if term.help[h] then
return output .. "\n" .. term.help[h]
else
return output .. "\nError: No help for \"" .. h .. "\""
end
end
local o = ""
local ot = {}
for k, _ in pairs(term.help) do
ot[#ot + 1] = k
end
table.sort(ot)
for _, v in ipairs(ot) do
o = o .. " " .. v .. "\n"
end
return output .. "\n" ..
"Available commands:\n" ..
o ..
"Type `help <command>` for more help about that command"
end,
}
function term.recv(player, fields, context)
-- input validation
local name = player:get_player_name()
local c = context
if not c or not c.pos then
log.fs_data(player, name, "term.recv/context", fields)
return true
end
local line = fields.input
if line and line ~= "" then
local output = c.output or ""
minetest.sound_play("terminal_keyboard_clicks", {pos = c.pos})
if c.writing then
-- this shouldn't get reached, but just to be safe, check ro
if not c.rw then
c.writing = nil
output = output .. "\n" .. line
output = output .. "\nError: no write access"
c.output = output
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
return
end
-- are we writing a file?
if line == "STOP" then
-- done writing a file
c.writing = nil
output = output .. "\n" .. line
c.output = output
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
return
end
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
local files = {}
if not meta_files or meta_files == "" then
files[c.writing] = line
else
files = minetest.parse_json(meta_files) or {}
if not files[c.writing] then
files[c.writing] = line
else
files[c.writing] = files[c.writing] .. "\n" .. line
end
end
if string.len(files[c.writing]) < 16384 then
local json = minetest.write_json(files)
if string.len(json) < 49152 then
meta:set_string("files", json)
meta:mark_as_private("files")
output = output .. "\n" .. line
else
output = output .. "\n" .. "Error: no space left on device"
end
else
output = output .. "\n" .. "Error: maximum file length exceeded"
end
c.output = output
fsc.show(name,
make_formspec(output, ""),
c,
term.recv)
return
else
-- else parse cmd
output = output .. "\n> " .. line
local meta = minetest.get_meta(c.pos)
local cmd, params = get_cmd_params(line)
if meta:get_int("locked") == 1 and cmd ~= "unlock" then
output = output .. "\nError: Terminal locked, type \"unlock\" to unlock it"
c.output = output
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
return
end
local fn = term.commands[cmd]
if fn then
output = fn(output, params, c)
else
output = output .. "\n" .. "Error: Syntax Error. Try \"help\""
end
if output ~= false then
c.output = output
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
end
return
end
elseif fields.quit then
minetest.sound_play("terminal_power_off", {pos = c.pos})
return true
elseif fields.output then
-- CHG events - do not return true
return
elseif fields.input then
-- KEYBOARD events - do not return true
return
end
log.fs_data(player, name, "term.recv/default", fields)
return true
end
function term.edit(player, fields, context)
if not fields.text then
return true
end
local name = player:get_player_name()
local c = context
if not c or not c.pos or not c.output then
log.fs_data(player, name, "term.edit/terminal", fields)
return true
end
local output = c.output
if not c.what then
output = output .. "\n" .. "Error: no such file\n"
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
return
end
local meta = minetest.get_meta(c.pos)
local meta_files = meta:get_string("files")
local files
files = minetest.parse_json(meta_files) or {}
files[c.what] = fields.text
-- validate it fits
local json = minetest.write_json(files)
if string.len(json) < 49152 then
meta:set_string("files", json)
meta:mark_as_private("files")
output = output .. "\n" .. "Wrote: " .. c.what .. "\n"
else
output = output .. "\n" .. "Error: no space left on device\n"
end
c.output = output
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
return
end
function term.draft(player, fields, context)
if not fields.text then
return true
end
local name = player:get_player_name()
local c = context
if not c or not c.pos or not c.output then
log.fs_data(player, name, "term.draft/terminal", fields)
return true
end
local output = c.output
local meta = player:get_meta()
-- validate it fits
if string.len(fields.text) < 49152 then
meta:set_string("telex_draft", fields.text)
meta:set_string("telex_subject", fields.subject)
output = output .. "\n" .. "Draft saved.\n"
else
output = output .. "\n" .. "Error: no space left on device\n"
end
c.output = output
fsc.show(name,
make_formspec(output, "> "),
c,
term.recv)
return
end
local terminal_use = function(pos, node, clicker, itemstack, pointed_thing)
if not clicker then
return
end
local name = clicker:get_player_name()
local context = {
pos = pos,
rw = false,
output = "",
name = name,
}
if boxes.players_editing_boxes[name] or
(not boxes.players_in_boxes[name] and minetest.check_player_privs(clicker, "server")) then
-- allow rw access
context.rw = true
end
-- send formspec to player
fsc.show(name,
make_formspec(nil, "> "),
context,
term.recv)
minetest.sound_play("terminal_power_on", {pos = pos})
-- trigger on first use
local meta = minetest.get_meta(pos)
if meta:get_int("locked") ~= 1 then
mech.trigger(pos)
minetest.after(1.0, mech.untrigger, pos)
end
end
minetest.register_node("terminal:terminal", {
description = "Interactive terminal console emulator access interface unit controller",
drawtype = "mesh",
mesh = "terminal.obj",
groups = {mech = 1, trigger = 1},
tiles = {
{name = "terminal_base.png"},
{name = "terminal_idle.png", animation = {type = "vertical_frames", aspect_w = 14, aspect_h = 13, length = 4.0}},
},
paramtype = "light",
paramtype2 = "facedir",
on_trigger = function(pos)
local meta = minetest.get_meta(pos)
minetest.sound_play("terminal_power_on", {pos = pos})
meta:set_int("locked", 0)
meta:mark_as_private("locked")
end,
on_untrigger = function(pos)
local meta = minetest.get_meta(pos)
minetest.sound_play("terminal_power_off", {pos = pos})
meta:set_int("locked", 1)
meta:mark_as_private("locked")
end,
on_rightclick = terminal_use,
sounds = sounds.metal,
})