Lion4ever

IRC Turtle Bot

Jul 15th, 2014
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.53 KB | None | 0 0
  1. --[[
  2. IRC Controlled Turtle Scipt
  3. Lion4ever, 2014
  4. http://pastebin.com/sPpkBP9v
  5.  
  6.  
  7. Heavyly base on the CC IRC Client from:
  8. Matti Vapa, 2014
  9. https://github.com/mattijv/IRCCC/
  10. http://pastebin.com/gaSL8HZC
  11.  
  12. This program is released under the MIT license.
  13.  
  14. ]]--
  15.  
  16. --Preventing second instance (but not third):
  17. if isRunning then
  18.     isRunning = false --leaving no traces
  19.     return false
  20. end
  21. isRunning = true
  22.  
  23. -- look at the source of the qwebirc webchat login page and take the values
  24. -- for baseUrl and dynamicBaseUrl for use here
  25. -- examples for espernet and quakenet
  26.  
  27. local baseUrl = "http://webchat.esper.net/"
  28. local dynamicUrl = ""
  29. --local baseUrl = "http://webchat.quakenet.org/"
  30. --local dynamicUrl = "dynamic/leibniz/"
  31.  
  32.  
  33. --------------------------------------------------------------------------------
  34. --------------------------------------------------------------------------------
  35. -- JSON4Lua: JSON encoding / decoding support for the Lua language.
  36. -- json Module.
  37. -- Author: Craig Mason-Jones
  38. -- Homepage: http://json.luaforge.net/
  39. -- Version: 0.9.40
  40. -- This module is released under the MIT License (MIT).
  41.  
  42. -- edited for brevity
  43.  
  44. local base = _G
  45. local decode_scanArray
  46. local decode_scanComment
  47. local decode_scanConstant
  48. local decode_scanNumber
  49. local decode_scanObject
  50. local decode_scanString
  51. local decode_scanWhitespace
  52. local encodeString
  53. local isArray
  54. local isEncodable
  55. local function encode (v)
  56.   -- Handle nil values
  57.   if v==nil then
  58.     return "null"
  59.   end
  60.  
  61.   local vtype = base.type(v)  
  62.  
  63.   -- Handle strings
  64.   if vtype=='string' then    
  65.     return '"' .. encodeString(v) .. '"'            -- Need to handle encoding in string
  66.   end
  67.  
  68.   -- Handle booleans
  69.   if vtype=='number' or vtype=='boolean' then
  70.     return base.tostring(v)
  71.   end
  72.  
  73.   -- Handle tables
  74.   if vtype=='table' then
  75.     local rval = {}
  76.     -- Consider arrays separately
  77.     local bArray, maxCount = isArray(v)
  78.     if bArray then
  79.       for i = 1,maxCount do
  80.         table.insert(rval, encode(v[i]))
  81.       end
  82.     else        -- An object, not an array
  83.       for i,j in base.pairs(v) do
  84.         if isEncodable(i) and isEncodable(j) then
  85.           table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
  86.         end
  87.       end
  88.     end
  89.     if bArray then
  90.       return '[' .. table.concat(rval,',') ..']'
  91.     else
  92.       return '{' .. table.concat(rval,',') .. '}'
  93.     end
  94.   end
  95.  
  96.   -- Handle null values
  97.   if vtype=='function' and v==null then
  98.     return 'null'
  99.   end
  100.  
  101.   base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
  102. end
  103.  
  104. local function decode(s, startPos)
  105.   startPos = startPos and startPos or 1
  106.   startPos = decode_scanWhitespace(s,startPos)
  107.   base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
  108.   local curChar = string.sub(s,startPos,startPos)
  109.   -- Object
  110.   if curChar=='{' then
  111.     return decode_scanObject(s,startPos)
  112.   end
  113.   -- Array
  114.   if curChar=='[' then
  115.     return decode_scanArray(s,startPos)
  116.   end
  117.   -- Number
  118.   if string.find("+-0123456789.e", curChar, 1, true) then
  119.     return decode_scanNumber(s,startPos)
  120.   end
  121.   -- String
  122.   if curChar==[["]] or curChar==[[']] then
  123.    return decode_scanString(s,startPos)
  124.  end
  125.  if string.sub(s,startPos,startPos+1)=='/*' then
  126.    return decode(s, decode_scanComment(s,startPos))
  127.  end
  128.  -- Otherwise, it must be a constant
  129.  return decode_scanConstant(s,startPos)
  130. end
  131.  
  132. local function null()
  133.  return null -- so json.null() will also return null ;-)
  134. end
  135.  
  136.  
  137. function decode_scanArray(s,startPos)
  138.  local array = {}      -- The return value
  139.  local stringLen = string.len(s)
  140.  base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
  141.  startPos = startPos + 1
  142.  -- Infinite loop for array elements
  143.  repeat
  144.    startPos = decode_scanWhitespace(s,startPos)
  145.    base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
  146.    local curChar = string.sub(s,startPos,startPos)
  147.    if (curChar==']') then
  148.      return array, startPos+1
  149.    end
  150.    if (curChar==',') then
  151.      startPos = decode_scanWhitespace(s,startPos+1)
  152.    end
  153.    base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
  154.    object, startPos = decode(s,startPos)
  155.    table.insert(array,object)
  156.  until false
  157. end
  158.  
  159. function decode_scanComment(s, startPos)
  160.  base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
  161.  local endPos = string.find(s,'*/',startPos+2)
  162.  base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
  163.  return endPos+2  
  164. end
  165.  
  166. function decode_scanConstant(s, startPos)
  167.  local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
  168.  local constNames = {"true","false","null"}
  169.  
  170.  for i,k in base.pairs(constNames) do
  171.    --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
  172.    if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
  173.      return consts[k], startPos + string.len(k)
  174.    end
  175.  end
  176.  base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
  177. end
  178.  
  179. function decode_scanNumber(s,startPos)
  180.  local endPos = startPos+1
  181.  local stringLen = string.len(s)
  182.  local acceptableChars = "+-0123456789.e"
  183.  while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
  184.        and endPos<=stringLen
  185.        ) do
  186.    endPos = endPos + 1
  187.  end
  188.  local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
  189.  local stringEval = base.loadstring(stringValue)
  190.  base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
  191.  return stringEval(), endPos
  192. end
  193.  
  194. function decode_scanObject(s,startPos)
  195.  local object = {}
  196.  local stringLen = string.len(s)
  197.  local key, value
  198.  base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
  199.  startPos = startPos + 1
  200.  repeat
  201.    startPos = decode_scanWhitespace(s,startPos)
  202.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
  203.    local curChar = string.sub(s,startPos,startPos)
  204.    if (curChar=='}') then
  205.      return object,startPos+1
  206.    end
  207.    if (curChar==',') then
  208.      startPos = decode_scanWhitespace(s,startPos+1)
  209.    end
  210.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
  211.    -- Scan the key
  212.    key, startPos = decode(s,startPos)
  213.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  214.    startPos = decode_scanWhitespace(s,startPos)
  215.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  216.    base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
  217.    startPos = decode_scanWhitespace(s,startPos+1)
  218.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  219.    value, startPos = decode(s,startPos)
  220.    object[key]=value
  221.  until false   -- infinite loop while key-value pairs are found
  222. end
  223.  
  224. function decode_scanString(s,startPos)
  225.  base.assert(startPos, 'decode_scanString(..) called without start position')
  226.  local startChar = string.sub(s,startPos,startPos)
  227.  base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
  228.   local escaped = false
  229.   local endPos = startPos + 1
  230.   local bEnded = false
  231.   local stringLen = string.len(s)
  232.   repeat
  233.     local curChar = string.sub(s,endPos,endPos)
  234.     -- Character escaping is only used to escape the string delimiters
  235.     if not escaped then
  236.       if curChar==[[\]] then
  237.         escaped = true
  238.       else
  239.         bEnded = curChar==startChar
  240.       end
  241.     else
  242.       -- If we're escaped, we accept the current character come what may
  243.       escaped = false
  244.     end
  245.     endPos = endPos + 1
  246.     base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
  247.   until bEnded
  248.   local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
  249.   local stringEval = base.loadstring(stringValue)
  250.   base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
  251.   return stringEval(), endPos  
  252. end
  253.  
  254. function decode_scanWhitespace(s,startPos)
  255.   local whitespace=" \n\r\t"
  256.   local stringLen = string.len(s)
  257.   while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
  258.     startPos = startPos + 1
  259.   end
  260.   return startPos
  261. end
  262.  
  263. function encodeString(s)
  264.   s = string.gsub(s,'\\','\\\\')
  265.   s = string.gsub(s,'"','\\"')
  266.   s = string.gsub(s,'\n','\\n')
  267.   s = string.gsub(s,'\t','\\t')
  268.   return s
  269. end
  270.  
  271. function isArray(t)
  272.   -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
  273.   -- (with the possible exception of 'n')
  274.   local maxIndex = 0
  275.   for k,v in base.pairs(t) do
  276.     if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then      -- k,v is an indexed pair
  277.       if (not isEncodable(v)) then return false end     -- All array elements must be encodable
  278.       maxIndex = math.max(maxIndex,k)
  279.     else
  280.       if (k=='n') then
  281.         if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
  282.       else -- Else of (k=='n')
  283.         if isEncodable(v) then return false end
  284.       end  -- End of (k~='n')
  285.     end -- End of k,v not an indexed pair
  286.   end  -- End of loop across all pairs
  287.   return true, maxIndex
  288. end
  289.  
  290. function isEncodable(o)
  291.   local t = base.type(o)
  292.   return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
  293. end
  294.  
  295. --------------------------------------------------------------------------------
  296. --------------------------------------------------------------------------------
  297.  
  298. -- ComputerCraft IRC bot code begins here
  299.  
  300. --------------------------------------------------------------------------------
  301. --------------------------------------------------------------------------------
  302. local exeTime = 120
  303. local defaultNick = "cc-turtle-bot"..tostring(math.random(1,1000))
  304. local nick = defaultNick
  305. local newNick = ""
  306.  
  307. local counter = 0 -- used by the qwebirc protocol for something
  308. local sessionID = "" -- as is this, although a bit more self evident
  309.  
  310. local currentChannel = "#cctesting"
  311. local quitReason = ""
  312.  
  313. local actions = {
  314.     ["f"] = turtle.forward,
  315.     ["fd"] = turtle.forward,
  316.     ["forwards"] = turtle.forward,
  317.     ["b"] = turtle.back,
  318.     ["bk"] = turtle.back,
  319.     ["u"] = turtle.up,
  320.     ["d"] = turtle.down,
  321.     ["dn"] = turtle.down,
  322.     ["down"] = turtle.down,
  323.     ["l"] = turtle.turnLeft,
  324.     ["lt"] = turtle.turnLeft,
  325.     ["left"] = turtle.turnLeft,
  326.     ["r"] = turtle.turnRight,
  327.     ["rt"] = turtle.turnRight,
  328.     ["right"] = turtle.turnRight,
  329.     ["dg"] = turtle.dig,
  330.     ["dig"] = turtle.dig,
  331.     ["du"] = turtle.digUp,
  332.     ["dd"] = turtle.digDown,
  333.     ["da"] = function() return turtle.digUp() or turtle.dig() or turtle.digDown() end,
  334.     ["df"] = function() turtle.dig() return turtle.forward() end,
  335.     ["dgd"] = function() turtle.digDown() return turtle.down() end,
  336.     ["dgu"] = function() turtle.digUp() return turtle.up() end,
  337.     ["sn"] = function() turtle.select((turtle.getSelectedSlot()) % 16 + 1) return true end,
  338.     ["sp"] = function() turtle.select((turtle.getSelectedSlot() +14) % 16 + 1) return true end,
  339.     ["gic"] = function() return turtle.getItemCount(turtle.getSelectedSlot()) end,
  340.     ["gis"] = function() return turtle.getItemCount(turtle.getSelectedSlot()) end,
  341.     ["rfo"] = function() return turtle.refuel(1) end,
  342.     ["do"] = function() return turtle.drop(1) end,
  343.     ["ca"] = function() local r={} for i=1,16 do if turtle.compareTo(i) then table.insert(r,i) end end return r end,
  344.     ["tn"] = function() return turtle.transferTo((turtle.getSelectedSlot()) % 16 + 1) end,
  345.     ["ton"] = function() return turtle.transferTo((turtle.getSelectedSlot()) % 16 + 1,1) end,
  346.     ["c"] = turtle.craft,
  347.     ["cp"] = turtle.compare,
  348.     ["cpu"] = turtle.compareUp,
  349.     ["cpd"] = turtle.compareDown,
  350.     ["rf"] = turtle.refuel,
  351.     ["el"] = turtle.equipLeft,
  352.     ["er"] = turtle.equipRight,
  353.     ["a"] = turtle.attack,
  354.     ["au"] = turtle.attackUp,
  355.     ["ad"] = turtle.attackDown,
  356.     ["p"] = turtle.place,
  357.     ["pu"] = turtle.placeUp,
  358.     ["pd"] = turtle.placeDown,
  359.     ["dt"] = turtle.detect,
  360.     ["dtu"] = turtle.detectUp,
  361.     ["dtd"] = turtle.detectDown,
  362.     ["gfl"] = turtle.getFuelLevel,
  363.     ["gflm"] = turtle.getFuelLimit,
  364.     ["id"] = os.getComputerID,
  365.     ["label"] = os.getComputerLabel,
  366.     ["day"] = os.day,
  367.     ["time"] = os.time,
  368.     ["commands"] = function() local result = "Commands: " for i,j in pairs(actions) do result=result..i..", " end return result:sub(1,-3).." Put m infront (mf instead f) for not aborting. Put n infront for negated return value. Loops are \"wl detect forward end\" or \"for 3 up end\"." end
  369. }
  370. for i,j in pairs(turtle) do
  371.     if i ~= "native" then
  372.         actions[i] = j
  373.     end
  374. end
  375. local split
  376. local writeResult
  377. local send
  378. local runCode = function(code)
  379.     local result = "Everything went fine"
  380.     local exe = function()
  381.         local step = 1
  382.         local inst = split(code, " ")
  383.         local markers = {}
  384.         local succ = true
  385.         while step <= #inst and succ do
  386.             local optio = inst[step]:sub(1,1) == "m"
  387.             local negat = inst[step]:sub(1,1) == "n"
  388.             local cmd = (optio or negat) and inst[step]:sub(2) or inst[step]
  389.             if actions[cmd] then
  390.                 succ,result = actions[cmd]()
  391.                 succ = optio or (type(succ) =="boolean" and succ ~= negat) or (type(succ) ~="boolean" and succ)
  392.             elseif cmd:sub(1,1) == "s" and tonumber(cmd:sub(2) or "a") then
  393.                 turtle.select(tonumber(cmd:sub(2)))
  394.             elseif cmd == "wl" and step < #inst then
  395.                 if not markers[1] or markers[1][1] ~= step then
  396.                     table.insert(markers,1,{step})
  397.                 end
  398.                 local negat = inst[step+1]:sub(1,1) == "n"
  399.                 local concmd = negat and inst[step+1]:sub(2) or inst[step+1]
  400.                 if not actions[concmd] then
  401.                     succ, result = false,"Unknown Command:"..inst[step+1]
  402.                 else
  403.                     local cond = actions[concmd]()
  404.                     if (type(cond) == "boolean" and cond ~= negat) or (type(cond) == "number" and ((cond >0) ~= negat)) then
  405.                         step = step + 1
  406.                     else
  407.                         while step <= #inst and inst[step] ~= "end" do step = step +1 end
  408.                         table.remove(markers,1)
  409.                     end
  410.                 end
  411.             elseif cmd == "for" and step < #inst then
  412.                 if not markers[1] or markers[1][1] ~= step then
  413.                     table.insert(markers,1,{step,tonumber(inst[step+1]) or 1})
  414.                 end
  415.                 markers[1][2] = markers[1][2]-1
  416.                 step = step + 1
  417.             elseif cmd == "end" and #markers > 0 then
  418.                 sleep(0)
  419.                 if inst[markers[1][1]] ~= "for" or markers[1][2] >0 then
  420.                     step = markers[1][1]-1
  421.                 else
  422.                     table.remove(markers,1)
  423.                 end
  424.             elseif cmd == "send" then
  425.                 send("PRIVMSG "..currentChannel.." :"..writeResult({succ,result},""))
  426.             else
  427.                 succ, result = false,"Unknown Command:"..cmd
  428.             end
  429.             step = step + 1
  430.         end
  431.         result = (succ == true) and "Success" or (succ == false) and {"Failed",result,"On "..tostring(step-1)..". Operation"} or {succ,result}
  432.         result = writeResult(result,"")
  433.     end
  434.     local timeout = function()
  435.         sleep(exeTime)
  436.         result = "Operation timeout of "..tostring(exeTime).." exceeded!"
  437.     end
  438.     parallel.waitForAny(exe,timeout)
  439.     return result
  440. end
  441.  
  442.  
  443. local post = function(url,data)
  444.     local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
  445.     local resp = http.post(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
  446.     counter = counter + 1
  447.     return resp
  448. end
  449.  
  450. -- better responsiveness with asynchronous methods as we usually let the recv couroutine handle the responses
  451. local request = function(url,data)
  452.     local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
  453.     http.request(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
  454.     counter = counter + 1
  455. end
  456.  
  457. -- these special URLs are used by the webchat server for different methods
  458. send = function(data)
  459.     return request("p","&s="..sessionID.."&c="..textutils.urlEncode(data))
  460. end
  461.  
  462. local recv = function()
  463.     return post("s","&s="..sessionID)
  464. end
  465.  
  466. local connect = function()
  467.     return post("n","&nick="..nick)
  468. end
  469.  
  470. -- some helper functions
  471. local pong = function(data)
  472.     return send("PONG :"..data)
  473. end
  474.  
  475. local quit = function(_reason)
  476.     local reason = _reason or ""
  477.     return send("QUIT :"..reason)
  478. end
  479.  
  480. -- lua default string methods suck :/
  481. split = function(str,sep)
  482.         local sep, fields = sep or ":", {}
  483.         local pattern = string.format("([^%s]+)", sep)
  484.         str:gsub(pattern, function(c) fields[#fields+1] = c end)
  485.         return fields
  486. end
  487.  
  488.  
  489. -- for debug
  490.  
  491. writeResult = function(data,line)
  492.     if not (type(data) == "table") then
  493.         return tostring(data)
  494.     end
  495.     for i,j in pairs(data) do
  496.         if type(j) == "table" then
  497.             line = writeResult(j,line.."(").."), "
  498.         else
  499.             line = line..tostring(j)..", "
  500.         end
  501.     end
  502.     return line:sub(1,-3)
  503. end
  504. local lastSwitch = 0
  505. local handleResponse = function(data)
  506.     for i = 1, #data do
  507.         local id = type(data) =="table" and type(data[i]) =="table" and data[i][2] or sessionID
  508.         if id == "PING" then
  509.             pong(data[i][4][1])
  510.         elseif id == "PRIVMSG" then
  511.             local senderDetails = data[i][3]
  512.             local sender = senderDetails:sub(1,senderDetails:find("!")-1)
  513.             local channel = data[i][4][1]:lower()
  514.             local msg = data[i][4][2]
  515.             if msg:sub(1,32) == "change channel of turtle bot to " and lastSwitch ~= os.day() then
  516.                 local oldChannel = currentChannel
  517.                 currentChannel = msg:sub(33,33) == "#" and msg:sub(33) or "#"..msg:sub(33)
  518.                 send("JOIN "..currentChannel)
  519.                 send("PART "..oldChannel.." [Changing channel to "..currentChannel.."]")               
  520.             end
  521.             if msg == "update this minecraft irc bot" then
  522.                 send("QUIT Updating")
  523.                 shell.run("pastebin get sPpkBP9v .ircbot2")
  524.                 if fs.exists(".ircbot2") then
  525.                     if fs.exists(".ircbot") then
  526.                         fs.delete(".ircbot")
  527.                     end
  528.                     fs.move(".ircbot2",".ircbot")
  529.                 end
  530.                 os.reboot()
  531.             end
  532.             if msg:sub(1,3) == "go " then
  533.                 local r = runCode(msg:sub(4))
  534.                 send("PRIVMSG "..currentChannel.." :"..tostring(r or "Operation successful"))
  535.             end
  536.             --print("<"..sender.."> "..msg)
  537.         elseif id == "376" then
  538.             send("JOIN "..currentChannel)
  539.         end
  540.     end
  541. end
  542.  
  543. local receive = function()
  544.     local resp = connect()
  545.     if not resp then print(os.version()) return end
  546.     local _data = resp.readAll()
  547.     local data = decode(_data)
  548.     sessionID = data[2]
  549.     resp = recv()
  550.     while resp do
  551.         _data = resp.readAll()
  552.         if #_data > 0 then
  553.             data = decode(_data)
  554.             handleResponse(data)
  555.         end
  556.         resp = recv()
  557.     end
  558.     quitReason = "Disconnected!"
  559. end
  560.  
  561. local fakeShell = function() --copied from /rom/programs/shell
  562.     if term.isColour() then
  563.         promptColour = colours.yellow
  564.         textColour = colours.white
  565.         bgColour = colours.black
  566.     else
  567.         promptColour = colours.white
  568.         textColour = colours.white
  569.         bgColour = colours.black
  570.     end
  571.     print(os.version())
  572.     local tCommandHistory = {}
  573.     while not bExit do
  574.         term.setBackgroundColor( bgColour )
  575.         term.setTextColour( promptColour )
  576.         write( shell.dir() .. "> " )
  577.         term.setTextColour( textColour )
  578.  
  579.         local sLine = read( nil, tCommandHistory )
  580.         table.insert( tCommandHistory, sLine )
  581.         pcall(function() shell.run( sLine ) end)
  582.     end
  583. end
  584.  
  585.  
  586. term.clear()
  587. term.setCursorPos(1,1)
  588. parallel.waitForAny(receive,fakeShell)
Advertisement
Add Comment
Please, Sign In to add comment