random-stuff/computercraft/endermail.lua

242 lines
8.1 KiB
Lua

package.path = "/?;/?.lua;" .. package.path
local chest = settings.get "mail.chest" or error "please set mail.chest to the network name of the (non-ender) chest to use"
local ender_chest = peripheral.find "ender_chest" or error "ender chest connected through adapter + relay required"
local ender_chest_inv = peripheral.find "minecraft:ender chest" or error "ender chest directly connected required"
local modem = peripheral.find("modem", function(_, x) return x.isWireless() end) or error "wireless modem required"
local ok, ecnet = pcall(require, "ecnet")
if not ok then
print "Downloading ECNet library (https://forums.computercraft.cc/index.php?topic=181.0)"
shell.run "wget https://gist.githubusercontent.com/migeyel/278f77628248ea991719f0376979b525/raw/ecnet.min.lua ecnet.lua"
end
ecnet = require "ecnet"
local label = os.getComputerLabel() or error "Please set a label to use as a device name"
print("Address is", ecnet.address)
local ecnet_modem = ecnet.wrap(modem)
local maildata_path = "maildata"
local acceptable_mailbox_name_pattern = "^[A-Za-z0-9_]+$"
if not label:match(acceptable_mailbox_name_pattern) then error("label must match: " .. acceptable_mailbox_name_pattern) end
local function find_channel()
for i = 0, 10 do
local new = math.random(0, 0xFFF)
ender_chest.setFrequency(new)
local count = 0
for _, stack in pairs(ender_chest_inv.list()) do
count = count + stack.count
end
if count == 0 then
return new
end
end
error "Available channel scan failed after 10 tries - has someone flooded ender chests with random stuff?"
end
local function writef(n, c)
local f = fs.open(n, "w")
f.write(c)
f.close()
end
local function readf(n)
local f = fs.open(n, "r")
local out = f.readAll()
f.close()
return out
end
local data = {}
if fs.exists(maildata_path) then data = textutils.unserialise(readf(maildata_path)) end
if type(data.paired) ~= "table" then data.paired = {} end
local function save_data() writef(maildata_path, textutils.serialise(data)) end
local function split_at_spaces(s)
local t = {}
for i in string.gmatch(s, "%S+") do
table.insert(t, i)
end
return t
end
local function update_self()
print "Downloading update."
local h = http.get "https://pastebin.com/raw/86Kjhq32"
local t = h.readAll()
h.close()
local fn, err = load(t, "@mail")
if not fn then printError("Not updating: syntax error in new version:\n" .. err) return end
local f = fs.open("startup", "w")
f.write(t)
f.close()
os.reboot()
end
local function first_letter(s)
return string.sub(s, 1, 1)
end
local function send_stack(slot, addr)
local channel = find_channel()
print("[OUT] Channel:", channel)
ecnet_modem.send(addr, { "stack_request", channel = channel })
local _, result = os.pullEvent "stack_request_response"
if result == true then
ender_chest_inv.pullItems(chest, slot)
print("[OUT] Sent stack", slot)
local _, result, x = os.pullEvent "stack_result"
if result == false then
printError("[OUT] Destination error: " .. tostring(x))
for eslot in pairs(ender_chest_inv.list()) do
ender_chest_inv.pushItems(chest, eslot)
end
end
return result
else return false end
end
local function get_name(address)
for name, addr in pairs(data.paired) do
if addr == address then return name end
end
return address
end
local last_pair_request = nil
local CLI_commands = {
address = function()
print(ecnet.address)
end,
update = update_self,
pair = function(addr)
local ok = ecnet_modem.connect(addr, 2)
if not ok then error("Could not contact " .. addr) end
ecnet_modem.send(addr, { "pair", label = label })
end,
accept_pair = function()
if not last_pair_request then error "no pair request to accept" end
ecnet_modem.send(last_pair_request.address, { "pair_accept", label = label })
data.paired[last_pair_request.label] = last_pair_request.address
save_data()
last_pair_request = nil
end,
reject_pair = function()
if not last_pair_request then error "no pair request to reject" end
ecnet_modem.send(last_pair_request.address, { "pair_reject", label = label })
last_pair_request = nil
end,
paired = function()
print "Paired:"
for label, addr in pairs(data.paired) do
print(label, addr)
end
end,
unpair = function(name)
data.paired[name] = nil
save_data()
end,
send = function(name)
local addr = data.paired[name]
if not addr then error(name .. " not found") end
if not ecnet_modem.connect(addr, 3) then error("Connection to " .. name .. " failed") end
print "Connected"
for slot, contents in pairs(peripheral.call(chest, "list")) do
print("[OUT] Sending stack", slot)
local timed_out, result = false, nil
parallel.waitForAny(function() result = send_stack(slot, addr) end, function() sleep(5) timed_out = true end)
if not timed_out then print("[OUT] Destination success") else printError "[OUT] Timed out." end
end
end,
help = function()
write([[EnderMail UI commands:
address - print address
update - update the code
pair [address] - send a pairing request to the specified address
accept_pair - accept the latest pairing request
deny_pair - reject the latest pairing request
paired - list all paired mailboxes
unpair [name] - remove the named mailbox from your paired list
send [name] - send contents of chest to specified paired mailbox
]])
end
}
local function handle_commands()
print "Mailbox UI"
local history = {}
while true do
write "|> "
local text = read(nil, history)
if text ~= "" then table.insert(history, text) end
local tokens = split_at_spaces(text)
local command = table.remove(tokens, 1)
local args = tokens
local fn = CLI_commands[command]
if not fn then
for command_name, func in pairs(CLI_commands) do
if command and first_letter(command_name) == first_letter(command) then fn = func end
end
end
if not fn then
print("Command", command, "not found.")
else
local ok, err = pcall(fn, table.unpack(args))
if not ok then printError(err) end
end
end
end
local function handle_message(addr, msg)
if type(msg) == "table" then
if msg[1] == "pair" then
if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end
print(("Pair request from %s (%s)"):format(addr, msg.label))
print "`accept_pair` to accept, `reject_pair` to deny"
last_pair_request = { address = addr, label = msg.label }
elseif msg[1] == "pair_accept" then
if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end
print(("%s (%s) accepted pairing"):format(addr, msg.label))
data.paired[msg.label] = addr
save_data()
elseif msg[1] == "pair_reject" then
if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end
print(("%s (%s) rejected pairing"):format(addr, msg.label))
elseif msg[1] == "stack_request" then
if not msg.channel or msg.channel < 0 or msg.channel > 0xFFF then ecnet_modem.send(addr, { "stack_request_response", false, "channel missing/invalid" }) end
ender_chest.setFrequency(msg.channel)
ecnet_modem.send(addr, { "stack_request_response", true })
local start = os.clock()
-- constantly attempt to move items until done
while os.clock() - start <= 5 do
for slot, stack in pairs(ender_chest_inv.list()) do
local moved = ender_chest_inv.pushItems(chest, slot)
print("[IN]", get_name(addr), stack.name, moved)
if moved > 0 then
ecnet_modem.send(addr, { "stack_result", true, channel = msg.channel })
return
else
ecnet_modem.send(addr, { "stack_result", false, "out of space", channel = msg.channel })
return
end
end
end
ecnet_modem.send(addr, { "stack_result", false, channel = msg.channel })
elseif msg[1] == "stack_request_response" then
os.queueEvent("stack_request_response", msg[2])
elseif msg[1] == "stack_result" then
os.queueEvent("stack_result", msg[2], msg[3])
end
end
end
local function handle_messages()
while true do
handle_message(ecnet_modem.receive())
end
end
parallel.waitForAll(handle_commands, handle_messages)