LordMirai

ftp client

Aug 6th, 2025 (edited)
9
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.17 KB | None | 0 0
  1. -- LOGS FTP Client Terminal
  2. -- Connects to LOGS FTP Server for file management
  3.  
  4. local modemSide = "back" -- Change this to your modem's side
  5. local serverID = nil -- Will be set after server discovery
  6.  
  7. -- Terminal colors
  8. local colors = {
  9. primary = colors.cyan,
  10. secondary = colors.lightBlue,
  11. accent = colors.yellow,
  12. success = colors.lime,
  13. error = colors.red,
  14. text = colors.white,
  15. background = colors.black
  16. }
  17.  
  18. -- Setup
  19. rednet.open(modemSide)
  20.  
  21. -- Screen size detection
  22. local function getScreenType()
  23. local w, h = term.getSize()
  24. if w <= 13 or h <= 9 then
  25. return "pocket" -- Pocket computer
  26. elseif w <= 25 or h <= 15 then
  27. return "compact" -- Small monitor or tablet
  28. else
  29. return "full" -- Regular monitor or computer
  30. end
  31. end
  32.  
  33. -- Utility Functions
  34. local function centerText(text, y)
  35. local w, h = term.getSize()
  36. if #text <= w then
  37. term.setCursorPos(math.floor((w - #text) / 2) + 1, y)
  38. term.write(text)
  39. else
  40. -- Text too long, truncate and center
  41. local truncated = text:sub(1, w - 3) .. "..."
  42. term.setCursorPos(1, y)
  43. term.write(truncated)
  44. end
  45. end
  46.  
  47. local function clearScreen()
  48. term.setBackgroundColor(colors.background)
  49. term.setTextColor(colors.text)
  50. term.clear()
  51. term.setCursorPos(1, 1)
  52. end
  53.  
  54. local function printColored(text, color)
  55. color = color or colors.text
  56. term.setTextColor(color)
  57. print(text)
  58. term.setTextColor(colors.text)
  59. end
  60.  
  61. -- Wrap text to fit screen width
  62. local function printWrapped(text, color)
  63. color = color or colors.text
  64. term.setTextColor(color)
  65.  
  66. local w, h = term.getSize()
  67. local words = {}
  68. for word in text:gmatch("%S+") do
  69. table.insert(words, word)
  70. end
  71.  
  72. local line = ""
  73. for i, word in ipairs(words) do
  74. local testLine = line == "" and word or line .. " " .. word
  75. if #testLine <= w then
  76. line = testLine
  77. else
  78. if line ~= "" then
  79. print(line)
  80. line = word
  81. else
  82. -- Single word longer than line, just print it
  83. print(word)
  84. end
  85. end
  86. end
  87.  
  88. if line ~= "" then
  89. print(line)
  90. end
  91.  
  92. term.setTextColor(colors.text)
  93. end
  94.  
  95. -- Adaptive prompt function
  96. local function promptUser(fullPrompt, shortPrompt)
  97. local w, h = term.getSize()
  98. local prompt = (w >= 30) and fullPrompt or shortPrompt
  99.  
  100. term.setTextColor(colors.accent)
  101. write(prompt)
  102. term.setTextColor(colors.text)
  103. return read()
  104. end
  105.  
  106. local function printHeader(title)
  107. local w, h = term.getSize()
  108. term.setTextColor(colors.primary)
  109.  
  110. -- Adaptive header based on screen width
  111. if w >= 30 then
  112. print(string.rep("=", w))
  113. centerText(title, select(2, term.getCursorPos()))
  114. print()
  115. term.setTextColor(colors.primary)
  116. print(string.rep("=", w))
  117. elseif w >= 15 then
  118. print(string.rep("-", w))
  119. centerText(title, select(2, term.getCursorPos()))
  120. print()
  121. print(string.rep("-", w))
  122. else
  123. -- Very compact header for pocket computers
  124. local shortTitle = title
  125. if #title > w then
  126. shortTitle = title:sub(1, w - 3) .. "..."
  127. end
  128. print(shortTitle)
  129. print(string.rep("-", math.min(w, #shortTitle)))
  130. end
  131.  
  132. term.setTextColor(colors.text)
  133. end
  134.  
  135. -- Splash Screen
  136. local function showSplash()
  137. clearScreen()
  138.  
  139. local w, h = term.getSize()
  140.  
  141. -- Adaptive splash screen based on screen size
  142. if w >= 35 and h >= 15 then
  143. -- Full logo for larger screens
  144. local logoY = math.floor(h / 2) - 4
  145.  
  146. term.setTextColor(colors.primary)
  147. centerText("[" .. string.rep("=", w - 2) .. "]", logoY)
  148. centerText("|" .. string.rep(" ", w - 2) .. "|", logoY + 1)
  149. centerText("| LOGS FTP CLIENT |", logoY + 2)
  150. centerText("|" .. string.rep(" ", w - 2) .. "|", logoY + 3)
  151. centerText("| File Transfer Terminal |", logoY + 4)
  152. centerText("|" .. string.rep(" ", w - 2) .. "|", logoY + 5)
  153. centerText("[" .. string.rep("=", w - 2) .. "]", logoY + 6)
  154.  
  155. term.setTextColor(colors.accent)
  156. centerText("Initializing connection...", logoY + 8)
  157. elseif w >= 20 and h >= 10 then
  158. -- Compact logo for medium screens
  159. local logoY = math.floor(h / 2) - 2
  160.  
  161. term.setTextColor(colors.primary)
  162. centerText(string.rep("=", math.min(w, 20)), logoY)
  163. centerText("LOGS FTP CLIENT", logoY + 1)
  164. centerText("File Transfer", logoY + 2)
  165. centerText(string.rep("=", math.min(w, 20)), logoY + 3)
  166.  
  167. term.setTextColor(colors.accent)
  168. centerText("Connecting...", logoY + 5)
  169. else
  170. -- Minimal logo for pocket computers
  171. local logoY = math.floor(h / 2) - 1
  172.  
  173. term.setTextColor(colors.primary)
  174. centerText("LOGS FTP", logoY)
  175. centerText(string.rep("-", math.min(w, 10)), logoY + 1)
  176.  
  177. term.setTextColor(colors.accent)
  178. centerText("Connecting", logoY + 3)
  179. end
  180.  
  181. term.setTextColor(colors.text)
  182. sleep(1.2)
  183. end
  184.  
  185. -- Server Discovery
  186. local function discoverServer()
  187. local screenType = getScreenType()
  188. local searchMsg = (screenType == "pocket") and "Finding server..." or "Searching for LOGS server..."
  189.  
  190. printColored(searchMsg, colors.secondary)
  191.  
  192. -- Send broadcast to find server
  193. rednet.broadcast("ping", "LOGS")
  194.  
  195. local timeout = 5
  196. local startTime = os.clock()
  197.  
  198. while os.clock() - startTime < timeout do
  199. local id, response = rednet.receive("LOGS", 1)
  200. if id then
  201. serverID = id
  202. local successMsg = (screenType == "pocket") and ":) Found: " .. id or ":) Found LOGS server at ID: " .. id
  203. printColored(successMsg, colors.success)
  204. return true
  205. end
  206. end
  207.  
  208. local errorMsg = (screenType == "pocket") and ":( No server" or ":( No LOGS server found"
  209. printColored(errorMsg, colors.error)
  210. return false
  211. end
  212.  
  213. -- Send command to server
  214. local function sendCommand(command)
  215. if not serverID then
  216. local screenType = getScreenType()
  217. local errorMsg = (screenType == "pocket") and "Error: No server" or "Error: Not connected to server"
  218. printColored(errorMsg, colors.error)
  219. return nil
  220. end
  221.  
  222. rednet.send(serverID, command, "LOGS")
  223.  
  224. -- Wait for response
  225. local id, response = rednet.receive("LOGS", 10)
  226. if id == serverID then
  227. return response
  228. else
  229. local screenType = getScreenType()
  230. local errorMsg = (screenType == "pocket") and "Error: No response" or "Error: No response from server"
  231. printColored(errorMsg, colors.error)
  232. return nil
  233. end
  234. end
  235.  
  236. -- Parse server response
  237. local function parseResponse(response)
  238. if not response then
  239. return nil
  240. end
  241.  
  242. -- Check if it's a serialized response
  243. local success, parsed = pcall(textutils.unserialize, response)
  244. if success and type(parsed) == "table" then
  245. return parsed
  246. else
  247. -- Plain text response (likely an error)
  248. return { content = response, method = "plain" }
  249. end
  250. end
  251.  
  252. -- Display formatted response
  253. local function displayResponse(parsed)
  254. if not parsed then
  255. printColored("No response received", colors.error)
  256. return
  257. end
  258.  
  259. if parsed.method == "plain" or parsed.content:find("ERROR:") then
  260. if parsed.content:find("ERROR:") then
  261. printColored(parsed.content, colors.error)
  262. else
  263. printColored(parsed.content, colors.text)
  264. end
  265. return
  266. end
  267.  
  268. -- Display structured response
  269. if parsed.title then
  270. printHeader(parsed.title)
  271. end
  272.  
  273. if parsed.content then
  274. if parsed.method == "list" then
  275. term.setTextColor(colors.secondary)
  276. print(parsed.content)
  277. elseif parsed.method == "read" then
  278. -- Make title bigger for read operations
  279. term.setTextColor(colors.primary)
  280. print("\n")
  281. term.setTextColor(colors.text)
  282. print(parsed.content)
  283. elseif parsed.method == "accesses" then
  284. term.setTextColor(colors.accent)
  285. print("Total entries: " .. (parsed.totalEntries or "Unknown"))
  286. print("Showing: " .. (parsed.requestedCount or "All"))
  287. print()
  288. term.setTextColor(colors.text)
  289. print(parsed.content)
  290. else
  291. term.setTextColor(colors.text)
  292. print(parsed.content)
  293. end
  294. end
  295.  
  296. if parsed.method == "send" then
  297. printColored(":) File uploaded successfully!", colors.success)
  298. end
  299. end
  300.  
  301. -- Command Handlers
  302. local function handleList()
  303. local screenType = getScreenType()
  304. local statusMsg = (screenType == "pocket") and "Getting list..." or "Fetching file list..."
  305. printColored(statusMsg, colors.secondary)
  306. local response = sendCommand("list")
  307. local parsed = parseResponse(response)
  308. displayResponse(parsed)
  309. end
  310.  
  311. local function handleGet()
  312. local filename = promptUser("Enter filename or number: ", "File/num: ")
  313.  
  314. if filename and filename ~= "" then
  315. local screenType = getScreenType()
  316. local statusMsg = (screenType == "pocket") and "Getting: " .. filename or "Downloading file: " .. filename
  317. printColored(statusMsg, colors.secondary)
  318. local response = sendCommand("get " .. filename)
  319. local parsed = parseResponse(response)
  320.  
  321. if parsed and parsed.content and not parsed.content:find("ERROR:") then
  322. -- Create CC-friendly default filename
  323. local fName = parsed.title or filename
  324. -- Replace spaces with underscores
  325. fName = fName:gsub("%s+", "_")
  326. -- Ensure .txt extension if not present
  327. if not fName:match("%.txt$") then
  328. fName = fName .. ".txt"
  329. end
  330.  
  331. -- Prompt for local filename
  332. local localName = promptUser("Save as (press Enter for '" .. fName .. "'): ", "Save as: ")
  333.  
  334. if localName == "" then
  335. localName = fName
  336. else
  337. -- Apply same transformations to user input
  338. localName = localName:gsub("%s+", "_")
  339. if not localName:match("%.txt$") then
  340. localName = localName .. ".txt"
  341. end
  342. end
  343.  
  344. -- Save file locally
  345. local file = fs.open(localName, "w")
  346. file.write(parsed.content)
  347. file.close()
  348.  
  349. printColored("File saved as: " .. localName, colors.success)
  350. else
  351. displayResponse(parsed)
  352. end
  353. else
  354. printColored("No filename provided", colors.error)
  355. end
  356. end
  357.  
  358. local function handleRead()
  359. local filename = promptUser("Enter filename or number: ", "File/num: ")
  360.  
  361. if filename and filename ~= "" then
  362. local screenType = getScreenType()
  363. local statusMsg = (screenType == "pocket") and "Reading: " .. filename or "Reading file: " .. filename
  364. printColored(statusMsg, colors.secondary)
  365. local response = sendCommand("read " .. filename)
  366. local parsed = parseResponse(response)
  367. displayResponse(parsed)
  368. else
  369. printColored("No filename provided", colors.error)
  370. end
  371. end
  372.  
  373. local function handleSend()
  374. -- Show available .txt files in root directory
  375. local txtFiles = {}
  376. local allFiles = fs.list("/")
  377.  
  378. for _, file in ipairs(allFiles) do
  379. if file:match("%.txt$") and not fs.isDir(file) then
  380. table.insert(txtFiles, file)
  381. end
  382. end
  383.  
  384. if #txtFiles > 0 then
  385. local w, h = term.getSize()
  386. term.setTextColor(colors.secondary)
  387.  
  388. if w >= 40 then
  389. print("Available .txt files in root directory:")
  390. elseif w >= 20 then
  391. print("Available .txt files:")
  392. else
  393. print("Files:")
  394. end
  395.  
  396. for i, file in ipairs(txtFiles) do
  397. if w >= 25 then
  398. print(i .. ". " .. file)
  399. else
  400. -- Compact display for small screens
  401. local displayName = file
  402. if #file > w - 4 then
  403. displayName = file:sub(1, w - 7) .. "..."
  404. end
  405. print(i .. "." .. displayName)
  406. end
  407. end
  408. print()
  409. end
  410.  
  411. local filename = promptUser("Enter filename to upload (or number from list): ", "Upload file: ")
  412.  
  413. if not filename or filename == "" then
  414. printColored("No filename provided", colors.error)
  415. return
  416. end
  417.  
  418. -- Check if it's a number referring to the txt files list
  419. local fileNumber = tonumber(filename)
  420. if fileNumber and txtFiles[fileNumber] then
  421. filename = txtFiles[fileNumber]
  422. end
  423.  
  424. if not fs.exists(filename) then
  425. printColored("File not found: " .. filename, colors.error)
  426. return
  427. end
  428.  
  429. if fs.isDir(filename) then
  430. printColored("Cannot upload directory", colors.error)
  431. return
  432. end
  433.  
  434. -- Prompt for server filename
  435. local serverName = promptUser("Enter name for file on server (press Enter for '" .. fs.getName(filename) .. "'): ", "Server name: ")
  436.  
  437. if serverName == "" then
  438. serverName = fs.getName(filename)
  439. end
  440.  
  441. -- Read file content
  442. local file = fs.open(filename, "r")
  443. local content = file.readAll()
  444. file.close()
  445.  
  446. printColored("Uploading file as: " .. serverName, colors.secondary)
  447. local response = sendCommand("send " .. serverName .. " " .. content)
  448. local parsed = parseResponse(response)
  449. displayResponse(parsed)
  450. end
  451.  
  452. local function handleAccesses()
  453. local count = promptUser("Enter number of entries to view (default 20): ", "Entries: ")
  454.  
  455. if count == "" then
  456. count = "20"
  457. end
  458.  
  459. printColored("Fetching access log...", colors.secondary)
  460. local response = sendCommand("accesses " .. count)
  461. local parsed = parseResponse(response)
  462. displayResponse(parsed)
  463. end
  464.  
  465. -- Help display
  466. local function showHelp()
  467. local w, h = term.getSize()
  468.  
  469. if w >= 30 then
  470. printHeader("LOGS FTP Client - Commands")
  471. else
  472. printHeader("Commands")
  473. end
  474.  
  475. term.setTextColor(colors.accent)
  476. print("Available Commands:")
  477. print()
  478.  
  479. term.setTextColor(colors.secondary)
  480.  
  481. -- Adaptive command display based on screen width
  482. if w >= 45 then
  483. -- Full descriptions for wide screens
  484. print("list - List all files on the server")
  485. print("get - Download a file from the server")
  486. print("read - Read a file from the server (view only)")
  487. print("send - Upload a file to the server")
  488. print("accesses - View server access log")
  489. print("help - Show this help menu")
  490. print("clear - Clear the screen")
  491. print("reconnect- Reconnect to server")
  492. print("exit - Exit the client")
  493. print()
  494.  
  495. term.setTextColor(colors.text)
  496. printWrapped("Note: For 'get' and 'read' commands, you can use either the filename or the number from the file list.")
  497. elseif w >= 25 then
  498. -- Medium descriptions
  499. print("list - List files")
  500. print("get - Download file")
  501. print("read - View file")
  502. print("send - Upload file")
  503. print("accesses - Access log")
  504. print("help - Show help")
  505. print("clear - Clear screen")
  506. print("reconnect - Reconnect")
  507. print("exit - Exit")
  508. print()
  509.  
  510. term.setTextColor(colors.text)
  511. printWrapped("Use filename or number for get/read commands.")
  512. else
  513. -- Compact for pocket computers
  514. print("list, get, read")
  515. print("send, accesses")
  516. print("help, clear")
  517. print("reconnect, exit")
  518. print()
  519.  
  520. term.setTextColor(colors.text)
  521. printWrapped("Use file# for get/read")
  522. end
  523. end
  524.  
  525. -- Main menu
  526. local function showMenu()
  527. local w, h = term.getSize()
  528.  
  529. term.setTextColor(colors.primary)
  530. print()
  531.  
  532. if w >= 35 then
  533. print("Connected to server ID: " .. serverID)
  534. print("Type 'help' for available commands")
  535. print(string.rep("-", math.min(w, 40)))
  536. elseif w >= 20 then
  537. print("Server: " .. serverID)
  538. print("Type 'help' for commands")
  539. print(string.rep("-", w))
  540. else
  541. print("ID: " .. serverID)
  542. print("'help' for cmds")
  543. print(string.rep("-", w))
  544. end
  545.  
  546. term.setTextColor(colors.text)
  547. end
  548.  
  549. -- Main application loop
  550. local function main()
  551. showSplash()
  552.  
  553. if not discoverServer() then
  554. printColored("Failed to connect to server. Exiting...", colors.error)
  555. return
  556. end
  557.  
  558. clearScreen()
  559. printHeader("LOGS FTP Client Terminal")
  560. showMenu()
  561.  
  562. while true do
  563. local w, h = term.getSize()
  564. local prompt = (w >= 15) and "LOGS> " or "> "
  565.  
  566. term.setTextColor(colors.accent)
  567. write(prompt)
  568. term.setTextColor(colors.text)
  569. local input = read()
  570.  
  571. if not input then
  572. break
  573. end
  574.  
  575. local command = input:lower():trim()
  576.  
  577. if command == "exit" or command == "quit" then
  578. printColored("Goodbye!", colors.accent)
  579. break
  580. elseif command == "help" then
  581. showHelp()
  582. elseif command == "clear" or command == "cls" then
  583. clearScreen()
  584. printHeader("LOGS FTP Client Terminal")
  585. showMenu()
  586. elseif command == "list" or command == "ls" then
  587. handleList()
  588. elseif command == "get" or command == "download" then
  589. handleGet()
  590. elseif command == "read" or command == "cat" then
  591. handleRead()
  592. elseif command == "send" or command == "upload" then
  593. handleSend()
  594. elseif command == "accesses" or command == "log" then
  595. handleAccesses()
  596. elseif command == "reconnect" then
  597. serverID = nil
  598. if discoverServer() then
  599. printColored(":) Reconnected successfully", colors.success)
  600. showMenu()
  601. end
  602. elseif command == "" then
  603. -- Do nothing for empty input
  604. else
  605. printColored("Unknown command: " .. command, colors.error)
  606. printColored("Type 'help' for available commands", colors.secondary)
  607. end
  608.  
  609. print() -- Add spacing between commands
  610. end
  611. end
  612.  
  613. -- String utility function
  614. string.trim = string.trim or function(s)
  615. return s:match("^%s*(.-)%s*$")
  616. end
  617.  
  618. -- Start the application
  619. main()
  620.  
  621. -- Cleanup
  622. rednet.close(modemSide)
  623.  
Advertisement
Add Comment
Please, Sign In to add comment