Advertisement
nonogamer9

DEVELOPMENT

Oct 11th, 2024 (edited)
46
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.21 KB | None | 0 0
  1. local component = require("component")
  2. local event = require("event")
  3. local term = require("term")
  4. local unicode = require("unicode")
  5. local gpu = component.gpu
  6. local keyboard = require("keyboard")
  7. local serialization = require("serialization")
  8. local filesystem = require("filesystem")
  9. local computer = require("computer")
  10. local internet = component.internet
  11.  
  12. -- Constants
  13. local IAC, DONT, DO, WONT, WILL = 255, 254, 253, 252, 251
  14. local SB, SE = 250, 240
  15. local ECHO, SGA, NAWS, TTYPE = 1, 3, 31, 24
  16. local ESC, CSI = string.char(27), string.char(27) .. "["
  17.  
  18. -- Color scheme
  19. local COLORS = {
  20. background = 0x1E1E1E,
  21. foreground = 0xFFFFFF,
  22. accent1 = 0x007ACC,
  23. accent2 = 0x5F5F5F,
  24. highlight = 0x3C3C3C,
  25. error = 0xCC0000,
  26. }
  27.  
  28. -- Configuration
  29. local config = {
  30. termType = "ANSI",
  31. autoWrap = true,
  32. localEcho = false,
  33. logFile = "/tmp/telnet.log",
  34. menuKey = keyboard.keys.f1,
  35. macros = {},
  36. backgroundColor = COLORS.background,
  37. foregroundColor = COLORS.foreground,
  38. ansiCompatMode = false,
  39. }
  40.  
  41. -- ANSI state
  42. local ansiState = {
  43. bold = false,
  44. underline = false,
  45. inverse = false,
  46. fg = config.foregroundColor,
  47. bg = config.backgroundColor,
  48. }
  49.  
  50. -- Color mapping (extended for 256 colors)
  51. local colors = {}
  52. for i = 0, 15 do
  53. colors[i] = 2^24 - 1 - (i * 2^20)
  54. end
  55. for i = 16, 231 do
  56. local r = math.floor((i - 16) / 36) * 51
  57. local g = math.floor(((i - 16) % 36) / 6) * 51
  58. local b = ((i - 16) % 6) * 51
  59. colors[i] = (r * 2^16) + (g * 2^8) + b
  60. end
  61. for i = 232, 255 do
  62. local gray = (i - 232) * 10 + 8
  63. colors[i] = (gray * 2^16) + (gray * 2^8) + gray
  64. end
  65.  
  66. -- UI Components
  67. local UI = {}
  68.  
  69. function UI.drawBox(x, y, width, height, title, style)
  70. style = style or {}
  71. local bg = style.bg or COLORS.background
  72. local fg = style.fg or COLORS.foreground
  73. local borderColor = style.borderColor or COLORS.accent1
  74.  
  75. gpu.setBackground(bg)
  76. gpu.setForeground(borderColor)
  77. gpu.fill(x, y, width, height, " ")
  78. gpu.set(x, y, "╔" .. string.rep("═", width-2) .. "╗")
  79. for i = 1, height-2 do
  80. gpu.set(x, y+i, "║")
  81. gpu.set(x+width-1, y+i, "║")
  82. end
  83. gpu.set(x, y+height-1, "╚" .. string.rep("═", width-2) .. "╝")
  84.  
  85. if title then
  86. gpu.setForeground(fg)
  87. gpu.set(x + (width - #title) / 2, y, " " .. title .. " ")
  88. end
  89. end
  90.  
  91. function UI.drawButton(x, y, text, active)
  92. local width = #text + 4
  93. local bg = active and COLORS.accent1 or COLORS.accent2
  94. gpu.setBackground(bg)
  95. gpu.setForeground(COLORS.foreground)
  96. gpu.fill(x, y, width, 3, " ")
  97. gpu.set(x, y, "┌" .. string.rep("─", width-2) .. "┐")
  98. gpu.set(x, y+1, "│ " .. text .. " │")
  99. gpu.set(x, y+2, "└" .. string.rep("─", width-2) .. "┘")
  100. end
  101.  
  102. function UI.drawProgressBar(x, y, width, progress)
  103. local filledWidth = math.floor(width * progress)
  104. gpu.setBackground(COLORS.accent2)
  105. gpu.fill(x, y, width, 1, "─")
  106. gpu.setBackground(COLORS.accent1)
  107. gpu.fill(x, y, filledWidth, 1, "─")
  108. end
  109.  
  110. function UI.input(x, y, width, prompt)
  111. gpu.setBackground(COLORS.background)
  112. gpu.setForeground(COLORS.foreground)
  113. gpu.fill(x, y, width, 1, " ")
  114. gpu.set(x, y, prompt)
  115.  
  116. local input = ""
  117. local cursorPos = #prompt + 1
  118. local startTime = computer.uptime()
  119.  
  120. while true do
  121. gpu.set(x + cursorPos - 1, y, "_")
  122. local evt, _, char, code = event.pull(0.5)
  123. gpu.set(x + cursorPos - 1, y, " ")
  124.  
  125. if evt == "key_down" then
  126. if code == keyboard.keys.enter then
  127. break
  128. elseif code == keyboard.keys.back and #input > 0 then
  129. input = input:sub(1, -2)
  130. cursorPos = cursorPos - 1
  131. elseif char > 31 and char < 127 then
  132. input = input .. unicode.char(char)
  133. cursorPos = cursorPos + 1
  134. end
  135. end
  136.  
  137. gpu.set(x + #prompt, y, input .. string.rep(" ", width - #prompt - #input))
  138.  
  139. local currentTime = computer.uptime()
  140. local progress = (currentTime - startTime) % 2 / 2
  141. UI.drawProgressBar(x, y + 1, width, progress)
  142. end
  143.  
  144. return input
  145. end
  146.  
  147. function UI.showMenu()
  148. local w, h = gpu.getResolution()
  149. local menuWidth, menuHeight = 50, 15
  150. local x, y = (w - menuWidth) / 2, (h - menuHeight) / 2
  151.  
  152. UI.drawBox(x, y, menuWidth, menuHeight, "Telnet Menu", {borderColor = COLORS.accent1})
  153.  
  154. local options = {
  155. "1. Toggle Local Echo",
  156. "2. Toggle Auto Wrap",
  157. "3. Set Terminal Type",
  158. "4. Configure Macros",
  159. "5. Set Colors",
  160. "6. Toggle ANSI Compatibility Mode",
  161. "7. Exit"
  162. }
  163.  
  164. for i, option in ipairs(options) do
  165. gpu.setForeground(COLORS.foreground)
  166. gpu.set(x + 2, y + i + 1, option)
  167. end
  168.  
  169. return tonumber(UI.input(x + 2, y + #options + 3, menuWidth - 4, "Choice: "))
  170. end
  171.  
  172. -- Load configuration
  173. local function loadConfig()
  174. if filesystem.exists("/etc/telnet.cfg") then
  175. local file = io.open("/etc/telnet.cfg", "r")
  176. local content = file:read("*all")
  177. file:close()
  178. local loaded = serialization.unserialize(content)
  179. for k, v in pairs(loaded) do
  180. config[k] = v
  181. end
  182. end
  183. end
  184.  
  185. -- Save configuration
  186. local function saveConfig()
  187. local file = io.open("/etc/telnet.cfg", "w")
  188. file:write(serialization.serialize(config))
  189. file:close()
  190. end
  191.  
  192. -- Handle telnet negotiations
  193. local function handleNegotiation(socket, data)
  194. if data:byte(1) == IAC then
  195. local cmd, option = data:byte(2), data:byte(3)
  196.  
  197. if cmd == DO then
  198. if option == ECHO or option == SGA or option == NAWS or option == TTYPE then
  199. socket.write(string.char(IAC, WILL, option))
  200. if option == ECHO then config.localEcho = false end
  201. else
  202. socket.write(string.char(IAC, WONT, option))
  203. end
  204. elseif cmd == WILL then
  205. if option == ECHO or option == SGA or option == NAWS or option == TTYPE then
  206. socket.write(string.char(IAC, DO, option))
  207. if option == ECHO then config.localEcho = true end
  208. else
  209. socket.write(string.char(IAC, DONT, option))
  210. end
  211. elseif cmd == SB then
  212. if option == TTYPE then
  213. socket.write(string.char(IAC, SB, TTYPE, 0) .. config.termType .. string.char(IAC, SE))
  214. elseif option == NAWS then
  215. local w, h = gpu.getResolution()
  216. socket.write(string.char(IAC, SB, NAWS, w // 256, w % 256, h // 256, h % 256, IAC, SE))
  217. end
  218. end
  219. return true
  220. end
  221. return false
  222. end
  223.  
  224. -- ANSI escape sequence handler
  225. local function handleANSI(sequence)
  226. local params = {}
  227. for param in sequence:gmatch("%d+") do
  228. params[#params + 1] = tonumber(param)
  229. end
  230.  
  231. local command = sequence:sub(-1)
  232. if command == "m" then -- SGR (Select Graphic Rendition)
  233. for i = 1, #params do
  234. local param = params[i]
  235. if param == 0 then
  236. ansiState = {bold = false, underline = false, inverse = false, fg = config.foregroundColor, bg = config.backgroundColor}
  237. elseif param == 1 then
  238. ansiState.bold = true
  239. elseif param == 4 then
  240. ansiState.underline = true
  241. elseif param == 7 then
  242. ansiState.inverse = true
  243. elseif param >= 30 and param <= 37 then
  244. ansiState.fg = colors[param - 30]
  245. elseif param >= 40 and param <= 47 then
  246. ansiState.bg = colors[param - 40]
  247. elseif param >= 90 and param <= 97 then
  248. ansiState.fg = colors[param - 82]
  249. elseif param >= 100 and param <= 107 then
  250. ansiState.bg = colors[param - 92]
  251. elseif param == 38 or param == 48 then
  252. if params[i+1] == 5 then -- 256 color mode
  253. local color = colors[params[i+2]] or 0
  254. if param == 38 then ansiState.fg = color else ansiState.bg = color end
  255. i = i + 2
  256. elseif params[i+1] == 2 then -- RGB color mode
  257. local r, g, b = params[i+2], params[i+3], params[i+4]
  258. local color = (r * 2^16) + (g * 2^8) + b
  259. if param == 38 then ansiState.fg = color else ansiState.bg = color end
  260. i = i + 4
  261. end
  262. end
  263. end
  264. gpu.setForeground(ansiState.fg)
  265. gpu.setBackground(ansiState.bg)
  266. elseif command == "H" or command == "f" then -- Cursor Position
  267. term.setCursor(params[2] or 1, params[1] or 1)
  268. elseif command == "J" then -- Erase in Display
  269. local x, y = term.getCursor()
  270. local w, h = gpu.getResolution()
  271. if params[1] == 2 then
  272. gpu.fill(1, 1, w, h, " ")
  273. elseif params[1] == 1 then
  274. gpu.fill(1, 1, w, y, " ")
  275. gpu.fill(1, y, x, 1, " ")
  276. else
  277. gpu.fill(x, y, w - x + 1, 1, " ")
  278. gpu.fill(1, y + 1, w, h - y, " ")
  279. end
  280. elseif command == "K" then -- Erase in Line
  281. local x, y = term.getCursor()
  282. local w = gpu.getResolution()
  283. if params[1] == 2 then
  284. gpu.fill(1, y, w, 1, " ")
  285. elseif params[1] == 1 then
  286. gpu.fill(1, y, x, 1, " ")
  287. else
  288. gpu.fill(x, y, w - x + 1, 1, " ")
  289. end
  290. elseif command == "A" then -- Cursor Up
  291. local x, y = term.getCursor()
  292. term.setCursor(x, math.max(1, y - (params[1] or 1)))
  293. elseif command == "B" then -- Cursor Down
  294. local x, y = term.getCursor()
  295. local _, h = gpu.getResolution()
  296. term.setCursor(x, math.min(h, y + (params[1] or 1)))
  297. elseif command == "C" then -- Cursor Forward
  298. local x, y = term.getCursor()
  299. local w = gpu.getResolution()
  300. term.setCursor(math.min(w, x + (params[1] or 1)), y)
  301. elseif command == "D" then -- Cursor Back
  302. local x, y = term.getCursor()
  303. term.setCursor(math.max(1, x - (params[1] or 1)), y)
  304. end
  305. end
  306.  
  307. -- Main telnet function
  308. local function telnet(host, port)
  309. local socket = internet.connect(host, port)
  310. if not socket then error("Could not connect to " .. host .. ":" .. port) end
  311.  
  312. local buffer = ""
  313. local w, h = gpu.getResolution()
  314.  
  315. -- Draw UI
  316. UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
  317. UI.drawButton(w-10, h-3, "Menu", false)
  318.  
  319. -- Set up a scrollable area for telnet output
  320. local scrollArea = {x = 2, y = 2, width = w-4, height = h-5}
  321. local lines = {}
  322.  
  323. local function drawScrollArea()
  324. gpu.setBackground(COLORS.background)
  325. gpu.setForeground(COLORS.foreground)
  326. for i = 1, scrollArea.height do
  327. local line = lines[#lines - scrollArea.height + i] or ""
  328. gpu.set(scrollArea.x, scrollArea.y + i - 1, line .. string.rep(" ", scrollArea.width - #line))
  329. end
  330. end
  331.  
  332. local function addLine(text)
  333. for line in (text .. "\n"):gmatch("([^\n]*)\n") do
  334. table.insert(lines, line)
  335. end
  336. while #lines > 1000 do table.remove(lines, 1) end
  337. drawScrollArea()
  338. end
  339.  
  340. local function processData(data)
  341. buffer = buffer .. data
  342. while true do
  343. local escStart, escEnd = buffer:find(ESC .. "%[" .. "[%d;]*[A-Za-z]")
  344. if escStart then
  345. addLine(buffer:sub(1, escStart - 1))
  346. if config.ansiCompatMode then
  347. handleANSI(buffer:sub(escStart + 1, escEnd))
  348. else
  349. addLine(buffer:sub(escStart, escEnd))
  350. end
  351. buffer = buffer:sub(escEnd + 1)
  352. else
  353. addLine(buffer)
  354. buffer = ""
  355. break
  356. end
  357. end
  358. end
  359.  
  360. local inputBuffer = ""
  361. local cursorPos = 1
  362.  
  363. local function drawInputArea()
  364. gpu.setBackground(COLORS.background)
  365. gpu.setForeground(COLORS.foreground)
  366. gpu.set(2, h-2, string.rep(" ", w-4))
  367. gpu.set(2, h-2, inputBuffer)
  368. gpu.set(1 + cursorPos, h-2, "_")
  369. end
  370.  
  371. local startTime = computer.uptime()
  372. while true do
  373. local currentTime = computer.uptime()
  374. local progress = (currentTime - startTime) % 5 / 5 -- 5-second cycle
  375. UI.drawProgressBar(2, h-1, w-4, progress)
  376.  
  377. local data = socket.read(1024)
  378. if data then
  379. if not handleNegotiation(socket, data) then
  380. processData(data)
  381. end
  382. end
  383.  
  384. drawInputArea()
  385.  
  386. local eventType, _, char, code = event.pull(0.05)
  387. if eventType == "key_down" then
  388. if char == 3 then -- Ctrl-C
  389. break
  390. elseif code == keyboard.keys.enter then
  391. socket.write(inputBuffer .. "\r\n")
  392. addLine("> " .. inputBuffer)
  393. inputBuffer = ""
  394. cursorPos = 1
  395. elseif code == keyboard.keys.back and #inputBuffer > 0 then
  396. inputBuffer = inputBuffer:sub(1, -2)
  397. cursorPos = math.max(1, cursorPos - 1)
  398. elseif code == config.menuKey then
  399. local choice = UI.showMenu()
  400. if choice == 7 then break end
  401. -- Handle other menu choices here (as before)
  402. UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
  403. drawScrollArea()
  404. elseif char > 31 and char < 127 then
  405. local newChar = unicode.char(char)
  406. inputBuffer = inputBuffer:sub(1, cursorPos - 1) .. newChar .. inputBuffer:sub(cursorPos)
  407. cursorPos = cursorPos + 1
  408. if config.localEcho then
  409. addLine(newChar)
  410. end
  411. end
  412. end
  413. end
  414.  
  415. socket.close()
  416. end
  417. -- Main telnet function
  418. local function telnet(host, port)
  419. local socket = internet.connect(host, port)
  420. if not socket then error("Could not connect to " .. host .. ":" .. port) end
  421.  
  422. local buffer = ""
  423. local w, h = gpu.getResolution()
  424.  
  425. -- Draw UI
  426. UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
  427. UI.drawButton(w-10, h-3, "Menu", false)
  428.  
  429. -- Set up a scrollable area for telnet output
  430. local scrollArea = {x = 2, y = 2, width = w-4, height = h-5}
  431. local lines = {}
  432.  
  433. local function drawScrollArea()
  434. gpu.setBackground(COLORS.background)
  435. gpu.setForeground(COLORS.foreground)
  436. for i = 1, scrollArea.height do
  437. local line = lines[#lines - scrollArea.height + i] or ""
  438. gpu.set(scrollArea.x, scrollArea.y + i - 1, line .. string.rep(" ", scrollArea.width - #line))
  439. end
  440. end
  441.  
  442. local function addLine(text)
  443. for line in (text .. "\n"):gmatch("([^\n]*)\n") do
  444. table.insert(lines, line)
  445. end
  446. while #lines > 1000 do table.remove(lines, 1) end
  447. drawScrollArea()
  448. end
  449.  
  450. local function processData(data)
  451. buffer = buffer .. data
  452. while true do
  453. local escStart, escEnd = buffer:find(ESC .. "%[" .. "[%d;]*[A-Za-z]")
  454. if escStart then
  455. addLine(buffer:sub(1, escStart - 1))
  456. if config.ansiCompatMode then
  457. handleANSI(buffer:sub(escStart + 1, escEnd))
  458. else
  459. addLine(buffer:sub(escStart, escEnd))
  460. end
  461. buffer = buffer:sub(escEnd + 1)
  462. else
  463. addLine(buffer)
  464. buffer = ""
  465. break
  466. end
  467. end
  468. end
  469.  
  470. local inputBuffer = ""
  471. local cursorPos = 1
  472.  
  473. local function drawInputArea()
  474. gpu.setBackground(COLORS.background)
  475. gpu.setForeground(COLORS.foreground)
  476. gpu.set(2, h-2, string.rep(" ", w-4))
  477. gpu.set(2, h-2, inputBuffer)
  478. gpu.set(1 + cursorPos, h-2, "_")
  479. end
  480.  
  481. local startTime = computer.uptime()
  482. while true do
  483. local currentTime = computer.uptime()
  484. local progress = (currentTime - startTime) % 5 / 5 -- 5-second cycle
  485. UI.drawProgressBar(2, h-1, w-4, progress)
  486.  
  487. local data = socket.read(1024)
  488. if data then
  489. if not handleNegotiation(socket, data) then
  490. processData(data)
  491. end
  492. end
  493.  
  494. drawInputArea()
  495.  
  496. local eventType, _, char, code = event.pull(0.05)
  497. if eventType == "key_down" then
  498. if char == 3 then -- Ctrl-C
  499. break
  500. elseif code == keyboard.keys.enter then
  501. socket.write(inputBuffer .. "\r\n")
  502. addLine("> " .. inputBuffer)
  503. inputBuffer = ""
  504. cursorPos = 1
  505. elseif code == keyboard.keys.back and #inputBuffer > 0 then
  506. inputBuffer = inputBuffer:sub(1, -2)
  507. cursorPos = math.max(1, cursorPos - 1)
  508. elseif code == config.menuKey then
  509. local choice = UI.showMenu()
  510. if choice == 7 then break end
  511. -- Handle other menu choices here (as before)
  512. UI.drawBox(1, 1, w, h, "Telnet: " .. host .. ":" .. port, {borderColor = COLORS.accent1})
  513. drawScrollArea()
  514. elseif char > 31 and char < 127 then
  515. local newChar = unicode.char(char)
  516. inputBuffer = inputBuffer:sub(1, cursorPos - 1) .. newChar .. inputBuffer:sub(cursorPos)
  517. cursorPos = cursorPos + 1
  518. if config.localEcho then
  519. addLine(newChar)
  520. end
  521. end
  522. end
  523. end
  524.  
  525. socket.close()
  526. end
  527.  
  528.  
  529. -- Main menu
  530. local function mainMenu()
  531. local w, h = gpu.getResolution()
  532. UI.drawBox(1, 1, w, h, "Modern Telnet Client", {borderColor = COLORS.accent1})
  533.  
  534. local options = {
  535. "1. Connect to server",
  536. "2. Configure settings",
  537. "3. Exit"
  538. }
  539.  
  540. for i, option in ipairs(options) do
  541. local x = (w - #option) / 2
  542. local y = h/2 - #options/2 + i*3
  543. UI.drawButton(x, y, option, i == 1)
  544. end
  545.  
  546. local choice = tonumber(UI.input((w - 20) / 2, h/2 + #options*2, 20, "Choice: "))
  547. if choice == 1 then
  548. local host = UI.input(2, h-2, w-4, "Enter server address: ")
  549. local port = tonumber(UI.input(2, h-1, w-4, "Enter port: "))
  550. telnet(host, port)
  551. elseif choice == 2 then
  552. UI.showMenu()
  553. elseif choice == 3 then
  554. return false
  555. end
  556. return true
  557. end
  558.  
  559. -- Load configuration
  560. loadConfig()
  561.  
  562. -- Set initial color scheme
  563. gpu.setBackground(COLORS.background)
  564. gpu.setForeground(COLORS.foreground)
  565.  
  566. -- Start the client
  567. while true do
  568. if not mainMenu() then break end
  569. end
  570.  
  571. -- Save configuration
  572. saveConfig()
  573.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement