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)