Advertisement
osmarks

EnderMail

Mar 30th, 2020
1,448
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. package.path = "/?;/?.lua;" .. package.path
  2. local chest = settings.get "mail.chest" or error "please set mail.chest to the network name of the (non-ender) chest to use"
  3. local ender_chest = peripheral.find "ender_chest" or error "ender chest connected through adapter + relay required"
  4. local ender_chest_inv = peripheral.find "minecraft:ender chest" or error "ender chest directly connected required"
  5. local modem = peripheral.find("modem", function(_, x) return x.isWireless() end) or error "wireless modem required"
  6. local ok, ecnet = pcall(require, "ecnet")
  7. if not ok then
  8.     print "Downloading ECNet library (https://forums.computercraft.cc/index.php?topic=181.0)"
  9.     shell.run "wget https://gist.githubusercontent.com/migeyel/278f77628248ea991719f0376979b525/raw/ecnet.min.lua ecnet.lua"
  10. end
  11. ecnet = require "ecnet"
  12. local label = os.getComputerLabel() or error "Please set a label to use as a device name"
  13. print("Address is", ecnet.address)
  14. local ecnet_modem = ecnet.wrap(modem)
  15. local maildata_path = "maildata"
  16.  
  17. local acceptable_mailbox_name_pattern = "^[A-Za-z0-9_]+$"
  18. if not label:match(acceptable_mailbox_name_pattern) then error("label must match: " .. acceptable_mailbox_name_pattern) end
  19.  
  20. local function find_channel()
  21.     for i = 0, 10 do
  22.         local new = math.random(0, 0xFFF)
  23.         ender_chest.setFrequency(new)
  24.         local count = 0
  25.         for _, stack in pairs(ender_chest_inv.list()) do
  26.             count = count + stack.count
  27.         end
  28.         if count == 0 then
  29.             return new
  30.         end
  31.     end
  32.     error "Available channel scan failed after 10 tries - has someone flooded ender chests with random stuff?"
  33. end
  34.  
  35. local function writef(n, c)
  36.     local f = fs.open(n, "w")
  37.     f.write(c)
  38.     f.close()
  39. end
  40.  
  41. local function readf(n)
  42.     local f = fs.open(n, "r")
  43.     local out = f.readAll()
  44.     f.close()
  45.     return out
  46. end
  47.  
  48. local data = {}
  49. if fs.exists(maildata_path) then data = textutils.unserialise(readf(maildata_path)) end
  50. if type(data.paired) ~= "table" then data.paired = {} end
  51.  
  52. local function save_data() writef(maildata_path, textutils.serialise(data)) end
  53.  
  54. local function split_at_spaces(s)
  55.     local t = {}
  56.     for i in string.gmatch(s, "%S+") do
  57.        table.insert(t, i)
  58.     end
  59.     return t
  60. end
  61.  
  62. local function update_self()
  63.     print "Downloading update."
  64.     local h = http.get "https://pastebin.com/raw/86Kjhq32"
  65.     local t = h.readAll()
  66.     h.close()
  67.     local fn, err = load(t, "@mail")
  68.     if not fn then printError("Not updating: syntax error in new version:\n" .. err) return end
  69.     local f = fs.open("startup", "w")
  70.     f.write(t)
  71.     f.close()
  72.     os.reboot()
  73. end
  74.  
  75. local function first_letter(s)
  76.     return string.sub(s, 1, 1)
  77. end
  78.  
  79. local function send_stack(slot, addr)
  80.     local channel = find_channel()
  81.     print("[OUT] Channel:", channel)
  82.     ecnet_modem.send(addr, { "stack_request", channel = channel })
  83.     local _, result = os.pullEvent "stack_request_response"
  84.     if result == true then
  85.         ender_chest_inv.pullItems(chest, slot)
  86.         print("[OUT] Sent stack", slot)
  87.         local _, result, x = os.pullEvent "stack_result"
  88.         if result == false then
  89.             printError("[OUT] Destination error: " .. tostring(x))
  90.             for eslot in pairs(ender_chest_inv.list()) do
  91.                 ender_chest_inv.pushItems(chest, eslot)
  92.             end
  93.         end
  94.         return result
  95.     else return false end
  96. end
  97.  
  98. local function get_name(address)
  99.     for name, addr in pairs(data.paired) do
  100.         if addr == address then return name end
  101.     end
  102.     return address
  103. end
  104.  
  105. local last_pair_request = nil
  106.  
  107. local CLI_commands = {
  108.     address = function()
  109.         print(ecnet.address)
  110.     end,
  111.     update = update_self,
  112.     pair = function(addr)
  113.         local ok = ecnet_modem.connect(addr, 2)
  114.         if not ok then error("Could not contact " .. addr) end
  115.         ecnet_modem.send(addr, { "pair", label = label })
  116.     end,
  117.     accept_pair = function()
  118.         if not last_pair_request then error "no pair request to accept" end
  119.         ecnet_modem.send(last_pair_request.address, { "pair_accept", label = label })
  120.         data.paired[last_pair_request.label] = last_pair_request.address
  121.         save_data()
  122.         last_pair_request = nil
  123.     end,
  124.     reject_pair = function()
  125.         if not last_pair_request then error "no pair request to reject" end
  126.         ecnet_modem.send(last_pair_request.address, { "pair_reject", label = label })
  127.         last_pair_request = nil
  128.     end,
  129.     paired = function()
  130.         print "Paired:"
  131.         for label, addr in pairs(data.paired) do
  132.             print(label, addr)
  133.         end
  134.     end,
  135.     unpair = function(name)
  136.         data.paired[name] = nil
  137.         save_data()
  138.     end,
  139.     send = function(name)
  140.         local addr = data.paired[name]
  141.         if not addr then error(name .. " not found") end
  142.         if not ecnet_modem.connect(addr, 3) then error("Connection to " .. name .. " failed") end
  143.         print "Connected"
  144.         for slot, contents in pairs(peripheral.call(chest, "list")) do
  145.             print("[OUT] Sending stack", slot)
  146.             local timed_out, result = false, nil
  147.             parallel.waitForAny(function() result = send_stack(slot, addr) end, function() sleep(5) timed_out = true end)
  148.             if not timed_out then print("[OUT] Destination success") else printError "[OUT] Timed out." end
  149.         end
  150.     end,
  151.     help = function()
  152.         write([[EnderMail UI commands:
  153. address - print address
  154. update - update the code
  155. pair [address] - send a pairing request to the specified address
  156. accept_pair - accept the latest pairing request
  157. deny_pair - reject the latest pairing request
  158. paired - list all paired mailboxes
  159. unpair [name] - remove the named mailbox from your paired list
  160. send [name] - send contents of chest to specified paired mailbox
  161. ]])
  162.     end
  163. }
  164.  
  165. local function handle_commands()
  166.     print "Mailbox UI"
  167.     local history = {}
  168.     while true do
  169.         write "|> "
  170.         local text = read(nil, history)
  171.  
  172.         if text ~= "" then table.insert(history, text) end
  173.  
  174.         local tokens = split_at_spaces(text)
  175.         local command = table.remove(tokens, 1)
  176.         local args = tokens
  177.         local fn = CLI_commands[command]
  178.  
  179.         if not fn then
  180.             for command_name, func in pairs(CLI_commands) do
  181.                 if command and first_letter(command_name) == first_letter(command) then fn = func end
  182.             end
  183.         end
  184.         if not fn then
  185.             print("Command", command, "not found.")
  186.         else
  187.             local ok, err = pcall(fn, table.unpack(args))
  188.             if not ok then printError(err) end
  189.         end
  190.     end
  191. end
  192.  
  193. local function handle_message(addr, msg)
  194.     if type(msg) == "table" then
  195.         if msg[1] == "pair" then
  196.             if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end
  197.             print(("Pair request from %s (%s)"):format(addr, msg.label))
  198.             print "`accept_pair` to accept, `reject_pair` to deny"
  199.             last_pair_request = { address = addr, label = msg.label }
  200.         elseif msg[1] == "pair_accept" then
  201.             if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end
  202.             print(("%s (%s) accepted pairing"):format(addr, msg.label))
  203.             data.paired[msg.label] = addr
  204.             save_data()
  205.         elseif msg[1] == "pair_reject" then
  206.             if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end
  207.             print(("%s (%s) rejected pairing"):format(addr, msg.label))
  208.         elseif msg[1] == "stack_request" then
  209.             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
  210.             ender_chest.setFrequency(msg.channel)
  211.             ecnet_modem.send(addr, { "stack_request_response", true })
  212.             local start = os.clock()
  213.             -- constantly attempt to move items until done
  214.             while os.clock() - start <= 5 do
  215.                 for slot, stack in pairs(ender_chest_inv.list()) do
  216.                     local moved = ender_chest_inv.pushItems(chest, slot)
  217.                     print("[IN]", get_name(addr), stack.name, moved)
  218.                     if moved > 0 then
  219.                         ecnet_modem.send(addr, { "stack_result", true, channel = msg.channel })
  220.                         return
  221.                     else
  222.                         ecnet_modem.send(addr, { "stack_result", false, "out of space", channel = msg.channel })
  223.                         return
  224.                     end
  225.                 end
  226.             end
  227.             ecnet_modem.send(addr, { "stack_result", false, channel = msg.channel })
  228.         elseif msg[1] == "stack_request_response" then
  229.             os.queueEvent("stack_request_response", msg[2])
  230.         elseif msg[1] == "stack_result" then
  231.             os.queueEvent("stack_result", msg[2], msg[3])
  232.         end
  233.     end
  234. end
  235.  
  236. local function handle_messages()
  237.     while true do
  238.         handle_message(ecnet_modem.receive())
  239.     end
  240. end
  241.  
  242. parallel.waitForAll(handle_commands, handle_messages)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement