Advertisement
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 unicode = require("unicode")
- local gpu = component.gpu
- local keyboard = require("keyboard")
- local serialization = require("serialization")
- local filesystem = require("filesystem")
- local computer = require("computer")
- local internet = component.internet
- -- Constants
- local IAC, DONT, DO, WONT, WILL = 255, 254, 253, 252, 251
- local SB, SE = 250, 240
- local ECHO, SGA, NAWS, TTYPE = 1, 3, 31, 24
- local ESC, CSI = string.char(27), string.char(27) .. "["
- -- Color scheme
- local COLORS = {
- background = 0x1E1E1E,
- foreground = 0xFFFFFF,
- accent1 = 0x007ACC,
- accent2 = 0x5F5F5F,
- highlight = 0x3C3C3C,
- error = 0xCC0000,
- }
- -- Configuration
- local config = {
- termType = "ANSI",
- autoWrap = true,
- localEcho = false,
- logFile = "/tmp/telnet.log",
- menuKey = keyboard.keys.f1,
- macros = {},
- backgroundColor = COLORS.background,
- foregroundColor = COLORS.foreground,
- ansiCompatMode = false,
- }
- -- ANSI state
- local ansiState = {
- bold = false,
- underline = false,
- inverse = false,
- fg = config.foregroundColor,
- bg = config.backgroundColor,
- }
- -- Color mapping (extended for 256 colors)
- local colors = {}
- for i = 0, 15 do
- colors[i] = 2^24 - 1 - (i * 2^20)
- end
- for i = 16, 231 do
- local r = math.floor((i - 16) / 36) * 51
- local g = math.floor(((i - 16) % 36) / 6) * 51
- local b = ((i - 16) % 6) * 51
- colors[i] = (r * 2^16) + (g * 2^8) + b
- end
- for i = 232, 255 do
- local gray = (i - 232) * 10 + 8
- colors[i] = (gray * 2^16) + (gray * 2^8) + gray
- end
- -- UI Components
- local UI = {}
- function UI.drawBox(x, y, width, height, title, style)
- style = style or {}
- local bg = style.bg or COLORS.background
- local fg = style.fg or COLORS.foreground
- local borderColor = style.borderColor or COLORS.accent1
- gpu.setBackground(bg)
- gpu.setForeground(borderColor)
- gpu.fill(x, y, width, height, " ")
- gpu.set(x, y, "╔" .. string.rep("═", width-2) .. "╗")
- for i = 1, height-2 do
- gpu.set(x, y+i, "║")
- gpu.set(x+width-1, y+i, "║")
- end
- gpu.set(x, y+height-1, "╚" .. string.rep("═", width-2) .. "╝")
- if title then
- gpu.setForeground(fg)
- gpu.set(x + (width - #title) / 2, y, " " .. title .. " ")
- end
- end
- function UI.drawButton(x, y, text, active)
- local width = #text + 4
- local bg = active and COLORS.accent1 or COLORS.accent2
- gpu.setBackground(bg)
- gpu.setForeground(COLORS.foreground)
- gpu.fill(x, y, width, 3, " ")
- gpu.set(x, y, "┌" .. string.rep("─", width-2) .. "┐")
- gpu.set(x, y+1, "│ " .. text .. " │")
- gpu.set(x, y+2, "└" .. string.rep("─", width-2) .. "┘")
- end
- function UI.drawProgressBar(x, y, width, progress)
- local filledWidth = math.floor(width * progress)
- gpu.setBackground(COLORS.accent2)
- gpu.fill(x, y, width, 1, "─")
- gpu.setBackground(COLORS.accent1)
- gpu.fill(x, y, filledWidth, 1, "─")
- end
- function UI.input(x, y, width, prompt)
- gpu.setBackground(COLORS.background)
- gpu.setForeground(COLORS.foreground)
- gpu.fill(x, y, width, 1, " ")
- gpu.set(x, y, prompt)
- local input = ""
- local cursorPos = #prompt + 1
- local startTime = computer.uptime()
- while true do
- gpu.set(x + cursorPos - 1, y, "_")
- local evt, _, char, code = event.pull(0.5)
- gpu.set(x + cursorPos - 1, y, " ")
- if evt == "key_down" then
- if code == keyboard.keys.enter then
- break
- elseif code == keyboard.keys.back and #input > 0 then
- input = input:sub(1, -2)
- cursorPos = cursorPos - 1
- elseif char > 31 and char < 127 then
- input = input .. unicode.char(char)
- cursorPos = cursorPos + 1
- end
- end
- gpu.set(x + #prompt, y, input .. string.rep(" ", width - #prompt - #input))
- local currentTime = computer.uptime()
- local progress = (currentTime - startTime) % 2 / 2
- UI.drawProgressBar(x, y + 1, width, progress)
- end
- return input
- end
- function UI.showMenu()
- local w, h = gpu.getResolution()
- local menuWidth, menuHeight = 50, 15
- local x, y = (w - menuWidth) / 2, (h - menuHeight) / 2
- UI.drawBox(x, y, menuWidth, menuHeight, "Telnet Menu", {borderColor = COLORS.accent1})
- local options = {
- "1. Toggle Local Echo",
- "2. Toggle Auto Wrap",
- "3. Set Terminal Type",
- "4. Configure Macros",
- "5. Set Colors",
- "6. Toggle ANSI Compatibility Mode",
- "7. Exit"
- }
- for i, option in ipairs(options) do
- gpu.setForeground(COLORS.foreground)
- gpu.set(x + 2, y + i + 1, option)
- end
- return tonumber(UI.input(x + 2, y + #options + 3, menuWidth - 4, "Choice: "))
- end
- -- Load configuration
- local function loadConfig()
- if filesystem.exists("/etc/telnet.cfg") then
- local file = io.open("/etc/telnet.cfg", "r")
- local content = file:read("*all")
- file:close()
- local loaded = serialization.unserialize(content)
- for k, v in pairs(loaded) do
- config[k] = v
- end
- end
- end
- -- Save configuration
- local function saveConfig()
- local file = io.open("/etc/telnet.cfg", "w")
- file:write(serialization.serialize(config))
- file:close()
- end
- -- Handle telnet negotiations
- local function handleNegotiation(socket, data)
- if data:byte(1) == IAC then
- local cmd, option = data:byte(2), data:byte(3)
- if cmd == DO then
- if option == ECHO or option == SGA or option == NAWS or option == TTYPE then
- socket.write(string.char(IAC, WILL, option))
- if option == ECHO then config.localEcho = false end
- else
- socket.write(string.char(IAC, WONT, option))
- end
- elseif cmd == WILL then
- if option == ECHO or option == SGA or option == NAWS or option == TTYPE then
- socket.write(string.char(IAC, DO, option))
- if option == ECHO then config.localEcho = true end
- else
- socket.write(string.char(IAC, DONT, option))
- end
- elseif cmd == SB then
- if option == TTYPE then
- socket.write(string.char(IAC, SB, TTYPE, 0) .. config.termType .. string.char(IAC, SE))
- elseif option == NAWS then
- local w, h = gpu.getResolution()
- socket.write(string.char(IAC, SB, NAWS, w // 256, w % 256, h // 256, h % 256, IAC, SE))
- end
- end
- return true
- end
- return false
- end
- -- ANSI escape sequence handler
- local function handleANSI(sequence)
- local params = {}
- for param in sequence:gmatch("%d+") do
- params[#params + 1] = tonumber(param)
- end
- local command = sequence:sub(-1)
- if command == "m" then -- SGR (Select Graphic Rendition)
- for i = 1, #params do
- local param = params[i]
- if param == 0 then
- ansiState = {bold = false, underline = false, inverse = false, fg = config.foregroundColor, bg = config.backgroundColor}
- elseif param == 1 then
- ansiState.bold = true
- elseif param == 4 then
- ansiState.underline = true
- elseif param == 7 then
- ansiState.inverse = true
- elseif param >= 30 and param <= 37 then
- ansiState.fg = colors[param - 30]
- elseif param >= 40 and param <= 47 then
- ansiState.bg = colors[param - 40]
- elseif param >= 90 and param <= 97 then
- ansiState.fg = colors[param - 82]
- elseif param >= 100 and param <= 107 then
- ansiState.bg = colors[param - 92]
- elseif param == 38 or param == 48 then
- if params[i+1] == 5 then -- 256 color mode
- local color = colors[params[i+2]] or 0
- if param == 38 then ansiState.fg = color else ansiState.bg = color end
- i = i + 2
- elseif params[i+1] == 2 then -- RGB color mode
- local r, g, b = params[i+2], params[i+3], params[i+4]
- local color = (r * 2^16) + (g * 2^8) + b
- if param == 38 then ansiState.fg = color else ansiState.bg = color end
- i = i + 4
- end
- end
- end
- gpu.setForeground(ansiState.fg)
- gpu.setBackground(ansiState.bg)
- elseif command == "H" or command == "f" then -- Cursor Position
- term.setCursor(params[2] or 1, params[1] or 1)
- elseif command == "J" then -- Erase in Display
- local x, y = term.getCursor()
- local w, h = gpu.getResolution()
- if params[1] == 2 then
- gpu.fill(1, 1, w, h, " ")
- elseif params[1] == 1 then
- gpu.fill(1, 1, w, y, " ")
- gpu.fill(1, y, x, 1, " ")
- else
- gpu.fill(x, y, w - x + 1, 1, " ")
- gpu.fill(1, y + 1, w, h - y, " ")
- end
- elseif command == "K" then -- Erase in Line
- local x, y = term.getCursor()
- local w = gpu.getResolution()
- if params[1] == 2 then
- gpu.fill(1, y, w, 1, " ")
- elseif params[1] == 1 then
- gpu.fill(1, y, x, 1, " ")
- else
- gpu.fill(x, y, w - x + 1, 1, " ")
- end
- elseif command == "A" then -- Cursor Up
- local x, y = term.getCursor()
- term.setCursor(x, math.max(1, y - (params[1] or 1)))
- elseif command == "B" then -- Cursor Down
- local x, y = term.getCursor()
- local _, h = gpu.getResolution()
- term.setCursor(x, math.min(h, y + (params[1] or 1)))
- elseif command == "C" then -- Cursor Forward
- local x, y = term.getCursor()
- local w = gpu.getResolution()
- term.setCursor(math.min(w, x + (params[1] or 1)), y)
- elseif command == "D" then -- Cursor Back
- local x, y = term.getCursor()
- term.setCursor(math.max(1, x - (params[1] or 1)), y)
- end
- end
- -- Main telnet function
- local function telnet(host, port)
- local socket = internet.connect(host, port)
- if not socket then error("Could not connect to " .. host .. ":" .. port) end
- local buffer = ""
- local w, h = gpu.getResolution()
- -- Draw UI
- UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
- UI.drawButton(w-10, h-3, "Menu", false)
- -- Set up a scrollable area for telnet output
- local scrollArea = {x = 2, y = 2, width = w-4, height = h-5}
- local lines = {}
- local function drawScrollArea()
- gpu.setBackground(COLORS.background)
- gpu.setForeground(COLORS.foreground)
- for i = 1, scrollArea.height do
- local line = lines[#lines - scrollArea.height + i] or ""
- gpu.set(scrollArea.x, scrollArea.y + i - 1, line .. string.rep(" ", scrollArea.width - #line))
- end
- end
- local function addLine(text)
- for line in (text .. "\n"):gmatch("([^\n]*)\n") do
- table.insert(lines, line)
- end
- while #lines > 1000 do table.remove(lines, 1) end
- drawScrollArea()
- end
- local function processData(data)
- buffer = buffer .. data
- while true do
- local escStart, escEnd = buffer:find(ESC .. "%[" .. "[%d;]*[A-Za-z]")
- if escStart then
- addLine(buffer:sub(1, escStart - 1))
- if config.ansiCompatMode then
- handleANSI(buffer:sub(escStart + 1, escEnd))
- else
- addLine(buffer:sub(escStart, escEnd))
- end
- buffer = buffer:sub(escEnd + 1)
- else
- addLine(buffer)
- buffer = ""
- break
- end
- end
- end
- local inputBuffer = ""
- local cursorPos = 1
- local function drawInputArea()
- gpu.setBackground(COLORS.background)
- gpu.setForeground(COLORS.foreground)
- gpu.set(2, h-2, string.rep(" ", w-4))
- gpu.set(2, h-2, inputBuffer)
- gpu.set(1 + cursorPos, h-2, "_")
- end
- local startTime = computer.uptime()
- while true do
- local currentTime = computer.uptime()
- local progress = (currentTime - startTime) % 5 / 5 -- 5-second cycle
- UI.drawProgressBar(2, h-1, w-4, progress)
- local data = socket.read(1024)
- if data then
- if not handleNegotiation(socket, data) then
- processData(data)
- end
- end
- drawInputArea()
- local eventType, _, char, code = event.pull(0.05)
- if eventType == "key_down" then
- if char == 3 then -- Ctrl-C
- break
- elseif code == keyboard.keys.enter then
- socket.write(inputBuffer .. "\r\n")
- addLine("> " .. inputBuffer)
- inputBuffer = ""
- cursorPos = 1
- elseif code == keyboard.keys.back and #inputBuffer > 0 then
- inputBuffer = inputBuffer:sub(1, -2)
- cursorPos = math.max(1, cursorPos - 1)
- elseif code == config.menuKey then
- local choice = UI.showMenu()
- if choice == 7 then break end
- -- Handle other menu choices here (as before)
- UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
- drawScrollArea()
- elseif char > 31 and char < 127 then
- local newChar = unicode.char(char)
- inputBuffer = inputBuffer:sub(1, cursorPos - 1) .. newChar .. inputBuffer:sub(cursorPos)
- cursorPos = cursorPos + 1
- if config.localEcho then
- addLine(newChar)
- end
- end
- end
- end
- socket.close()
- end
- -- Main telnet function
- local function telnet(host, port)
- local socket = internet.connect(host, port)
- if not socket then error("Could not connect to " .. host .. ":" .. port) end
- local buffer = ""
- local w, h = gpu.getResolution()
- -- Draw UI
- UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
- UI.drawButton(w-10, h-3, "Menu", false)
- -- Set up a scrollable area for telnet output
- local scrollArea = {x = 2, y = 2, width = w-4, height = h-5}
- local lines = {}
- local function drawScrollArea()
- gpu.setBackground(COLORS.background)
- gpu.setForeground(COLORS.foreground)
- for i = 1, scrollArea.height do
- local line = lines[#lines - scrollArea.height + i] or ""
- gpu.set(scrollArea.x, scrollArea.y + i - 1, line .. string.rep(" ", scrollArea.width - #line))
- end
- end
- local function addLine(text)
- for line in (text .. "\n"):gmatch("([^\n]*)\n") do
- table.insert(lines, line)
- end
- while #lines > 1000 do table.remove(lines, 1) end
- drawScrollArea()
- end
- local function processData(data)
- buffer = buffer .. data
- while true do
- local escStart, escEnd = buffer:find(ESC .. "%[" .. "[%d;]*[A-Za-z]")
- if escStart then
- addLine(buffer:sub(1, escStart - 1))
- if config.ansiCompatMode then
- handleANSI(buffer:sub(escStart + 1, escEnd))
- else
- addLine(buffer:sub(escStart, escEnd))
- end
- buffer = buffer:sub(escEnd + 1)
- else
- addLine(buffer)
- buffer = ""
- break
- end
- end
- end
- local inputBuffer = ""
- local cursorPos = 1
- local function drawInputArea()
- gpu.setBackground(COLORS.background)
- gpu.setForeground(COLORS.foreground)
- gpu.set(2, h-2, string.rep(" ", w-4))
- gpu.set(2, h-2, inputBuffer)
- gpu.set(1 + cursorPos, h-2, "_")
- end
- local startTime = computer.uptime()
- while true do
- local currentTime = computer.uptime()
- local progress = (currentTime - startTime) % 5 / 5 -- 5-second cycle
- UI.drawProgressBar(2, h-1, w-4, progress)
- local data = socket.read(1024)
- if data then
- if not handleNegotiation(socket, data) then
- processData(data)
- end
- end
- drawInputArea()
- local eventType, _, char, code = event.pull(0.05)
- if eventType == "key_down" then
- if char == 3 then -- Ctrl-C
- break
- elseif code == keyboard.keys.enter then
- socket.write(inputBuffer .. "\r\n")
- addLine("> " .. inputBuffer)
- inputBuffer = ""
- cursorPos = 1
- elseif code == keyboard.keys.back and #inputBuffer > 0 then
- inputBuffer = inputBuffer:sub(1, -2)
- cursorPos = math.max(1, cursorPos - 1)
- elseif code == config.menuKey then
- local choice = UI.showMenu()
- if choice == 7 then break end
- -- Handle other menu choices here (as before)
- UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
- drawScrollArea()
- elseif char > 31 and char < 127 then
- local newChar = unicode.char(char)
- inputBuffer = inputBuffer:sub(1, cursorPos - 1) .. newChar .. inputBuffer:sub(cursorPos)
- cursorPos = cursorPos + 1
- if config.localEcho then
- addLine(newChar)
- end
- end
- end
- end
- socket.close()
- end
- -- Main menu
- local function mainMenu()
- local w, h = gpu.getResolution()
- UI.drawBox(1, 1, w, h, "Modern Telnet Client", {borderColor = COLORS.accent1})
- local options = {
- "1. Connect to server",
- "2. Configure settings",
- "3. Exit"
- }
- for i, option in ipairs(options) do
- local x = (w - #option) / 2
- local y = h/2 - #options/2 + i*3
- UI.drawButton(x, y, option, i == 1)
- end
- local choice = tonumber(UI.input((w - 20) / 2, h/2 + #options*2, 20, "Choice: "))
- if choice == 1 then
- local host = UI.input(2, h-2, w-4, "Enter server address: ")
- local port = tonumber(UI.input(2, h-1, w-4, "Enter port: "))
- telnet(host, port)
- elseif choice == 2 then
- UI.showMenu()
- elseif choice == 3 then
- return false
- end
- return true
- end
- -- Load configuration
- loadConfig()
- -- Set initial color scheme
- gpu.setBackground(COLORS.background)
- gpu.setForeground(COLORS.foreground)
- -- Start the client
- while true do
- if not mainMenu() then break end
- end
- -- Save configuration
- saveConfig()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement