Advertisement
justync7

NetShellEdit

May 20th, 2013
182
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --I DID NOT MAKE THIS PROGRAM IM MAKING SLIGHT EDITS TO MY NEEDS
  2. local tArgs = { ... }
  3.  
  4. local connections = {}
  5.  
  6. local nshAPI = {
  7.     connList = connections
  8. }
  9.  
  10. nshAPI.getRemoteID = function()
  11.     --check for connected clients with matching threads.
  12.     for cNum, cInfo in pairs(nshAPI.connList) do
  13.         if cInfo.thread == coroutine.running() then
  14.             if cNum == "localShell" then
  15.                 --if we are a client running on the server, return the remote server ID.
  16.                 if nshAPI.serverNum then
  17.                     return nshAPI.serverNum
  18.                 else
  19.                     return nil
  20.                 end
  21.             end
  22.             return cNum
  23.         end
  24.     end
  25.     --client running without local server, return remote server ID.
  26.     if nshAPI.serverNum then return nshAPI.serverNum end
  27.     return nil
  28. end
  29.  
  30. nshAPI.send = function(msg)
  31.     local id = nshAPI.getRemoteID()
  32.     if id then
  33.         return rednet.send(id, msg)
  34.     end
  35.     return nil
  36. end
  37.  
  38. nshAPI.receive = function(timeout)
  39.     if type(timeout) == number then timeout = os.startTimer(timeout) end
  40.     while true do
  41.         event = {os.pullEvent()}
  42.         if event[1] == "rednet_message" and event[2] == nshAPI.getRemoteID() then
  43.             return event[3]
  44.         elseif event[1] == "timer" and event[2] == timeout then
  45.             return nil
  46.         end
  47.     end
  48. end
  49.  
  50. nshAPI.getClientCapabilities = function()
  51.     if nshAPI.clientCapabilities then return nshAPI.clientCapabilities end
  52.     nshAPI.send("SP:;clientCapabilities")
  53.     return nshAPI.receive(1)
  54. end
  55.  
  56. nshAPI.getRemoteConnections = function()
  57.     local remotes = {}
  58.     for cNum, cInfo in pairs(nshAPI.connList) do
  59.         table.insert(remotes, cNum)
  60.         if cInfo.outbound then
  61.             table.insert(remotes, cInfo.outbound)
  62.         end
  63.     end
  64.     return remotes
  65. end
  66.  
  67. local packetConversion = {
  68.     query = "SQ",
  69.     response = "SR",
  70.     data = "SP",
  71.     close = "SC",
  72.     fileQuery = "FQ",
  73.     fileSend = "FS",
  74.     fileResponse = "FR",
  75.     fileHeader = "FH",
  76.     fileData = "FD",
  77.     fileEnd = "FE",
  78.     textWrite = "TW",
  79.     textCursorPos = "TC",
  80.     textGetCursorPos = "TG",
  81.     textGetSize = "TD",
  82.     textInfo = "TI",
  83.     textClear = "TE",
  84.     textClearLine = "TL",
  85.     textScroll = "TS",
  86.     textBlink = "TB",
  87.     textColor = "TF",
  88.     textBackground = "TK",
  89.     textIsColor = "TA",
  90.     event = "EV",
  91.     SQ = "query",
  92.     SR = "response",
  93.     SP = "data",
  94.     SC = "close",
  95.     FQ = "fileQuery",
  96.     FS = "fileSend",
  97.     FR = "fileResponse",
  98.     FH = "fileHeader",
  99.     FD = "fileData",
  100.     FE = "fileEnd",
  101.     TW = "textWrite",
  102.     TC = "textCursorPos",
  103.     TG = "textGetCursorPos",
  104.     TD = "textGetSize",
  105.     TI = "textInfo",
  106.     TE = "textClear",
  107.     TL = "textClearLine",
  108.     TS = "textScroll",
  109.     TB = "textBlink",
  110.     TF = "textColor",
  111.     TK = "textBackground",
  112.     TA = "textIsColor",
  113.     EV = "event",
  114. }
  115.  
  116. local function openModem()
  117.     local modemFound = false
  118.     for _, side in ipairs(rs.getSides()) do
  119.         if peripheral.getType(side) == "modem" then
  120.             if not rednet.isOpen(side) then rednet.open(side) end
  121.             modemFound = true
  122.             break
  123.         end
  124.     end
  125.     return modemFound
  126. end
  127.  
  128. local function send(id, type, message)
  129.     return rednet.send(id, packetConversion[type]..":;"..message)
  130. end
  131.  
  132. local function awaitResponse(id, time)
  133.     id = tonumber(id)
  134.     local listenTimeOut = nil
  135.     local messRecv = false
  136.     if time then listenTimeOut = os.startTimer(time) end
  137.     while not messRecv do
  138.         local event, p1, p2 = os.pullEvent()
  139.         if event == "timer" and p1 == listenTimeOut then
  140.             return false
  141.         elseif event == "rednet_message" then
  142.             sender, message = p1, p2
  143.             if id == sender and message then
  144.                 if packetConversion[string.sub(message, 1, 2)] then packetType = packetConversion[string.sub(message, 1, 2)] end
  145.                 message = string.match(message, ";(.*)")
  146.                 messRecv = true
  147.             end
  148.         end
  149.     end
  150.     return packetType, message
  151. end
  152.  
  153. local function processText(conn, pType, value)
  154.     if not pType then return false end
  155.     if pType == "textWrite" and value then
  156.         term.write(value)
  157.     elseif pType == "textClear" then
  158.         term.clear()
  159.     elseif pType == "textClearLine" then
  160.         term.clearLine()
  161.     elseif pType == "textGetCursorPos" then
  162.         local x, y = term.getCursorPos()
  163.         send(conn, "textInfo", math.floor(x)..","..math.floor(y))
  164.     elseif pType == "textCursorPos" then
  165.         local x, y = string.match(value, "(%d+),(%d+)")
  166.         term.setCursorPos(tonumber(x), tonumber(y))
  167.     elseif pType == "textBlink" then
  168.         if value == "true" then
  169.             term.setCursorBlink(true)
  170.         else
  171.             term.setCursorBlink(false)
  172.         end
  173.     elseif pType == "textGetSize" then
  174.         x, y = term.getSize()
  175.         send(conn, "textInfo", x..","..y)
  176.     elseif pType == "textScroll" and value then
  177.         term.scroll(tonumber(value))
  178.     elseif pType == "textIsColor" then
  179.         send(conn, "textInfo", tostring(term.isColor()))
  180.     elseif pType == "textColor" and value then
  181.         value = tonumber(value)
  182.         if (value == 1 or value == 32768) or term.isColor() then
  183.             term.setTextColor(value)
  184.         end
  185.     elseif pType == "textBackground" and value then
  186.         value = tonumber(value)
  187.         if (value == 1 or value == 32768) or term.isColor() then
  188.             term.setBackgroundColor(value)
  189.         end
  190.     end
  191.     return
  192. end
  193.  
  194. local function textRedirect (id)
  195.     local textTable = {}
  196.     textTable.id = id
  197.     textTable.write = function(text)
  198.         return send(textTable.id, "textWrite", text)
  199.     end
  200.     textTable.clear = function()
  201.         return send(textTable.id, "textClear", "nil")
  202.     end
  203.     textTable.clearLine = function()
  204.         return send(textTable.id, "textClearLine", "nil")
  205.     end
  206.     textTable.getCursorPos = function()
  207.         send(textTable.id, "textGetCursorPos", "nil")
  208.         local pType, message = awaitResponse(textTable.id, 2)
  209.         if pType and pType == "textInfo" then
  210.             local x, y = string.match(message, "(%d+),(%d+)")
  211.             return tonumber(x), tonumber(y)
  212.         end
  213.     end
  214.     textTable.setCursorPos = function(x, y)
  215.         return send(textTable.id, "textCursorPos", math.floor(x)..","..math.floor(y))
  216.     end
  217.     textTable.setCursorBlink = function(b)
  218.         if b then
  219.             return send(textTable.id, "textBlink", "true")
  220.         else
  221.             return send(textTable.id, "textBlink", "false")
  222.         end
  223.     end
  224.     textTable.getSize = function()
  225.         send(textTable.id, "textGetSize", "nil")
  226.         local pType, message = awaitResponse(textTable.id, 2)
  227.         if pType and pType == "textInfo" then
  228.             local x, y = string.match(message, "(%d+),(%d+)")
  229.             return tonumber(x), tonumber(y)
  230.         end
  231.     end
  232.     textTable.scroll = function(lines)
  233.         return send(textTable.id, "textScroll", lines)
  234.     end
  235.     textTable.isColor = function()
  236.         send(textTable.id, "textIsColor", "nil")
  237.         local pType, message = awaitResponse(textTable.id, 2)
  238.         if pType and pType == "textInfo" then
  239.             if message == "true" then
  240.                 return true
  241.             end
  242.         end
  243.         return false
  244.     end
  245.     textTable.isColour = textTable.isColor
  246.     textTable.setTextColor = function(color)
  247.         return send(textTable.id, "textColor", tostring(color))
  248.     end
  249.     textTable.setTextColour = textTable.setTextColor
  250.     textTable.setBackgroundColor = function(color)
  251.         return send(textTable.id, "textBackground", tostring(color))
  252.     end
  253.     textTable.setBackgroundColour = textTable.setBackgroundColor
  254.     return textTable
  255. end
  256.  
  257. local eventFilter = {
  258.     key = true,
  259.     char = true,
  260.     mouse_click = true,
  261.     mouse_drag = true,
  262.     mouse_scroll = true,
  263. }
  264.  
  265. local function newSession()
  266.     local path = "/shell"
  267.     if #tArgs >= 2 and shell.resolveProgram(tArgs[2]) then path = shell.resolveProgram(tArgs[2]) end
  268.     local sessionThread = coroutine.create(function() shell.run(path) end)
  269.     return sessionThread
  270. end
  271.  
  272. if #tArgs >= 1 and tArgs[1] == "host" then
  273.     _G.nsh = nshAPI
  274.     _G.nsh = nshAPI
  275.     if not openModem() then return end
  276.     local connInfo = {}
  277.     connInfo.target = term.native
  278.     local path = "/shell"
  279.     if #tArgs >= 3 and shell.resolveProgram(tArgs[3]) then path = shell.resolveProgram(tArgs[3]) end
  280.     connInfo.thread = coroutine.create(function() shell.run(path) end)
  281.     connections.localShell = connInfo
  282.     term.clear()
  283.     term.setCursorPos(1,1)
  284.     coroutine.resume(connections.localShell.thread)
  285.  
  286.     while true do
  287.         event = {os.pullEventRaw()}
  288.         if event[1] == "rednet_message" then
  289.             if packetConversion[string.sub(event[3], 1, 2)] then
  290.                 --this is a packet meant for us.
  291.                 conn = event[2]
  292.                 packetType = packetConversion[string.sub(event[3], 1, 2)]
  293.                 message = string.match(event[3], ";(.*)")
  294.                 if connections[conn] and connections[conn].status == "open" then
  295.                     if packetType == "event" or string.sub(packetType, 1, 4) == "text" then
  296.                         local eventTable = {}
  297.                         if packetType == "event" then
  298.                             eventTable = textutils.unserialize(message)
  299.                         else
  300.                             --we can pass the packet in raw, since this is not an event packet.
  301.                             eventTable = event
  302.                         end
  303.                         if not connections[conn].filter or eventTable[1] == connections[conn].filter then
  304.                             connections[conn].filter = nil
  305.                             term.redirect(connections[conn].target)
  306.                             passback = {coroutine.resume(connections[conn].thread, unpack(eventTable))}
  307.                             if passback[1] and passback[2] then
  308.                                 connections[conn].filter = passback[2]
  309.                             end
  310.                             if coroutine.status(connections[conn].thread) == "dead" then
  311.                                 send(conn, "close", "disconnect")
  312.                                 table.remove(connections, conn)
  313.                             end
  314.                             term.restore()
  315.                         end
  316.                     elseif packetType == "query" then
  317.                         --reset connection
  318.                         connections[conn].status = "open"
  319.                         connections[conn].target = textRedirect(conn)
  320.                         connections[conn].thread = newSession()
  321.                         send(conn, "response", "OK")
  322.                         term.redirect(connections[conn].target)
  323.                         coroutine.resume(connections[conn].thread)
  324.                         term.restore()
  325.                     elseif packetType == "close" then
  326.                         table.remove(connections, conn)
  327.                         send(conn, "close", "disconnect")
  328.                         --close connection
  329.                     else
  330.                         --we got a packet, have an open connection, but despite it being in the conversion table, don't handle it ourselves. Send it onward.
  331.                         if not connections[conn].filter or eventTable[1] == connections[conn].filter then
  332.                             connections[conn].filter = nil
  333.                             term.redirect(connections[conn].target)
  334.                             passback = {coroutine.resume(connections[conn].thread, unpack(event))}
  335.                             if passback[2] then
  336.                                 connections[conn].filter = passback[2]
  337.                             end
  338.                             if coroutine.status(connections[conn].thread) == "dead" then
  339.                                 send(conn, "close", "disconnect")
  340.                                 table.remove(connections, conn)
  341.                             end
  342.                             term.restore()
  343.                         end
  344.                     end
  345.                 elseif packetType ~= "query" then
  346.                     --usually, we would send a disconnect here, but this prevents one from hosting nsh and connecting to other computers.  Pass these to all shells as well.
  347.                     for cNum, cInfo in pairs(connections) do
  348.                         if not cInfo.filter or event[1] == cInfo.filter then
  349.                             cInfo.filter = nil
  350.                             term.redirect(cInfo.target)
  351.                             passback = {coroutine.resume(cInfo.thread, unpack(event))}
  352.                             if passback[2] then
  353.                                 cInfo.filter = passback[2]
  354.                             end
  355.                             term.restore()
  356.                         end
  357.                     end
  358.                 else
  359.                     --open new connection
  360.                     local connInfo = {}
  361.                     connInfo.status = "open"
  362.                     connInfo.target = textRedirect(conn)
  363.                     connInfo.thread = newSession()
  364.                     send(conn, "response", "OK")
  365.                     connections[conn] = connInfo
  366.                     term.redirect(connInfo.target)
  367.                     coroutine.resume(connInfo.thread)
  368.                     term.restore()
  369.                 end
  370.             else
  371.                 --rednet message, but not in the correct format, so pass to all shells.
  372.                 for cNum, cInfo in pairs(connections) do
  373.                     if not cInfo.filter or event[1] == cInfo.filter then
  374.                         cInfo.filter = nil
  375.                         term.redirect(cInfo.target)
  376.                         passback = {coroutine.resume(cInfo.thread, unpack(event))}
  377.                         if passback[2] then
  378.                             cInfo.filter = passback[2]
  379.                         end
  380.                         term.restore()
  381.                     end
  382.                 end
  383.             end
  384.         elseif event[1] == "mouse_click" or event[1] == "mouse_drag" or event[1] == "mouse_scroll" or event[1] == "key" or event[1] == "char" then
  385.             --user interaction.
  386.             coroutine.resume(connections.localShell.thread, unpack(event))
  387.             if coroutine.status(connections.localShell.thread) == "dead" then
  388.                 for cNum, cInfo in pairs(connections) do
  389.                     if cNum ~= "localShell" then
  390.                         send(cNum, "close", "disconnect")
  391.                     end
  392.                 end
  393.                 return
  394.             end
  395.         elseif event[1] == "terminate" then
  396.             _G.nsh = nil
  397.             return
  398.         else
  399.             --dispatch all other events to all shells
  400.             for cNum, cInfo in pairs(connections) do
  401.                 if not cInfo.filter or event[1] == cInfo.filter then
  402.                     cInfo.filter = nil
  403.                     term.redirect(cInfo.target)
  404.                     passback = {coroutine.resume(cInfo.thread, unpack(event))}
  405.                     if passback[2] then
  406.                         cInfo.filter = passback[2]
  407.                     end
  408.                     term.restore()
  409.                 end
  410.             end
  411.         end
  412.     end
  413.  
  414. elseif #tArgs == 1 and nsh and nsh.getRemoteID() then
  415.     print(nsh.getRemoteID())
  416.     --forwarding mode
  417.     local conns = nsh.getRemoteConnections()
  418.     for i = 1, #conns do
  419.         if conns[i] == serverNum then
  420.             print("Cyclic connection refused.")
  421.             return
  422.         end
  423.     end
  424.     local fileTransferState = nil
  425.     local fileData = nil
  426.     local serverNum = tonumber(tArgs[1])
  427.     send(serverNum, "query", "connect")
  428.     local pType, message = awaitResponse(serverNum, 2)
  429.     if pType ~= "response" then
  430.         print("Connection Failed")
  431.         return
  432.     else
  433.         nsh.connList[nsh.getRemoteID()].outbound = serverNum
  434.         term.clear()
  435.         term.setCursorPos(1,1)
  436.     end
  437.     local clientID = nsh.getRemoteID()
  438.     local serverID = tonumber(tArgs[1])
  439.     while true do
  440.         event = {os.pullEvent()}
  441.         if event[1] == "rednet_message" then
  442.             if event[2] == clientID or event[2] == serverID then
  443.                 if event[2] == serverID and string.sub(event[3], 1, 2) == "SC" then break end
  444.                 rednet.send((event[2] == clientID and serverID or clientID), event[3])
  445.             end
  446.         elseif eventFilter[event[1]] then
  447.             rednet.send(serverID, "EV:;"..textutils.serialize(event))
  448.         end
  449.     end
  450.     nsh.connList[nsh.getRemoteID()].outbound = nil
  451.     term.clear()
  452.     term.setCursorPos(1, 1)
  453.     print("Connection closed by server")
  454.  
  455. elseif #tArgs == 1 then --either no server running or we are the local shell on the server.
  456.     local serverNum = tonumber(tArgs[1])
  457.     if nsh then
  458.         local conns = nsh.getRemoteConnections()
  459.         for i = 1, #conns do
  460.             if conns[i] == serverNum then
  461.                 print("Connection refused.")
  462.                 return
  463.             end
  464.         end
  465.     end
  466.     local fileTransferState = nil
  467.     local fileData = nil
  468.     if not openModem() then return end
  469.     send(serverNum, "query", "connect")
  470.     local pType, message = awaitResponse(serverNum, 2)
  471.     if pType ~= "response" then
  472.         print("Connection failed.")
  473.         return
  474.     else
  475.         if nsh then nshAPI = nsh end
  476.         if nshAPI.connList and nshAPI.connList.localShell then nshAPI.connList.localShell.outbound = serverNum end
  477.         nshAPI.serverNum = serverNum
  478.         nshAPI.clientCapabilities = "-fileTransfer-extensions-"
  479.         term.clear()
  480.         term.setCursorPos(1,1)
  481.     end
  482.  
  483.     while true do
  484.         event = {os.pullEventRaw()}
  485.         if event[1] == "rednet_message" and event[2] == serverNum then
  486.             if packetConversion[string.sub(event[3], 1, 2)] then
  487.                 packetType = packetConversion[string.sub(event[3], 1, 2)]
  488.                 message = string.match(event[3], ";(.*)")
  489.                 if string.sub(packetType, 1, 4) == "text" then
  490.                     processText(serverNum, packetType, message)
  491.                 elseif packetType == "data" then
  492.                     if message == "clientCapabilities" then
  493.                         rednet.send(serverNum, nshAPI.clientCapabilities)
  494.                     end
  495.                 elseif packetType == "fileQuery" then
  496.                     --send a file to the server
  497.                     if fs.exists(message) then
  498.                         send(serverNum, "fileHeader", message)
  499.                         local file = io.open(message, "r")
  500.                         if file then
  501.                             send(serverNum, "fileData", file:read("*a"))
  502.                             file:close()
  503.                         end
  504.                     else
  505.                         send(serverNum, "fileHeader", "fileNotFound")
  506.                     end
  507.                     send(serverNum, "fileEnd", "end")
  508.                 elseif packetType == "fileSend" then
  509.                     --receive a file from the server, but don't overwrite existing files.
  510.                     if not fs.exists(message) then
  511.                         fileTransferState = "receive_wait:"..message
  512.                         send(serverNum, "fileResponse", "ok")
  513.                         fileData = ""
  514.                     else
  515.                         send(serverNum, "fileResponse", "reject")
  516.                     end
  517.                 elseif packetType == "fileHeader" then
  518.                     if message == "fileNotFound" then
  519.                         fileTransferState = nil
  520.                     end
  521.                 elseif packetType == "fileData" then
  522.                     if fileTransferState and string.match(fileTransferState, "(.-):") == "receive_wait" then
  523.                         fileData = fileData..message
  524.                     end
  525.                 elseif packetType == "fileEnd" then
  526.                     if fileTransferState and string.match(fileTransferState, "(.-):") == "receive_wait" then
  527.                         local file = io.open(string.match(fileTransferState, ":(.*)"), "w")
  528.                         if file then
  529.                             file:write(fileData)
  530.                             file:close()
  531.                         end
  532.                         fileTransferState = nil
  533.                     end
  534.                 elseif packetType == "close" then
  535.                     if term.isColor() then
  536.                         term.setBackgroundColor(colors.black)
  537.                         term.setTextColor(colors.white)
  538.                     end
  539.                     term.clear()
  540.                     term.setCursorPos(1, 1)
  541.                     print("Connection closed by server.")
  542.                     nshAPI.serverNum = nil
  543.                     if nshAPI.connList and nshAPI.connList.localShell then nshAPI.connList.localShell.outbound = nil end
  544.                     return
  545.                 end
  546.             end
  547.         elseif event[1] == "mouse_click" or event[1] == "mouse_drag" or event[1] == "mouse_scroll" or event[1] == "key" or event[1] == "char" then
  548.             --pack up event
  549.             send(serverNum, "event", textutils.serialize(event))
  550.         elseif event[1] == "terminate" then
  551.             nshAPI.serverNum = nil
  552.             if nshAPI.localShell then nshAPI.localShell.outbound = nil end
  553.             return
  554.         end
  555.     end
  556. else
  557.     print("Usage: nsh <serverID>")
  558.     print("       nsh host [remote [local]]")
  559. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement