nonogamer9

OC-TELNET : An Telnet Client For OpenComputers

Aug 7th, 2024 (edited)
413
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 32.40 KB | Software | 0 0
  1. local component = require("component")
  2. local event = require("event")
  3. local term = require("term")
  4. local computer = require("computer")
  5. local keyboard = require("keyboard")
  6.  
  7. local internet = assert(component.internet, "Internet card is required")
  8. local gpu = assert(component.gpu, "GPU is required")
  9.  
  10. local TELNET = {
  11.     IAC  = string.char(255),
  12.     DONT = string.char(254),
  13.     DO   = string.char(253),
  14.     WONT = string.char(252),
  15.     WILL = string.char(251),
  16.     SB   = string.char(250),
  17.     SE   = string.char(240),
  18.  
  19.     ECHO               = 1,
  20.     SUPPRESS_GO_AHEAD  = 3,
  21.     TERMINAL_TYPE      = 24,
  22.     NAWS               = 31,
  23. }
  24.  
  25. local TERMINAL_TYPE_IS = 0
  26. local TERMINAL_TYPE_SEND = 1
  27.  
  28. local TERMINAL_TYPES = {"ANSI", "ANSI-BBS", "VT100"}
  29.  
  30. local DEFAULT_PORT = 23
  31. local CONNECT_TIMEOUT = 10
  32. local READ_SIZE = 4096
  33.  
  34. local cp437_to_utf8 = {
  35.     [0x80]="\u{00C7}",[0x81]="\u{00FC}",[0x82]="\u{00E9}",[0x83]="\u{00E2}",
  36.     [0x84]="\u{00E4}",[0x85]="\u{00E0}",[0x86]="\u{00E5}",[0x87]="\u{00E7}",
  37.     [0x88]="\u{00EA}",[0x89]="\u{00EB}",[0x8A]="\u{00E8}",[0x8B]="\u{00EF}",
  38.     [0x8C]="\u{00EE}",[0x8D]="\u{00EC}",[0x8E]="\u{00C4}",[0x8F]="\u{00C5}",
  39.     [0x90]="\u{00C9}",[0x91]="\u{00E6}",[0x92]="\u{00C6}",[0x93]="\u{00F4}",
  40.     [0x94]="\u{00F6}",[0x95]="\u{00F2}",[0x96]="\u{00FB}",[0x97]="\u{00F9}",
  41.     [0x98]="\u{00FF}",[0x99]="\u{00D6}",[0x9A]="\u{00DC}",[0x9B]="\u{00A2}",
  42.     [0x9C]="\u{00A3}",[0x9D]="\u{00A5}",[0x9E]="\u{20A7}",[0x9F]="\u{0192}",
  43.     [0xA0]="\u{00E1}",[0xA1]="\u{00ED}",[0xA2]="\u{00F3}",[0xA3]="\u{00FA}",
  44.     [0xA4]="\u{00F1}",[0xA5]="\u{00D1}",[0xA6]="\u{00AA}",[0xA7]="\u{00BA}",
  45.     [0xA8]="\u{00BF}",[0xA9]="\u{2310}",[0xAA]="\u{00AC}",[0xAB]="\u{00BD}",
  46.     [0xAC]="\u{00BC}",[0xAD]="\u{00A1}",[0xAE]="\u{00AB}",[0xAF]="\u{00BB}",
  47.     [0xB0]="\u{2591}",[0xB1]="\u{2592}",[0xB2]="\u{2593}",[0xB3]="\u{2502}",
  48.     [0xB4]="\u{2524}",[0xB5]="\u{2561}",[0xB6]="\u{2562}",[0xB7]="\u{2556}",
  49.     [0xB8]="\u{2555}",[0xB9]="\u{2563}",[0xBA]="\u{2551}",[0xBB]="\u{2557}",
  50.     [0xBC]="\u{255D}",[0xBD]="\u{255C}",[0xBE]="\u{255B}",[0xBF]="\u{2510}",
  51.     [0xC0]="\u{2514}",[0xC1]="\u{2534}",[0xC2]="\u{252C}",[0xC3]="\u{251C}",
  52.     [0xC4]="\u{2500}",[0xC5]="\u{253C}",[0xC6]="\u{255E}",[0xC7]="\u{255F}",
  53.     [0xC8]="\u{255A}",[0xC9]="\u{2554}",[0xCA]="\u{2569}",[0xCB]="\u{2566}",
  54.     [0xCC]="\u{2560}",[0xCD]="\u{2550}",[0xCE]="\u{256C}",[0xCF]="\u{2567}",
  55.     [0xD0]="\u{2568}",[0xD1]="\u{2564}",[0xD2]="\u{2565}",[0xD3]="\u{2559}",
  56.     [0xD4]="\u{2558}",[0xD5]="\u{2552}",[0xD6]="\u{2553}",[0xD7]="\u{256B}",
  57.     [0xD8]="\u{256A}",[0xD9]="\u{2518}",[0xDA]="\u{250C}",[0xDB]="\u{2588}",
  58.     [0xDC]="\u{2584}",[0xDD]="\u{258C}",[0xDE]="\u{2590}",[0xDF]="\u{2580}",
  59.     [0xE0]="\u{03B1}",[0xE1]="\u{00DF}",[0xE2]="\u{0393}",[0xE3]="\u{03C0}",
  60.     [0xE4]="\u{03A3}",[0xE5]="\u{03C3}",[0xE6]="\u{03BC}",[0xE7]="\u{03C4}",
  61.     [0xE8]="\u{03A6}",[0xE9]="\u{0398}",[0xEA]="\u{03A9}",[0xEB]="\u{03B4}",
  62.     [0xEC]="\u{221E}",[0xED]="\u{03C6}",[0xEE]="\u{03B5}",[0xEF]="\u{2229}",
  63.     [0xF0]="\u{2261}",[0xF1]="\u{00B1}",[0xF2]="\u{2265}",[0xF3]="\u{2264}",
  64.     [0xF4]="\u{2320}",[0xF5]="\u{2321}",[0xF6]="\u{00F7}",[0xF7]="\u{2248}",
  65.     [0xF8]="\u{00B0}",[0xF9]="\u{2219}",[0xFA]="\u{00B7}",[0xFB]="\u{221A}",
  66.     [0xFC]="\u{207F}",[0xFD]="\u{00B2}",[0xFE]="\u{25A0}",[0xFF]=" "
  67. }
  68.  
  69. local ansi_palette = {
  70.     [0]=0x000000,[1]=0xAA0000,[2]=0x00AA00,[3]=0xAA5500,
  71.     [4]=0x0000AA,[5]=0xAA00AA,[6]=0x00AAAA,[7]=0xAAAAAA,
  72.     [8]=0x555555,[9]=0xFF5555,[10]=0x55FF55,[11]=0xFFFF55,
  73.     [12]=0x5555FF,[13]=0xFF55FF,[14]=0x55FFFF,[15]=0xFFFFFF
  74. }
  75.  
  76. local ansi256 = {
  77.     [0]=0x000000,[1]=0x800000,[2]=0x008000,[3]=0x808000,
  78.     [4]=0x000080,[5]=0x800080,[6]=0x008080,[7]=0xC0C0C0,
  79.     [8]=0x808080,[9]=0xFF0000,[10]=0x00FF00,[11]=0xFFFF00,
  80.     [12]=0x0000FF,[13]=0xFF00FF,[14]=0x00FFFF,[15]=0xFFFFFF
  81. }
  82.  
  83. for i = 0, 215 do
  84.     local n = i + 16
  85.     local r = math.floor(i / 36)
  86.     local g = math.floor((i % 36) / 6)
  87.     local b = i % 6
  88.     local levels = {0, 95, 135, 175, 215, 255}
  89.     ansi256[n] =
  90.         levels[r + 1] * 0x10000 +
  91.         levels[g + 1] * 0x100 +
  92.         levels[b + 1]
  93. end
  94.  
  95. for i = 0, 23 do
  96.     local v = 8 + i * 10
  97.     ansi256[232 + i] = v * 0x10000 + v * 0x100 + v
  98. end
  99.  
  100. local function clamp(value, low, high)
  101.     return math.max(low, math.min(high, value))
  102. end
  103.  
  104. local function send(connection, data)
  105.     if not connection or not data or #data == 0 then
  106.         return true
  107.     end
  108.  
  109.     local ok, result = pcall(function()
  110.         return connection.write(data)
  111.     end)
  112.  
  113.     return ok and result ~= false
  114. end
  115.  
  116. local function getResolution()
  117.     local w, h = gpu.getResolution()
  118.     return w or 80, h or 25
  119. end
  120.  
  121. local Terminal = {}
  122. Terminal.__index = Terminal
  123.  
  124. function Terminal.new(respond)
  125.     local self = setmetatable({}, Terminal)
  126.     self.respond = respond
  127.     self.bold = false
  128.     self.reverse = false
  129.     self.fg = 7
  130.     self.bg = 0
  131.     self.savedX = 1
  132.     self.savedY = 1
  133.     self.cursorVisible = true
  134.     self.utf8Buffer = ""
  135.     self.ansiBuffer = ""
  136.     self.lastWasCR = false
  137.  
  138.     local width, height = getResolution()
  139.     self.scrollTop = 1
  140.     self.scrollBottom = height
  141.  
  142.     self.bufferId = nil
  143.     if gpu.allocateBuffer then
  144.         local ok, id = pcall(gpu.allocateBuffer, width, height)
  145.         if ok and id then
  146.             self.bufferId = id
  147.             gpu.setActiveBuffer(id)
  148.         end
  149.     end
  150.  
  151.     self:reset()
  152.  
  153.     if self.bufferId then
  154.         gpu.fill(1, 1, width, height, " ")
  155.         self:flush()
  156.     end
  157.  
  158.     return self
  159. end
  160.  
  161. function Terminal:flush()
  162.     if not self.bufferId then
  163.         return
  164.     end
  165.     local width, height = getResolution()
  166.     pcall(gpu.bitblt, 0, 1, 1, width, height, self.bufferId, 1, 1)
  167. end
  168.  
  169. function Terminal:destroy()
  170.     if self.bufferId then
  171.         pcall(gpu.setActiveBuffer, 0)
  172.         pcall(gpu.freeBuffer, self.bufferId)
  173.         self.bufferId = nil
  174.     end
  175. end
  176.  
  177. function Terminal:reset()
  178.     self.bold = false
  179.     self.reverse = false
  180.     self.fg = 7
  181.     self.bg = 0
  182.     self.savedX = 1
  183.     self.savedY = 1
  184.     self.cursorVisible = true
  185.     self.utf8Buffer = ""
  186.     self.ansiBuffer = ""
  187.  
  188.     local _, height = getResolution()
  189.     self.scrollTop = 1
  190.     self.scrollBottom = height
  191.  
  192.     self.fgColor = ansi_palette[7]
  193.     self.bgColor = ansi_palette[0]
  194.     self:applyColors()
  195.     term.setCursor(1, 1)
  196.     term.setCursorBlink(true)
  197. end
  198.  
  199. function Terminal:scrollRegion(n)
  200.     if n == 0 then
  201.         return
  202.     end
  203.  
  204.     local width = getResolution()
  205.     local top, bottom = self.scrollTop, self.scrollBottom
  206.     local regionHeight = bottom - top + 1
  207.     if regionHeight <= 0 then
  208.         return
  209.     end
  210.  
  211.     if n > 0 then
  212.         n = math.min(n, regionHeight)
  213.         gpu.copy(1, top + n, width, regionHeight - n, 0, -n)
  214.         gpu.fill(1, bottom - n + 1, width, n, " ")
  215.     else
  216.         n = math.min(-n, regionHeight)
  217.         gpu.copy(1, top, width, regionHeight - n, 0, n)
  218.         gpu.fill(1, top, width, n, " ")
  219.     end
  220. end
  221.  
  222. function Terminal:applyColors()
  223.     local fg, bg = self.fgColor, self.bgColor
  224.     if self.reverse then
  225.         fg, bg = bg, fg
  226.     end
  227.     gpu.setForeground(fg)
  228.     gpu.setBackground(bg)
  229. end
  230.  
  231. function Terminal:setColor(index, background)
  232.     index = clamp(index, 0, 15)
  233.     if background then
  234.         self.bg = index
  235.         self.bgColor = ansi_palette[index]
  236.     else
  237.         self.fg = index
  238.         self.fgColor = ansi_palette[index]
  239.     end
  240.     self:applyColors()
  241. end
  242.  
  243. function Terminal:set256Color(index, background)
  244.     index = clamp(index, 0, 255)
  245.     local color = ansi256[index]
  246.     if background then
  247.         self.bgColor = color
  248.     else
  249.         self.fgColor = color
  250.     end
  251.     self:applyColors()
  252. end
  253.  
  254. function Terminal:applySGR(params)
  255.     if #params == 0 then
  256.         params = {0}
  257.     end
  258.  
  259.     local i = 1
  260.     while i <= #params do
  261.         local p = params[i]
  262.  
  263.         if p == 0 then
  264.             self.bold = false
  265.             self.reverse = false
  266.             self:setColor(7, false)
  267.             self:setColor(0, true)
  268.  
  269.         elseif p == 1 then
  270.             self.bold = true
  271.  
  272.         elseif p == 22 then
  273.             self.bold = false
  274.  
  275.         elseif p == 7 then
  276.             self.reverse = true
  277.             self:applyColors()
  278.  
  279.         elseif p == 27 then
  280.             self.reverse = false
  281.             self:applyColors()
  282.  
  283.         elseif p == 39 then
  284.             self:setColor(7, false)
  285.  
  286.         elseif p == 49 then
  287.             self:setColor(0, true)
  288.  
  289.         elseif p >= 30 and p <= 37 then
  290.             local index = p - 30
  291.             if self.bold then index = index + 8 end
  292.             self:setColor(index, false)
  293.  
  294.         elseif p >= 40 and p <= 47 then
  295.             self:setColor(p - 40, true)
  296.  
  297.         elseif p >= 90 and p <= 97 then
  298.             self:setColor(p - 90 + 8, false)
  299.  
  300.         elseif p >= 100 and p <= 107 then
  301.             self:setColor(p - 100 + 8, true)
  302.  
  303.         elseif p == 38 or p == 48 then
  304.             local background = (p == 48)
  305.             local mode = params[i + 1]
  306.  
  307.             if mode == 5 and params[i + 2] then
  308.                 self:set256Color(params[i + 2], background)
  309.                 i = i + 2
  310.             elseif mode == 2 and params[i + 4] then
  311.                 local r = clamp(params[i + 2], 0, 255)
  312.                 local g = clamp(params[i + 3], 0, 255)
  313.                 local b = clamp(params[i + 4], 0, 255)
  314.                 local color = r * 0x10000 + g * 0x100 + b
  315.                 if background then
  316.                     self.bgColor = color
  317.                 else
  318.                     self.fgColor = color
  319.                 end
  320.                 self:applyColors()
  321.                 i = i + 4
  322.             end
  323.         end
  324.  
  325.         i = i + 1
  326.     end
  327. end
  328.  
  329. local function parseParams(raw)
  330.     local params = {}
  331.  
  332.     raw = raw:gsub("^[?>=]", "")
  333.  
  334.     if raw == "" then
  335.         return params
  336.     end
  337.  
  338.     for value in raw:gmatch("[0-9]+") do
  339.         params[#params + 1] = tonumber(value)
  340.     end
  341.  
  342.     return params
  343. end
  344.  
  345. function Terminal:executeCSI(raw, final)
  346.     local params = parseParams(raw)
  347.     local p1 = params[1] or 1
  348.  
  349.     local x, y = term.getCursor()
  350.     local width, height = getResolution()
  351.  
  352.     if final == "H" or final == "f" then
  353.         local row = params[1] or 1
  354.         local col = params[2] or 1
  355.         term.setCursor(clamp(col, 1, width), clamp(row, 1, height))
  356.  
  357.     elseif final == "A" then
  358.         term.setCursor(x, clamp(y - p1, 1, height))
  359.  
  360.     elseif final == "B" then
  361.         term.setCursor(x, clamp(y + p1, 1, height))
  362.  
  363.     elseif final == "C" or final == "a" then
  364.         term.setCursor(clamp(x + p1, 1, width), y)
  365.  
  366.     elseif final == "D" then
  367.         term.setCursor(clamp(x - p1, 1, width), y)
  368.  
  369.     elseif final == "E" then
  370.         term.setCursor(1, clamp(y + p1, 1, height))
  371.  
  372.     elseif final == "F" then
  373.         term.setCursor(1, clamp(y - p1, 1, height))
  374.  
  375.     elseif final == "G" or final == "`" then
  376.         term.setCursor(clamp(p1, 1, width), y)
  377.  
  378.     elseif final == "d" then
  379.         term.setCursor(x, clamp(p1, 1, height))
  380.  
  381.     elseif final == "J" then
  382.         local mode = params[1] or 0
  383.         local cx, cy = clamp(x, 1, width), clamp(y, 1, height)
  384.  
  385.         if mode == 2 or mode == 3 then
  386.             term.clear()
  387.             term.setCursor(1, 1)
  388.         elseif mode == 0 then
  389.             gpu.fill(cx, cy, width - cx + 1, 1, " ")
  390.             if cy < height then
  391.                 gpu.fill(1, cy + 1, width, height - cy, " ")
  392.             end
  393.         elseif mode == 1 then
  394.             if cy > 1 then
  395.                 gpu.fill(1, 1, width, cy - 1, " ")
  396.             end
  397.             gpu.fill(1, cy, cx, 1, " ")
  398.         end
  399.  
  400.     elseif final == "K" then
  401.         local mode = params[1] or 0
  402.         local cx, cy = clamp(x, 1, width), clamp(y, 1, height)
  403.  
  404.         if mode == 0 then
  405.             gpu.fill(cx, cy, width - cx + 1, 1, " ")
  406.         elseif mode == 1 then
  407.             gpu.fill(1, cy, cx, 1, " ")
  408.         else
  409.             gpu.fill(1, cy, width, 1, " ")
  410.         end
  411.  
  412.     elseif final == "m" then
  413.         self:applySGR(params)
  414.  
  415.     elseif final == "s" then
  416.         self.savedX, self.savedY = x, y
  417.  
  418.     elseif final == "u" then
  419.         term.setCursor(
  420.             clamp(self.savedX, 1, width),
  421.             clamp(self.savedY, 1, height)
  422.         )
  423.  
  424.     elseif final == "r" then
  425.         local top = clamp(params[1] or 1, 1, height)
  426.         local bottom = clamp(params[2] or height, 1, height)
  427.         if top < bottom then
  428.             self.scrollTop, self.scrollBottom = top, bottom
  429.         else
  430.             self.scrollTop, self.scrollBottom = 1, height
  431.         end
  432.         term.setCursor(1, 1)
  433.  
  434.     elseif final == "S" then
  435.         self:scrollRegion(p1)
  436.  
  437.     elseif final == "T" then
  438.         self:scrollRegion(-p1)
  439.  
  440.     elseif final == "n" then
  441.         if self.respond then
  442.             if params[1] == 6 then
  443.                 self.respond("\27[" .. y .. ";" .. x .. "R")
  444.             elseif params[1] == 5 then
  445.                 self.respond("\27[0n")
  446.             end
  447.         end
  448.  
  449.     elseif final == "c" then
  450.         if self.respond and raw:sub(1, 1) ~= ">" then
  451.             self.respond("\27[?1;2c")
  452.         end
  453.  
  454.     elseif final == "h" or final == "l" then
  455.         if raw:sub(1, 1) == "?" and params[1] == 25 then
  456.             self.cursorVisible = (final == "h")
  457.             term.setCursorBlink(self.cursorVisible)
  458.         end
  459.     end
  460. end
  461.  
  462. function Terminal:renderText(data, mayBuffer)
  463.     if mayBuffer == nil then
  464.         mayBuffer = true
  465.     end
  466.  
  467.     local output = {}
  468.     local i = 1
  469.     local len = #data
  470.  
  471.     while i <= len do
  472.         local byte = data:byte(i)
  473.  
  474.         if byte == 0x00 then
  475.             i = i + 1
  476.  
  477.         elseif byte == 0x07 then
  478.             if #output > 0 then
  479.                 io.write(table.concat(output))
  480.                 output = {}
  481.             end
  482.             pcall(computer.beep)
  483.             i = i + 1
  484.  
  485.         elseif byte < 0x80 then
  486.             output[#output + 1] = string.char(byte)
  487.             i = i + 1
  488.  
  489.         elseif byte >= 0xC2 and byte <= 0xDF then
  490.             if i + 1 > len then
  491.                 if mayBuffer then
  492.                     self.utf8Buffer = data:sub(i)
  493.                     break
  494.                 end
  495.                 output[#output + 1] = cp437_to_utf8[byte] or "?"
  496.                 i = i + 1
  497.             else
  498.                 local b2 = data:byte(i + 1)
  499.                 if b2 >= 0x80 and b2 <= 0xBF then
  500.                     output[#output + 1] = data:sub(i, i + 1)
  501.                     i = i + 2
  502.                 else
  503.                     output[#output + 1] = cp437_to_utf8[byte] or "?"
  504.                     i = i + 1
  505.                 end
  506.             end
  507.  
  508.         elseif byte >= 0xE0 and byte <= 0xEF then
  509.             if i + 2 > len then
  510.                 if mayBuffer then
  511.                     self.utf8Buffer = data:sub(i)
  512.                     break
  513.                 end
  514.                 output[#output + 1] = cp437_to_utf8[byte] or "?"
  515.                 i = i + 1
  516.             else
  517.                 local b2, b3 = data:byte(i + 1), data:byte(i + 2)
  518.                 if b2 >= 0x80 and b2 <= 0xBF and b3 >= 0x80 and b3 <= 0xBF then
  519.                     output[#output + 1] = data:sub(i, i + 2)
  520.                     i = i + 3
  521.                 else
  522.                     output[#output + 1] = cp437_to_utf8[byte] or "?"
  523.                     i = i + 1
  524.                 end
  525.             end
  526.  
  527.         elseif byte >= 0xF0 and byte <= 0xF4 then
  528.             if i + 3 > len then
  529.                 if mayBuffer then
  530.                     self.utf8Buffer = data:sub(i)
  531.                     break
  532.                 end
  533.                 output[#output + 1] = cp437_to_utf8[byte] or "?"
  534.                 i = i + 1
  535.             else
  536.                 local b2, b3, b4 = data:byte(i + 1), data:byte(i + 2), data:byte(i + 3)
  537.                 if b2 >= 0x80 and b2 <= 0xBF and
  538.                    b3 >= 0x80 and b3 <= 0xBF and
  539.                    b4 >= 0x80 and b4 <= 0xBF then
  540.                     output[#output + 1] = data:sub(i, i + 4)
  541.                     i = i + 4
  542.                 else
  543.                     output[#output + 1] = cp437_to_utf8[byte] or "?"
  544.                     i = i + 1
  545.                 end
  546.             end
  547.  
  548.         else
  549.             output[#output + 1] = cp437_to_utf8[byte] or "?"
  550.             i = i + 1
  551.         end
  552.     end
  553.  
  554.     if #output > 0 then
  555.         io.write(table.concat(output))
  556.     end
  557. end
  558.  
  559. function Terminal:write(data)
  560.     if not data or #data == 0 then
  561.         return
  562.     end
  563.  
  564.     data = self.ansiBuffer .. self.utf8Buffer .. data
  565.     self.ansiBuffer = ""
  566.     self.utf8Buffer = ""
  567.  
  568.     local i = 1
  569.     local len = #data
  570.     local textStart = 1
  571.  
  572.     while i <= len do
  573.         local esc = data:find("\27", i, true)
  574.  
  575.         if not esc then
  576.             self:renderText(data:sub(textStart), true)
  577.             break
  578.         end
  579.  
  580.         if esc > textStart then
  581.             self:renderText(data:sub(textStart, esc - 1), false)
  582.         end
  583.  
  584.         if esc + 1 <= len and data:sub(esc + 1, esc + 1) == "[" then
  585.             local finalPos = data:find("[@-~]", esc + 2)
  586.  
  587.             if not finalPos then
  588.                 self.ansiBuffer = data:sub(esc)
  589.                 return
  590.             end
  591.  
  592.             local raw = data:sub(esc + 2, finalPos - 1)
  593.             local final = data:sub(finalPos, finalPos)
  594.  
  595.             self:executeCSI(raw, final)
  596.             i = finalPos + 1
  597.             textStart = i
  598.  
  599.         elseif esc + 1 <= len then
  600.             local command = data:sub(esc + 1, esc + 1)
  601.  
  602.             if command == "]" or command == "P" or command == "X"
  603.                 or command == "^" or command == "_" then
  604.                 local bel = data:find("\7", esc + 2, true)
  605.                 local st = data:find("\27\\", esc + 2, true)
  606.                 local terminator
  607.  
  608.                 if bel and (not st or bel < st) then
  609.                     terminator = bel + 1
  610.                 elseif st then
  611.                     terminator = st + 2
  612.                 end
  613.  
  614.                 if not terminator then
  615.                     self.ansiBuffer = data:sub(esc)
  616.                     return
  617.                 end
  618.  
  619.                 i = terminator
  620.             else
  621.                 if command == "7" then
  622.                     self.savedX, self.savedY = term.getCursor()
  623.                 elseif command == "8" then
  624.                     local width, height = getResolution()
  625.                     term.setCursor(
  626.                         clamp(self.savedX, 1, width),
  627.                         clamp(self.savedY, 1, height)
  628.                     )
  629.                 elseif command == "c" then
  630.                     self:reset()
  631.                 elseif command == "D" then
  632.                     local x, y = term.getCursor()
  633.                     if y >= self.scrollBottom then
  634.                         self:scrollRegion(1)
  635.                     else
  636.                         term.setCursor(x, y + 1)
  637.                     end
  638.                 elseif command == "M" then
  639.                     local x, y = term.getCursor()
  640.                     if y <= self.scrollTop then
  641.                         self:scrollRegion(-1)
  642.                     else
  643.                         term.setCursor(x, y - 1)
  644.                     end
  645.                 end
  646.  
  647.                 i = esc + 2
  648.             end
  649.  
  650.             textStart = i
  651.  
  652.         else
  653.             self.ansiBuffer = data:sub(esc)
  654.             return
  655.         end
  656.     end
  657. end
  658.  
  659. local Telnet = {}
  660. Telnet.__index = Telnet
  661.  
  662. function Telnet.new(connection)
  663.     local self = setmetatable({}, Telnet)
  664.     self.connection = connection
  665.     self.buffer = ""
  666.     self.state = "DATA"
  667.     self.command = nil
  668.     self.option = nil
  669.     self.subnegotiation = ""
  670.     self.localOptions = {}
  671.     self.remoteOptions = {}
  672.     self.echo = false
  673.     self.ttypeIndex = 0
  674.     return self
  675. end
  676.  
  677. function Telnet:write(data)
  678.     return send(self.connection, data)
  679. end
  680.  
  681. function Telnet:sendCommand(command, option)
  682.     return self:write(TELNET.IAC .. command .. string.char(option))
  683. end
  684.  
  685. function Telnet:sendSubnegotiation(option, data)
  686.     data = data:gsub(TELNET.IAC, TELNET.IAC .. TELNET.IAC)
  687.     return self:write(TELNET.IAC .. TELNET.SB .. string.char(option) ..
  688.         data .. TELNET.IAC .. TELNET.SE)
  689. end
  690.  
  691. function Telnet:sendNAWS()
  692.     local w, h = getResolution()
  693.     w = clamp(w, 1, 65535)
  694.     h = clamp(h, 1, 65535)
  695.  
  696.     local data = string.char(
  697.         math.floor(w / 256), w % 256,
  698.         math.floor(h / 256), h % 256
  699.     )
  700.  
  701.     self:sendSubnegotiation(TELNET.NAWS, data)
  702. end
  703.  
  704. function Telnet:handleDO(option)
  705.     if option == TELNET.TERMINAL_TYPE or option == TELNET.NAWS then
  706.         if not self.localOptions[option] then
  707.             self.localOptions[option] = true
  708.             self:sendCommand(TELNET.WILL, option)
  709.  
  710.             if option == TELNET.NAWS then
  711.                 self:sendNAWS()
  712.             end
  713.         else
  714.             self:sendCommand(TELNET.WILL, option)
  715.         end
  716.     else
  717.         self:sendCommand(TELNET.WONT, option)
  718.     end
  719. end
  720.  
  721. function Telnet:handleDONT(option)
  722.     if self.localOptions[option] then
  723.         self.localOptions[option] = false
  724.         self:sendCommand(TELNET.WONT, option)
  725.     end
  726. end
  727.  
  728. function Telnet:handleWILL(option)
  729.     if option == TELNET.ECHO then
  730.         self.remoteOptions[option] = true
  731.         self.echo = false
  732.         self:sendCommand(TELNET.DO, option)
  733.  
  734.     elseif option == TELNET.SUPPRESS_GO_AHEAD then
  735.         self.remoteOptions[option] = true
  736.         self:sendCommand(TELNET.DO, option)
  737.  
  738.     else
  739.         self:sendCommand(TELNET.DONT, option)
  740.     end
  741. end
  742.  
  743. function Telnet:handleWONT(option)
  744.     self.remoteOptions[option] = false
  745.  
  746.     if option == TELNET.ECHO then
  747.         self.echo = true
  748.     end
  749. end
  750.  
  751. function Telnet:handleSubnegotiation(option, data)
  752.     if option == TELNET.TERMINAL_TYPE and data:byte(1) == TERMINAL_TYPE_SEND then
  753.         self.ttypeIndex = (self.ttypeIndex % #TERMINAL_TYPES) + 1
  754.         local name = TERMINAL_TYPES[self.ttypeIndex]
  755.  
  756.         self:sendSubnegotiation(
  757.             TELNET.TERMINAL_TYPE,
  758.             string.char(TERMINAL_TYPE_IS) .. name
  759.         )
  760.     end
  761. end
  762.  
  763. function Telnet:process(data)
  764.     local output = {}
  765.     local i = 1
  766.     local len = #data
  767.  
  768.     while i <= len do
  769.         local byte = data:byte(i)
  770.  
  771.         if self.state == "DATA" then
  772.             if byte == 255 then
  773.                 self.state = "IAC"
  774.             else
  775.                 output[#output + 1] = string.char(byte)
  776.             end
  777.             i = i + 1
  778.  
  779.         elseif self.state == "IAC" then
  780.             if byte == 255 then
  781.                 output[#output + 1] = TELNET.IAC
  782.                 self.state = "DATA"
  783.  
  784.             elseif byte == 253 then
  785.                 self.command = TELNET.DO
  786.                 self.state = "OPTION"
  787.  
  788.             elseif byte == 254 then
  789.                 self.command = TELNET.DONT
  790.                 self.state = "OPTION"
  791.  
  792.             elseif byte == 251 then
  793.                 self.command = TELNET.WILL
  794.                 self.state = "OPTION"
  795.  
  796.             elseif byte == 252 then
  797.                 self.command = TELNET.WONT
  798.                 self.state = "OPTION"
  799.  
  800.             elseif byte == 250 then
  801.                 self.state = "SB_OPTION"
  802.  
  803.             elseif byte == 240 then
  804.                 self.state = "DATA"
  805.  
  806.             else
  807.                 self.state = "DATA"
  808.             end
  809.  
  810.             i = i + 1
  811.  
  812.         elseif self.state == "OPTION" then
  813.             if self.command == TELNET.DO then
  814.                 self:handleDO(byte)
  815.             elseif self.command == TELNET.DONT then
  816.                 self:handleDONT(byte)
  817.             elseif self.command == TELNET.WILL then
  818.                 self:handleWILL(byte)
  819.             elseif self.command == TELNET.WONT then
  820.                 self:handleWONT(byte)
  821.             end
  822.  
  823.             self.command = nil
  824.             self.state = "DATA"
  825.             i = i + 1
  826.  
  827.         elseif self.state == "SB_OPTION" then
  828.             self.option = byte
  829.             self.subnegotiation = ""
  830.             self.state = "SB_DATA"
  831.             i = i + 1
  832.  
  833.         elseif self.state == "SB_DATA" then
  834.             if byte == 255 then
  835.                 self.state = "SB_IAC"
  836.             else
  837.                 self.subnegotiation = self.subnegotiation .. string.char(byte)
  838.             end
  839.             i = i + 1
  840.  
  841.         elseif self.state == "SB_IAC" then
  842.             if byte == 240 then
  843.                 self:handleSubnegotiation(self.option, self.subnegotiation)
  844.                 self.option = nil
  845.                 self.subnegotiation = ""
  846.                 self.state = "DATA"
  847.  
  848.             elseif byte == 255 then
  849.                 self.subnegotiation = self.subnegotiation .. TELNET.IAC
  850.                 self.state = "SB_DATA"
  851.  
  852.             else
  853.                 self.state = "SB_DATA"
  854.                 self.subnegotiation = self.subnegotiation .. TELNET.IAC .. string.char(byte)
  855.             end
  856.  
  857.             i = i + 1
  858.         end
  859.     end
  860.  
  861.     return table.concat(output)
  862. end
  863.  
  864. function Telnet:sendKey(data)
  865.     if not data or #data == 0 then
  866.         return true
  867.     end
  868.  
  869.     data = data:gsub(TELNET.IAC, TELNET.IAC .. TELNET.IAC)
  870.     return self:write(data)
  871. end
  872.  
  873. local SPECIAL_KEYS = {
  874.     [200] = "\27[A",
  875.     [208] = "\27[B",
  876.     [205] = "\27[C",
  877.     [203] = "\27[D",
  878.     [199] = "\27[H",
  879.     [207] = "\27[F",
  880.     [201] = "\27[5~",
  881.     [209] = "\27[6~",
  882.     [210] = "\27[2~",
  883.     [211] = "\27[3~",
  884.     [15]  = "\t",    
  885.     [1]   = "\27",    
  886.     [59]  = "\27OP",  
  887.     [60]  = "\27OQ",  
  888.     [61]  = "\27OR",  
  889.     [62]  = "\27OS",  
  890.     [63]  = "\27[15~",
  891.     [64]  = "\27[17~",
  892.     [65]  = "\27[18~",
  893.     [66]  = "\27[19~",
  894.     [67]  = "\27[20~",
  895.     [68]  = "\27[21~",
  896.     [87]  = "\27[23~",
  897.     [88]  = "\27[24~",
  898. }
  899.  
  900. local LETTER_SCANCODES = {
  901.     [30]="a",[48]="b",[46]="c",[32]="d",[18]="e",[33]="f",[34]="g",[35]="h",
  902.     [23]="i",[36]="j",[37]="k",[38]="l",[50]="m",[49]="n",[24]="o",[25]="p",
  903.     [16]="q",[19]="r",[31]="s",[20]="t",[22]="u",[47]="v",[17]="w",[45]="x",
  904.     [21]="y",[44]="z",
  905. }
  906.  
  907. local function keyToString(char, code)
  908.     if code == 28 then
  909.         return "\r"
  910.     elseif code == 14 then
  911.         return string.char(8)
  912.     elseif SPECIAL_KEYS[code] then
  913.         return SPECIAL_KEYS[code]
  914.     elseif char and char > 0 then
  915.         return string.char(char)
  916.     elseif keyboard.isControlDown() and LETTER_SCANCODES[code] then
  917.         local letter = LETTER_SCANCODES[code]
  918.         return string.char(letter:byte() - 96)
  919.     end
  920.  
  921.     return nil
  922. end
  923.  
  924. local function isCtrlAltDown()
  925.     return keyboard.isControlDown()
  926.        and keyboard.isAltDown()
  927. end
  928.  
  929. local function connectTelnet(host, port)
  930.     local connection
  931.     local ok, err = pcall(function()
  932.         connection = internet.connect(host, port)
  933.     end)
  934.  
  935.     if not ok or not connection then
  936.         print("Failed to connect to " .. host .. ":" .. port)
  937.         if err then
  938.             print(tostring(err))
  939.         end
  940.         return
  941.     end
  942.  
  943.     print("Connecting to " .. host .. ":" .. port .. "...")
  944.  
  945.     local start = computer.uptime()
  946.  
  947.     while true do
  948.         local connected
  949.         local finishOK, finishResult = pcall(function()
  950.             return connection.finishConnect()
  951.         end)
  952.  
  953.         if not finishOK then
  954.             print("Connection error: " .. tostring(finishResult))
  955.             pcall(function() connection.close() end)
  956.             return
  957.         end
  958.  
  959.         connected = finishResult
  960.  
  961.         if connected then
  962.             break
  963.         end
  964.  
  965.         if computer.uptime() - start >= CONNECT_TIMEOUT then
  966.             print("Connection timed out")
  967.             pcall(function() connection.close() end)
  968.             return
  969.         end
  970.  
  971.         os.sleep(0.05)
  972.     end
  973.  
  974.     local width, height = getResolution()
  975.     gpu.setResolution(width, height)
  976.  
  977.     term.clear()
  978.     term.setCursor(1, 1)
  979.  
  980.     local telnet
  981.     local terminal = Terminal.new(function(data)
  982.         telnet:sendKey(data)
  983.     end)
  984.  
  985.     local sessionOK, sessionErr = pcall(function()
  986.         telnet = Telnet.new(connection)
  987.         local running = true
  988.  
  989.         telnet:sendCommand(TELNET.DO, TELNET.ECHO)
  990.         telnet:sendCommand(TELNET.DO, TELNET.SUPPRESS_GO_AHEAD)
  991.  
  992.         telnet.localOptions[TELNET.TERMINAL_TYPE] = true
  993.         telnet.localOptions[TELNET.NAWS] = true
  994.         telnet:sendCommand(TELNET.WILL, TELNET.TERMINAL_TYPE)
  995.         telnet:sendCommand(TELNET.WILL, TELNET.NAWS)
  996.         telnet:sendNAWS()
  997.  
  998.         while running do
  999.             local data
  1000.             local readOK, readResult = pcall(function()
  1001.                 return connection.read(READ_SIZE)
  1002.             end)
  1003.  
  1004.             if not readOK then
  1005.                 print("\nNetwork read error: " .. tostring(readResult))
  1006.                 terminal:flush()
  1007.                 break
  1008.             end
  1009.  
  1010.             data = readResult
  1011.  
  1012.             if data and #data > 0 then
  1013.                 local applicationData = telnet:process(data)
  1014.                 if #applicationData > 0 then
  1015.                     terminal:write(applicationData)
  1016.                     io.flush()
  1017.                 end
  1018.             end
  1019.  
  1020.             local events = {event.pull(0.02)}
  1021.  
  1022.             if events[1] == "key_down" then
  1023.                 local char, code = events[3], events[4]
  1024.  
  1025.                 if isCtrlAltDown() and char == 46 then
  1026.                     print("\nDisconnecting...")
  1027.                     running = false
  1028.                 else
  1029.                     local key = keyToString(char, code)
  1030.  
  1031.                     if key then
  1032.                         if key == "\r" then
  1033.                             telnet:sendKey("\r")
  1034.                             if telnet.echo then
  1035.                                 io.write("\r\n")
  1036.                                 io.flush()
  1037.                             end
  1038.                         elseif key == string.char(8) then
  1039.                             telnet:sendKey(key)
  1040.                             if telnet.echo then
  1041.                                 io.write("\b \b")
  1042.                                 io.flush()
  1043.                             end
  1044.                         else
  1045.                             telnet:sendKey(key)
  1046.                             if telnet.echo and #key == 1 and key:byte() >= 0x20 then
  1047.                                 io.write(key)
  1048.                                 io.flush()
  1049.                             end
  1050.                         end
  1051.                     end
  1052.                 end
  1053.             elseif events[1] == "component_added" or events[1] == "component_removed" then
  1054.             end
  1055.  
  1056.             terminal:flush()
  1057.         end
  1058.     end)
  1059.  
  1060.     terminal:destroy()
  1061.     pcall(function() connection.close() end)
  1062.  
  1063.     term.setCursorBlink(true)
  1064.     gpu.setForeground(ansi_palette[7])
  1065.     gpu.setBackground(ansi_palette[0])
  1066.  
  1067.     if not sessionOK then
  1068.         error(sessionErr, 0)
  1069.     end
  1070.  
  1071.     print("\nDisconnected. Press any key to return.")
  1072.     event.pull("key_down")
  1073. end
  1074.  
  1075. local function displayLogo()
  1076.     term.clear()
  1077.     print([[
  1078.  ________________  ___________________________   __________________
  1079.  __  __ \_  ____/   ___  __/__  ____/__  /___  | / /__  ____/__  __/
  1080.  _  / / /  / _________  /  __  __/  __  / __   |/ /__  __/  __  /
  1081.  / /_/ // /___/_____/  /   _  /___  _  /___  /|  / _  /___  _  /
  1082.  \____/ \____/      /_/    /_____/  /_____/_/ |_/  /_____/  /_/
  1083.     ]])
  1084.     print("OC-TELNET Client made by nonogamer9\n")
  1085. end
  1086.  
  1087. local function parseAddress(input)
  1088.     input = input:match("^%s*(.-)%s*$")
  1089.  
  1090.     if input == "" then
  1091.         return nil
  1092.     end
  1093.  
  1094.     local host, port = input:match("^([^:%s]+):(%d+)$")
  1095.     if host and port then
  1096.         return host, tonumber(port)
  1097.     end
  1098.  
  1099.     host, port = input:match("^(%S+)%s+(%d+)$")
  1100.     if host and port then
  1101.         return host, tonumber(port)
  1102.     end
  1103.  
  1104.     if input:match("^%S+$") then
  1105.         return input, DEFAULT_PORT
  1106.     end
  1107.  
  1108.     return nil
  1109. end
  1110.  
  1111. while true do
  1112.     displayLogo()
  1113.  
  1114.     print("Enter host and optional port:")
  1115.     print("Examples: bbs.example.com 23")
  1116.     print("          bbs.example.com:23")
  1117.     print("          bbs.example.com")
  1118.     print("Type 'exit' to quit.")
  1119.     io.write("> ")
  1120.  
  1121.     local input = io.read()
  1122.  
  1123.     if not input or input:lower() == "exit" then
  1124.         break
  1125.     end
  1126.  
  1127.     local host, port = parseAddress(input)
  1128.  
  1129.     if host and port and port >= 1 and port <= 65535 then
  1130.         connectTelnet(host, port)
  1131.     else
  1132.         print("Invalid address. Use: host, host port, or host:port")
  1133.         os.sleep(1.5)
  1134.     end
  1135. end
  1136.  
  1137. term.clear()
  1138. term.setCursor(1, 1)
Advertisement
Add Comment
Please, Sign In to add comment