Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local component = require("component")
- local event = require("event")
- local term = require("term")
- local computer = require("computer")
- local keyboard = require("keyboard")
- local internet = assert(component.internet, "Internet card is required")
- local gpu = assert(component.gpu, "GPU is required")
- local TELNET = {
- IAC = string.char(255),
- DONT = string.char(254),
- DO = string.char(253),
- WONT = string.char(252),
- WILL = string.char(251),
- SB = string.char(250),
- SE = string.char(240),
- ECHO = 1,
- SUPPRESS_GO_AHEAD = 3,
- TERMINAL_TYPE = 24,
- NAWS = 31,
- }
- local TERMINAL_TYPE_IS = 0
- local TERMINAL_TYPE_SEND = 1
- local TERMINAL_TYPES = {"ANSI", "ANSI-BBS", "VT100"}
- local DEFAULT_PORT = 23
- local CONNECT_TIMEOUT = 10
- local READ_SIZE = 4096
- local cp437_to_utf8 = {
- [0x80]="\u{00C7}",[0x81]="\u{00FC}",[0x82]="\u{00E9}",[0x83]="\u{00E2}",
- [0x84]="\u{00E4}",[0x85]="\u{00E0}",[0x86]="\u{00E5}",[0x87]="\u{00E7}",
- [0x88]="\u{00EA}",[0x89]="\u{00EB}",[0x8A]="\u{00E8}",[0x8B]="\u{00EF}",
- [0x8C]="\u{00EE}",[0x8D]="\u{00EC}",[0x8E]="\u{00C4}",[0x8F]="\u{00C5}",
- [0x90]="\u{00C9}",[0x91]="\u{00E6}",[0x92]="\u{00C6}",[0x93]="\u{00F4}",
- [0x94]="\u{00F6}",[0x95]="\u{00F2}",[0x96]="\u{00FB}",[0x97]="\u{00F9}",
- [0x98]="\u{00FF}",[0x99]="\u{00D6}",[0x9A]="\u{00DC}",[0x9B]="\u{00A2}",
- [0x9C]="\u{00A3}",[0x9D]="\u{00A5}",[0x9E]="\u{20A7}",[0x9F]="\u{0192}",
- [0xA0]="\u{00E1}",[0xA1]="\u{00ED}",[0xA2]="\u{00F3}",[0xA3]="\u{00FA}",
- [0xA4]="\u{00F1}",[0xA5]="\u{00D1}",[0xA6]="\u{00AA}",[0xA7]="\u{00BA}",
- [0xA8]="\u{00BF}",[0xA9]="\u{2310}",[0xAA]="\u{00AC}",[0xAB]="\u{00BD}",
- [0xAC]="\u{00BC}",[0xAD]="\u{00A1}",[0xAE]="\u{00AB}",[0xAF]="\u{00BB}",
- [0xB0]="\u{2591}",[0xB1]="\u{2592}",[0xB2]="\u{2593}",[0xB3]="\u{2502}",
- [0xB4]="\u{2524}",[0xB5]="\u{2561}",[0xB6]="\u{2562}",[0xB7]="\u{2556}",
- [0xB8]="\u{2555}",[0xB9]="\u{2563}",[0xBA]="\u{2551}",[0xBB]="\u{2557}",
- [0xBC]="\u{255D}",[0xBD]="\u{255C}",[0xBE]="\u{255B}",[0xBF]="\u{2510}",
- [0xC0]="\u{2514}",[0xC1]="\u{2534}",[0xC2]="\u{252C}",[0xC3]="\u{251C}",
- [0xC4]="\u{2500}",[0xC5]="\u{253C}",[0xC6]="\u{255E}",[0xC7]="\u{255F}",
- [0xC8]="\u{255A}",[0xC9]="\u{2554}",[0xCA]="\u{2569}",[0xCB]="\u{2566}",
- [0xCC]="\u{2560}",[0xCD]="\u{2550}",[0xCE]="\u{256C}",[0xCF]="\u{2567}",
- [0xD0]="\u{2568}",[0xD1]="\u{2564}",[0xD2]="\u{2565}",[0xD3]="\u{2559}",
- [0xD4]="\u{2558}",[0xD5]="\u{2552}",[0xD6]="\u{2553}",[0xD7]="\u{256B}",
- [0xD8]="\u{256A}",[0xD9]="\u{2518}",[0xDA]="\u{250C}",[0xDB]="\u{2588}",
- [0xDC]="\u{2584}",[0xDD]="\u{258C}",[0xDE]="\u{2590}",[0xDF]="\u{2580}",
- [0xE0]="\u{03B1}",[0xE1]="\u{00DF}",[0xE2]="\u{0393}",[0xE3]="\u{03C0}",
- [0xE4]="\u{03A3}",[0xE5]="\u{03C3}",[0xE6]="\u{03BC}",[0xE7]="\u{03C4}",
- [0xE8]="\u{03A6}",[0xE9]="\u{0398}",[0xEA]="\u{03A9}",[0xEB]="\u{03B4}",
- [0xEC]="\u{221E}",[0xED]="\u{03C6}",[0xEE]="\u{03B5}",[0xEF]="\u{2229}",
- [0xF0]="\u{2261}",[0xF1]="\u{00B1}",[0xF2]="\u{2265}",[0xF3]="\u{2264}",
- [0xF4]="\u{2320}",[0xF5]="\u{2321}",[0xF6]="\u{00F7}",[0xF7]="\u{2248}",
- [0xF8]="\u{00B0}",[0xF9]="\u{2219}",[0xFA]="\u{00B7}",[0xFB]="\u{221A}",
- [0xFC]="\u{207F}",[0xFD]="\u{00B2}",[0xFE]="\u{25A0}",[0xFF]=" "
- }
- local ansi_palette = {
- [0]=0x000000,[1]=0xAA0000,[2]=0x00AA00,[3]=0xAA5500,
- [4]=0x0000AA,[5]=0xAA00AA,[6]=0x00AAAA,[7]=0xAAAAAA,
- [8]=0x555555,[9]=0xFF5555,[10]=0x55FF55,[11]=0xFFFF55,
- [12]=0x5555FF,[13]=0xFF55FF,[14]=0x55FFFF,[15]=0xFFFFFF
- }
- local ansi256 = {
- [0]=0x000000,[1]=0x800000,[2]=0x008000,[3]=0x808000,
- [4]=0x000080,[5]=0x800080,[6]=0x008080,[7]=0xC0C0C0,
- [8]=0x808080,[9]=0xFF0000,[10]=0x00FF00,[11]=0xFFFF00,
- [12]=0x0000FF,[13]=0xFF00FF,[14]=0x00FFFF,[15]=0xFFFFFF
- }
- for i = 0, 215 do
- local n = i + 16
- local r = math.floor(i / 36)
- local g = math.floor((i % 36) / 6)
- local b = i % 6
- local levels = {0, 95, 135, 175, 215, 255}
- ansi256[n] =
- levels[r + 1] * 0x10000 +
- levels[g + 1] * 0x100 +
- levels[b + 1]
- end
- for i = 0, 23 do
- local v = 8 + i * 10
- ansi256[232 + i] = v * 0x10000 + v * 0x100 + v
- end
- local function clamp(value, low, high)
- return math.max(low, math.min(high, value))
- end
- local function send(connection, data)
- if not connection or not data or #data == 0 then
- return true
- end
- local ok, result = pcall(function()
- return connection.write(data)
- end)
- return ok and result ~= false
- end
- local function getResolution()
- local w, h = gpu.getResolution()
- return w or 80, h or 25
- end
- local Terminal = {}
- Terminal.__index = Terminal
- function Terminal.new(respond)
- local self = setmetatable({}, Terminal)
- self.respond = respond
- self.bold = false
- self.reverse = false
- self.fg = 7
- self.bg = 0
- self.savedX = 1
- self.savedY = 1
- self.cursorVisible = true
- self.utf8Buffer = ""
- self.ansiBuffer = ""
- self.lastWasCR = false
- local width, height = getResolution()
- self.scrollTop = 1
- self.scrollBottom = height
- self.bufferId = nil
- if gpu.allocateBuffer then
- local ok, id = pcall(gpu.allocateBuffer, width, height)
- if ok and id then
- self.bufferId = id
- gpu.setActiveBuffer(id)
- end
- end
- self:reset()
- if self.bufferId then
- gpu.fill(1, 1, width, height, " ")
- self:flush()
- end
- return self
- end
- function Terminal:flush()
- if not self.bufferId then
- return
- end
- local width, height = getResolution()
- pcall(gpu.bitblt, 0, 1, 1, width, height, self.bufferId, 1, 1)
- end
- function Terminal:destroy()
- if self.bufferId then
- pcall(gpu.setActiveBuffer, 0)
- pcall(gpu.freeBuffer, self.bufferId)
- self.bufferId = nil
- end
- end
- function Terminal:reset()
- self.bold = false
- self.reverse = false
- self.fg = 7
- self.bg = 0
- self.savedX = 1
- self.savedY = 1
- self.cursorVisible = true
- self.utf8Buffer = ""
- self.ansiBuffer = ""
- local _, height = getResolution()
- self.scrollTop = 1
- self.scrollBottom = height
- self.fgColor = ansi_palette[7]
- self.bgColor = ansi_palette[0]
- self:applyColors()
- term.setCursor(1, 1)
- term.setCursorBlink(true)
- end
- function Terminal:scrollRegion(n)
- if n == 0 then
- return
- end
- local width = getResolution()
- local top, bottom = self.scrollTop, self.scrollBottom
- local regionHeight = bottom - top + 1
- if regionHeight <= 0 then
- return
- end
- if n > 0 then
- n = math.min(n, regionHeight)
- gpu.copy(1, top + n, width, regionHeight - n, 0, -n)
- gpu.fill(1, bottom - n + 1, width, n, " ")
- else
- n = math.min(-n, regionHeight)
- gpu.copy(1, top, width, regionHeight - n, 0, n)
- gpu.fill(1, top, width, n, " ")
- end
- end
- function Terminal:applyColors()
- local fg, bg = self.fgColor, self.bgColor
- if self.reverse then
- fg, bg = bg, fg
- end
- gpu.setForeground(fg)
- gpu.setBackground(bg)
- end
- function Terminal:setColor(index, background)
- index = clamp(index, 0, 15)
- if background then
- self.bg = index
- self.bgColor = ansi_palette[index]
- else
- self.fg = index
- self.fgColor = ansi_palette[index]
- end
- self:applyColors()
- end
- function Terminal:set256Color(index, background)
- index = clamp(index, 0, 255)
- local color = ansi256[index]
- if background then
- self.bgColor = color
- else
- self.fgColor = color
- end
- self:applyColors()
- end
- function Terminal:applySGR(params)
- if #params == 0 then
- params = {0}
- end
- local i = 1
- while i <= #params do
- local p = params[i]
- if p == 0 then
- self.bold = false
- self.reverse = false
- self:setColor(7, false)
- self:setColor(0, true)
- elseif p == 1 then
- self.bold = true
- elseif p == 22 then
- self.bold = false
- elseif p == 7 then
- self.reverse = true
- self:applyColors()
- elseif p == 27 then
- self.reverse = false
- self:applyColors()
- elseif p == 39 then
- self:setColor(7, false)
- elseif p == 49 then
- self:setColor(0, true)
- elseif p >= 30 and p <= 37 then
- local index = p - 30
- if self.bold then index = index + 8 end
- self:setColor(index, false)
- elseif p >= 40 and p <= 47 then
- self:setColor(p - 40, true)
- elseif p >= 90 and p <= 97 then
- self:setColor(p - 90 + 8, false)
- elseif p >= 100 and p <= 107 then
- self:setColor(p - 100 + 8, true)
- elseif p == 38 or p == 48 then
- local background = (p == 48)
- local mode = params[i + 1]
- if mode == 5 and params[i + 2] then
- self:set256Color(params[i + 2], background)
- i = i + 2
- elseif mode == 2 and params[i + 4] then
- local r = clamp(params[i + 2], 0, 255)
- local g = clamp(params[i + 3], 0, 255)
- local b = clamp(params[i + 4], 0, 255)
- local color = r * 0x10000 + g * 0x100 + b
- if background then
- self.bgColor = color
- else
- self.fgColor = color
- end
- self:applyColors()
- i = i + 4
- end
- end
- i = i + 1
- end
- end
- local function parseParams(raw)
- local params = {}
- raw = raw:gsub("^[?>=]", "")
- if raw == "" then
- return params
- end
- for value in raw:gmatch("[0-9]+") do
- params[#params + 1] = tonumber(value)
- end
- return params
- end
- function Terminal:executeCSI(raw, final)
- local params = parseParams(raw)
- local p1 = params[1] or 1
- local x, y = term.getCursor()
- local width, height = getResolution()
- if final == "H" or final == "f" then
- local row = params[1] or 1
- local col = params[2] or 1
- term.setCursor(clamp(col, 1, width), clamp(row, 1, height))
- elseif final == "A" then
- term.setCursor(x, clamp(y - p1, 1, height))
- elseif final == "B" then
- term.setCursor(x, clamp(y + p1, 1, height))
- elseif final == "C" or final == "a" then
- term.setCursor(clamp(x + p1, 1, width), y)
- elseif final == "D" then
- term.setCursor(clamp(x - p1, 1, width), y)
- elseif final == "E" then
- term.setCursor(1, clamp(y + p1, 1, height))
- elseif final == "F" then
- term.setCursor(1, clamp(y - p1, 1, height))
- elseif final == "G" or final == "`" then
- term.setCursor(clamp(p1, 1, width), y)
- elseif final == "d" then
- term.setCursor(x, clamp(p1, 1, height))
- elseif final == "J" then
- local mode = params[1] or 0
- local cx, cy = clamp(x, 1, width), clamp(y, 1, height)
- if mode == 2 or mode == 3 then
- term.clear()
- term.setCursor(1, 1)
- elseif mode == 0 then
- gpu.fill(cx, cy, width - cx + 1, 1, " ")
- if cy < height then
- gpu.fill(1, cy + 1, width, height - cy, " ")
- end
- elseif mode == 1 then
- if cy > 1 then
- gpu.fill(1, 1, width, cy - 1, " ")
- end
- gpu.fill(1, cy, cx, 1, " ")
- end
- elseif final == "K" then
- local mode = params[1] or 0
- local cx, cy = clamp(x, 1, width), clamp(y, 1, height)
- if mode == 0 then
- gpu.fill(cx, cy, width - cx + 1, 1, " ")
- elseif mode == 1 then
- gpu.fill(1, cy, cx, 1, " ")
- else
- gpu.fill(1, cy, width, 1, " ")
- end
- elseif final == "m" then
- self:applySGR(params)
- elseif final == "s" then
- self.savedX, self.savedY = x, y
- elseif final == "u" then
- term.setCursor(
- clamp(self.savedX, 1, width),
- clamp(self.savedY, 1, height)
- )
- elseif final == "r" then
- local top = clamp(params[1] or 1, 1, height)
- local bottom = clamp(params[2] or height, 1, height)
- if top < bottom then
- self.scrollTop, self.scrollBottom = top, bottom
- else
- self.scrollTop, self.scrollBottom = 1, height
- end
- term.setCursor(1, 1)
- elseif final == "S" then
- self:scrollRegion(p1)
- elseif final == "T" then
- self:scrollRegion(-p1)
- elseif final == "n" then
- if self.respond then
- if params[1] == 6 then
- self.respond("\27[" .. y .. ";" .. x .. "R")
- elseif params[1] == 5 then
- self.respond("\27[0n")
- end
- end
- elseif final == "c" then
- if self.respond and raw:sub(1, 1) ~= ">" then
- self.respond("\27[?1;2c")
- end
- elseif final == "h" or final == "l" then
- if raw:sub(1, 1) == "?" and params[1] == 25 then
- self.cursorVisible = (final == "h")
- term.setCursorBlink(self.cursorVisible)
- end
- end
- end
- function Terminal:renderText(data, mayBuffer)
- if mayBuffer == nil then
- mayBuffer = true
- end
- local output = {}
- local i = 1
- local len = #data
- while i <= len do
- local byte = data:byte(i)
- if byte == 0x00 then
- i = i + 1
- elseif byte == 0x07 then
- if #output > 0 then
- io.write(table.concat(output))
- output = {}
- end
- pcall(computer.beep)
- i = i + 1
- elseif byte < 0x80 then
- output[#output + 1] = string.char(byte)
- i = i + 1
- elseif byte >= 0xC2 and byte <= 0xDF then
- if i + 1 > len then
- if mayBuffer then
- self.utf8Buffer = data:sub(i)
- break
- end
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- else
- local b2 = data:byte(i + 1)
- if b2 >= 0x80 and b2 <= 0xBF then
- output[#output + 1] = data:sub(i, i + 1)
- i = i + 2
- else
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- end
- end
- elseif byte >= 0xE0 and byte <= 0xEF then
- if i + 2 > len then
- if mayBuffer then
- self.utf8Buffer = data:sub(i)
- break
- end
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- else
- local b2, b3 = data:byte(i + 1), data:byte(i + 2)
- if b2 >= 0x80 and b2 <= 0xBF and b3 >= 0x80 and b3 <= 0xBF then
- output[#output + 1] = data:sub(i, i + 2)
- i = i + 3
- else
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- end
- end
- elseif byte >= 0xF0 and byte <= 0xF4 then
- if i + 3 > len then
- if mayBuffer then
- self.utf8Buffer = data:sub(i)
- break
- end
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- else
- local b2, b3, b4 = data:byte(i + 1), data:byte(i + 2), data:byte(i + 3)
- if b2 >= 0x80 and b2 <= 0xBF and
- b3 >= 0x80 and b3 <= 0xBF and
- b4 >= 0x80 and b4 <= 0xBF then
- output[#output + 1] = data:sub(i, i + 4)
- i = i + 4
- else
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- end
- end
- else
- output[#output + 1] = cp437_to_utf8[byte] or "?"
- i = i + 1
- end
- end
- if #output > 0 then
- io.write(table.concat(output))
- end
- end
- function Terminal:write(data)
- if not data or #data == 0 then
- return
- end
- data = self.ansiBuffer .. self.utf8Buffer .. data
- self.ansiBuffer = ""
- self.utf8Buffer = ""
- local i = 1
- local len = #data
- local textStart = 1
- while i <= len do
- local esc = data:find("\27", i, true)
- if not esc then
- self:renderText(data:sub(textStart), true)
- break
- end
- if esc > textStart then
- self:renderText(data:sub(textStart, esc - 1), false)
- end
- if esc + 1 <= len and data:sub(esc + 1, esc + 1) == "[" then
- local finalPos = data:find("[@-~]", esc + 2)
- if not finalPos then
- self.ansiBuffer = data:sub(esc)
- return
- end
- local raw = data:sub(esc + 2, finalPos - 1)
- local final = data:sub(finalPos, finalPos)
- self:executeCSI(raw, final)
- i = finalPos + 1
- textStart = i
- elseif esc + 1 <= len then
- local command = data:sub(esc + 1, esc + 1)
- if command == "]" or command == "P" or command == "X"
- or command == "^" or command == "_" then
- local bel = data:find("\7", esc + 2, true)
- local st = data:find("\27\\", esc + 2, true)
- local terminator
- if bel and (not st or bel < st) then
- terminator = bel + 1
- elseif st then
- terminator = st + 2
- end
- if not terminator then
- self.ansiBuffer = data:sub(esc)
- return
- end
- i = terminator
- else
- if command == "7" then
- self.savedX, self.savedY = term.getCursor()
- elseif command == "8" then
- local width, height = getResolution()
- term.setCursor(
- clamp(self.savedX, 1, width),
- clamp(self.savedY, 1, height)
- )
- elseif command == "c" then
- self:reset()
- elseif command == "D" then
- local x, y = term.getCursor()
- if y >= self.scrollBottom then
- self:scrollRegion(1)
- else
- term.setCursor(x, y + 1)
- end
- elseif command == "M" then
- local x, y = term.getCursor()
- if y <= self.scrollTop then
- self:scrollRegion(-1)
- else
- term.setCursor(x, y - 1)
- end
- end
- i = esc + 2
- end
- textStart = i
- else
- self.ansiBuffer = data:sub(esc)
- return
- end
- end
- end
- local Telnet = {}
- Telnet.__index = Telnet
- function Telnet.new(connection)
- local self = setmetatable({}, Telnet)
- self.connection = connection
- self.buffer = ""
- self.state = "DATA"
- self.command = nil
- self.option = nil
- self.subnegotiation = ""
- self.localOptions = {}
- self.remoteOptions = {}
- self.echo = false
- self.ttypeIndex = 0
- return self
- end
- function Telnet:write(data)
- return send(self.connection, data)
- end
- function Telnet:sendCommand(command, option)
- return self:write(TELNET.IAC .. command .. string.char(option))
- end
- function Telnet:sendSubnegotiation(option, data)
- data = data:gsub(TELNET.IAC, TELNET.IAC .. TELNET.IAC)
- return self:write(TELNET.IAC .. TELNET.SB .. string.char(option) ..
- data .. TELNET.IAC .. TELNET.SE)
- end
- function Telnet:sendNAWS()
- local w, h = getResolution()
- w = clamp(w, 1, 65535)
- h = clamp(h, 1, 65535)
- local data = string.char(
- math.floor(w / 256), w % 256,
- math.floor(h / 256), h % 256
- )
- self:sendSubnegotiation(TELNET.NAWS, data)
- end
- function Telnet:handleDO(option)
- if option == TELNET.TERMINAL_TYPE or option == TELNET.NAWS then
- if not self.localOptions[option] then
- self.localOptions[option] = true
- self:sendCommand(TELNET.WILL, option)
- if option == TELNET.NAWS then
- self:sendNAWS()
- end
- else
- self:sendCommand(TELNET.WILL, option)
- end
- else
- self:sendCommand(TELNET.WONT, option)
- end
- end
- function Telnet:handleDONT(option)
- if self.localOptions[option] then
- self.localOptions[option] = false
- self:sendCommand(TELNET.WONT, option)
- end
- end
- function Telnet:handleWILL(option)
- if option == TELNET.ECHO then
- self.remoteOptions[option] = true
- self.echo = false
- self:sendCommand(TELNET.DO, option)
- elseif option == TELNET.SUPPRESS_GO_AHEAD then
- self.remoteOptions[option] = true
- self:sendCommand(TELNET.DO, option)
- else
- self:sendCommand(TELNET.DONT, option)
- end
- end
- function Telnet:handleWONT(option)
- self.remoteOptions[option] = false
- if option == TELNET.ECHO then
- self.echo = true
- end
- end
- function Telnet:handleSubnegotiation(option, data)
- if option == TELNET.TERMINAL_TYPE and data:byte(1) == TERMINAL_TYPE_SEND then
- self.ttypeIndex = (self.ttypeIndex % #TERMINAL_TYPES) + 1
- local name = TERMINAL_TYPES[self.ttypeIndex]
- self:sendSubnegotiation(
- TELNET.TERMINAL_TYPE,
- string.char(TERMINAL_TYPE_IS) .. name
- )
- end
- end
- function Telnet:process(data)
- local output = {}
- local i = 1
- local len = #data
- while i <= len do
- local byte = data:byte(i)
- if self.state == "DATA" then
- if byte == 255 then
- self.state = "IAC"
- else
- output[#output + 1] = string.char(byte)
- end
- i = i + 1
- elseif self.state == "IAC" then
- if byte == 255 then
- output[#output + 1] = TELNET.IAC
- self.state = "DATA"
- elseif byte == 253 then
- self.command = TELNET.DO
- self.state = "OPTION"
- elseif byte == 254 then
- self.command = TELNET.DONT
- self.state = "OPTION"
- elseif byte == 251 then
- self.command = TELNET.WILL
- self.state = "OPTION"
- elseif byte == 252 then
- self.command = TELNET.WONT
- self.state = "OPTION"
- elseif byte == 250 then
- self.state = "SB_OPTION"
- elseif byte == 240 then
- self.state = "DATA"
- else
- self.state = "DATA"
- end
- i = i + 1
- elseif self.state == "OPTION" then
- if self.command == TELNET.DO then
- self:handleDO(byte)
- elseif self.command == TELNET.DONT then
- self:handleDONT(byte)
- elseif self.command == TELNET.WILL then
- self:handleWILL(byte)
- elseif self.command == TELNET.WONT then
- self:handleWONT(byte)
- end
- self.command = nil
- self.state = "DATA"
- i = i + 1
- elseif self.state == "SB_OPTION" then
- self.option = byte
- self.subnegotiation = ""
- self.state = "SB_DATA"
- i = i + 1
- elseif self.state == "SB_DATA" then
- if byte == 255 then
- self.state = "SB_IAC"
- else
- self.subnegotiation = self.subnegotiation .. string.char(byte)
- end
- i = i + 1
- elseif self.state == "SB_IAC" then
- if byte == 240 then
- self:handleSubnegotiation(self.option, self.subnegotiation)
- self.option = nil
- self.subnegotiation = ""
- self.state = "DATA"
- elseif byte == 255 then
- self.subnegotiation = self.subnegotiation .. TELNET.IAC
- self.state = "SB_DATA"
- else
- self.state = "SB_DATA"
- self.subnegotiation = self.subnegotiation .. TELNET.IAC .. string.char(byte)
- end
- i = i + 1
- end
- end
- return table.concat(output)
- end
- function Telnet:sendKey(data)
- if not data or #data == 0 then
- return true
- end
- data = data:gsub(TELNET.IAC, TELNET.IAC .. TELNET.IAC)
- return self:write(data)
- end
- local SPECIAL_KEYS = {
- [200] = "\27[A",
- [208] = "\27[B",
- [205] = "\27[C",
- [203] = "\27[D",
- [199] = "\27[H",
- [207] = "\27[F",
- [201] = "\27[5~",
- [209] = "\27[6~",
- [210] = "\27[2~",
- [211] = "\27[3~",
- [15] = "\t",
- [1] = "\27",
- [59] = "\27OP",
- [60] = "\27OQ",
- [61] = "\27OR",
- [62] = "\27OS",
- [63] = "\27[15~",
- [64] = "\27[17~",
- [65] = "\27[18~",
- [66] = "\27[19~",
- [67] = "\27[20~",
- [68] = "\27[21~",
- [87] = "\27[23~",
- [88] = "\27[24~",
- }
- local LETTER_SCANCODES = {
- [30]="a",[48]="b",[46]="c",[32]="d",[18]="e",[33]="f",[34]="g",[35]="h",
- [23]="i",[36]="j",[37]="k",[38]="l",[50]="m",[49]="n",[24]="o",[25]="p",
- [16]="q",[19]="r",[31]="s",[20]="t",[22]="u",[47]="v",[17]="w",[45]="x",
- [21]="y",[44]="z",
- }
- local function keyToString(char, code)
- if code == 28 then
- return "\r"
- elseif code == 14 then
- return string.char(8)
- elseif SPECIAL_KEYS[code] then
- return SPECIAL_KEYS[code]
- elseif char and char > 0 then
- return string.char(char)
- elseif keyboard.isControlDown() and LETTER_SCANCODES[code] then
- local letter = LETTER_SCANCODES[code]
- return string.char(letter:byte() - 96)
- end
- return nil
- end
- local function isCtrlAltDown()
- return keyboard.isControlDown()
- and keyboard.isAltDown()
- end
- local function connectTelnet(host, port)
- local connection
- local ok, err = pcall(function()
- connection = internet.connect(host, port)
- end)
- if not ok or not connection then
- print("Failed to connect to " .. host .. ":" .. port)
- if err then
- print(tostring(err))
- end
- return
- end
- print("Connecting to " .. host .. ":" .. port .. "...")
- local start = computer.uptime()
- while true do
- local connected
- local finishOK, finishResult = pcall(function()
- return connection.finishConnect()
- end)
- if not finishOK then
- print("Connection error: " .. tostring(finishResult))
- pcall(function() connection.close() end)
- return
- end
- connected = finishResult
- if connected then
- break
- end
- if computer.uptime() - start >= CONNECT_TIMEOUT then
- print("Connection timed out")
- pcall(function() connection.close() end)
- return
- end
- os.sleep(0.05)
- end
- local width, height = getResolution()
- gpu.setResolution(width, height)
- term.clear()
- term.setCursor(1, 1)
- local telnet
- local terminal = Terminal.new(function(data)
- telnet:sendKey(data)
- end)
- local sessionOK, sessionErr = pcall(function()
- telnet = Telnet.new(connection)
- local running = true
- telnet:sendCommand(TELNET.DO, TELNET.ECHO)
- telnet:sendCommand(TELNET.DO, TELNET.SUPPRESS_GO_AHEAD)
- telnet.localOptions[TELNET.TERMINAL_TYPE] = true
- telnet.localOptions[TELNET.NAWS] = true
- telnet:sendCommand(TELNET.WILL, TELNET.TERMINAL_TYPE)
- telnet:sendCommand(TELNET.WILL, TELNET.NAWS)
- telnet:sendNAWS()
- while running do
- local data
- local readOK, readResult = pcall(function()
- return connection.read(READ_SIZE)
- end)
- if not readOK then
- print("\nNetwork read error: " .. tostring(readResult))
- terminal:flush()
- break
- end
- data = readResult
- if data and #data > 0 then
- local applicationData = telnet:process(data)
- if #applicationData > 0 then
- terminal:write(applicationData)
- io.flush()
- end
- end
- local events = {event.pull(0.02)}
- if events[1] == "key_down" then
- local char, code = events[3], events[4]
- if isCtrlAltDown() and char == 46 then
- print("\nDisconnecting...")
- running = false
- else
- local key = keyToString(char, code)
- if key then
- if key == "\r" then
- telnet:sendKey("\r")
- if telnet.echo then
- io.write("\r\n")
- io.flush()
- end
- elseif key == string.char(8) then
- telnet:sendKey(key)
- if telnet.echo then
- io.write("\b \b")
- io.flush()
- end
- else
- telnet:sendKey(key)
- if telnet.echo and #key == 1 and key:byte() >= 0x20 then
- io.write(key)
- io.flush()
- end
- end
- end
- end
- elseif events[1] == "component_added" or events[1] == "component_removed" then
- end
- terminal:flush()
- end
- end)
- terminal:destroy()
- pcall(function() connection.close() end)
- term.setCursorBlink(true)
- gpu.setForeground(ansi_palette[7])
- gpu.setBackground(ansi_palette[0])
- if not sessionOK then
- error(sessionErr, 0)
- end
- print("\nDisconnected. Press any key to return.")
- event.pull("key_down")
- end
- local function displayLogo()
- term.clear()
- print([[
- ________________ ___________________________ __________________
- __ __ \_ ____/ ___ __/__ ____/__ /___ | / /__ ____/__ __/
- _ / / / / _________ / __ __/ __ / __ |/ /__ __/ __ /
- / /_/ // /___/_____/ / _ /___ _ /___ /| / _ /___ _ /
- \____/ \____/ /_/ /_____/ /_____/_/ |_/ /_____/ /_/
- ]])
- print("OC-TELNET Client made by nonogamer9\n")
- end
- local function parseAddress(input)
- input = input:match("^%s*(.-)%s*$")
- if input == "" then
- return nil
- end
- local host, port = input:match("^([^:%s]+):(%d+)$")
- if host and port then
- return host, tonumber(port)
- end
- host, port = input:match("^(%S+)%s+(%d+)$")
- if host and port then
- return host, tonumber(port)
- end
- if input:match("^%S+$") then
- return input, DEFAULT_PORT
- end
- return nil
- end
- while true do
- displayLogo()
- print("Enter host and optional port:")
- print("Examples: bbs.example.com 23")
- print(" bbs.example.com:23")
- print(" bbs.example.com")
- print("Type 'exit' to quit.")
- io.write("> ")
- local input = io.read()
- if not input or input:lower() == "exit" then
- break
- end
- local host, port = parseAddress(input)
- if host and port and port >= 1 and port <= 65535 then
- connectTelnet(host, port)
- else
- print("Invalid address. Use: host, host port, or host:port")
- os.sleep(1.5)
- end
- end
- term.clear()
- term.setCursor(1, 1)
Advertisement
Add Comment
Please, Sign In to add comment