Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ============================================================
- -- cct_remote.lua – Pastebin + Remote CLI client
- -- Place this file on your ComputerCraft: Tweaked computer.
- --
- -- Usage:
- -- cct_remote daemon Connect & listen for commands
- -- cct_remote put <file> Upload file → get a code
- -- cct_remote get <code> [file] Download paste (print or save)
- -- cct_remote run <code> Download + run a paste
- -- cct_remote del <code> Delete a paste from the server
- -- ============================================================
- -- ┌─ CONFIG – change these to match your server ──────────────
- local SERVER_HTTP = "http://94.6.73.247:3000"
- local SERVER_WS = "ws://94.6.73.247:3000"
- -- └───────────────────────────────────────────────────────────
- local args = { ... }
- local cmd = args[1]
- -- ── Colour helpers ──────────────────────────────────────────
- local function colour(c)
- if not term.isColour() then return end
- term.setTextColour(c)
- end
- local function reset() colour(colours.white) end
- local function printC(c, ...)
- colour(c)
- print(...)
- reset()
- end
- -- ── Output capture ──────────────────────────────────────────
- -- Uses term.redirect to intercept ALL terminal output, including
- -- text written by shell programs (which bypass _G.print).
- local function captureOutput(fn)
- local lines = {}
- local curLine = ""
- -- Build a dummy terminal that records every character written
- local fakeTerm = {}
- for k, v in pairs(term.current()) do fakeTerm[k] = v end
- fakeTerm.write = function(text)
- curLine = curLine .. tostring(text)
- end
- fakeTerm.blit = function(text)
- curLine = curLine .. tostring(text)
- end
- fakeTerm.setCursorPos = function(x, y)
- -- Moving to a new line flushes the current buffer
- if curLine ~= "" then
- lines[#lines + 1] = curLine
- curLine = ""
- end
- end
- fakeTerm.scroll = function()
- if curLine ~= "" then
- lines[#lines + 1] = curLine
- curLine = ""
- end
- end
- fakeTerm.clear = function() end
- fakeTerm.clearLine = function() end
- fakeTerm.setTextColour = function() end
- fakeTerm.setTextColor = function() end
- fakeTerm.setBackgroundColour = function() end
- fakeTerm.setBackgroundColor = function() end
- local prev = term.redirect(fakeTerm)
- local ok, err = pcall(fn)
- term.redirect(prev)
- -- Flush any remaining partial line
- if curLine ~= "" then lines[#lines + 1] = curLine end
- return ok, table.concat(lines, "\n"), tostring(err)
- end
- -- ── HTTP helpers ────────────────────────────────────────────
- local function http_post(url, body, headers)
- local h = headers or {}
- h["Content-Type"] = h["Content-Type"] or "text/plain"
- local resp, err = http.post(url, body, h)
- if not resp then return nil, err or "HTTP error" end
- local data = resp.readAll()
- resp.close()
- return data
- end
- local function http_get(url)
- local resp, err = http.get(url)
- if not resp then return nil, err or "HTTP error" end
- local data = resp.readAll()
- resp.close()
- return data
- end
- local function http_delete(url)
- -- CC:T http.request supports DELETE
- local resp, err = http.request({ url = url, method = "DELETE" })
- if not resp then return nil, err or "HTTP error" end
- local data = resp.readAll()
- resp.close()
- return data
- end
- -- ── Paste API ───────────────────────────────────────────────
- local function paste_upload(content, filename)
- local headers = { ["Content-Type"] = "text/plain" }
- if filename then headers["X-Filename"] = filename end
- local data, err = http_post(SERVER_HTTP .. "/paste", content, headers)
- if not data then return nil, err end
- local decoded = textutils.unserialiseJSON(data)
- if decoded and decoded.code then return decoded.code end
- return nil, "Server error: " .. tostring(data)
- end
- local function paste_get(code)
- return http_get(SERVER_HTTP .. "/paste/" .. code)
- end
- local function paste_delete(code)
- local data, err = http_delete(SERVER_HTTP .. "/paste/" .. code)
- if not data then return false, err end
- return true
- end
- -- ── Daemon (WebSocket listener) ─────────────────────────────
- local function daemon()
- printC(colours.yellow, "Connecting to " .. SERVER_WS .. " …")
- local ws, err = http.websocket(SERVER_WS)
- if not ws then
- printC(colours.red, "Failed to connect: " .. (err or "unknown"))
- return
- end
- -- Register
- local label = os.getComputerLabel() or ("Computer " .. os.getComputerID())
- ws.send(textutils.serialiseJSON({
- type = "register",
- id = tostring(os.getComputerID()),
- label = label,
- }))
- printC(colours.lime, "Connected as: " .. label)
- printC(colours.yellow, "Listening for remote commands… (hold Ctrl+T to stop)")
- -- ── Receive loop ──────────────────────────────────────────
- while true do
- local event, _, data = os.pullEvent()
- if event == "websocket_message" then
- local ok, msg = pcall(textutils.unserialiseJSON, data)
- if not (ok and msg) then goto continue end
- if msg.type == "run" and msg.code then
- -- Try compiling as Lua first
- local fn, loadErr = load(msg.code, "remote", "t", _ENV)
- local resultOk, output, errMsg
- if fn then
- resultOk, output, errMsg = captureOutput(fn)
- else
- -- Fall back to shell.run (handles programs like "ls", "edit foo", etc.)
- resultOk, output, errMsg = captureOutput(function()
- local success = shell.run(msg.code)
- if not success then error("shell.run failed") end
- end)
- end
- -- Reply to server
- ws.send(textutils.serialiseJSON({
- type = "response",
- msgId = msg.msgId or "",
- ok = resultOk,
- data = resultOk and (output ~= "" and output or "OK") or errMsg,
- }))
- -- Also show locally
- if resultOk then
- printC(colours.lime, "[remote] " .. (output ~= "" and output or "OK"))
- else
- printC(colours.red, "[remote ERROR] " .. errMsg)
- end
- end
- ::continue::
- elseif event == "websocket_closed" then
- printC(colours.orange, "Connection closed. Retrying in 5 s…")
- sleep(5)
- daemon() -- reconnect
- return
- elseif event == "terminate" then
- ws.close()
- printC(colours.yellow, "Daemon stopped.")
- return
- end
- end
- end
- -- ── Subcommand dispatch ─────────────────────────────────────
- if cmd == "daemon" or cmd == "d" then
- -- ── daemon ────────────────────────────────────────────────
- daemon()
- elseif cmd == "put" or cmd == "upload" then
- -- ── put <filename> ────────────────────────────────────────
- local filename = args[2]
- if not filename then
- printError("Usage: cct_remote put <filename>")
- return
- end
- if not fs.exists(filename) then
- printError("File not found: " .. filename)
- return
- end
- local f = fs.open(filename, "r")
- local content = f.readAll()
- f.close()
- printC(colours.yellow, "Uploading " .. filename .. " …")
- local code, err = paste_upload(content, fs.getName(filename))
- if code then
- printC(colours.lime, "Uploaded! Code: " .. code)
- print(" Get: cct_remote get " .. code)
- print(" Run: cct_remote run " .. code)
- else
- printError("Upload failed: " .. (err or "unknown"))
- end
- elseif cmd == "get" or cmd == "download" then
- -- ── get <code> [saveas] ───────────────────────────────────
- local code = args[2]
- local saveas = args[3]
- if not code then
- printError("Usage: cct_remote get <code> [filename]")
- return
- end
- printC(colours.yellow, "Fetching " .. code .. " …")
- local content, err = paste_get(code)
- if not content then
- printError("Failed: " .. (err or "unknown"))
- return
- end
- if saveas then
- local f = fs.open(saveas, "w")
- f.write(content)
- f.close()
- printC(colours.lime, "Saved to: " .. saveas)
- else
- print(content)
- end
- elseif cmd == "run" or cmd == "execute" then
- -- ── run <code> ────────────────────────────────────────────
- local code = args[2]
- if not code then
- printError("Usage: cct_remote run <code>")
- return
- end
- printC(colours.yellow, "Fetching " .. code .. " …")
- local content, err = paste_get(code)
- if not content then
- printError("Failed to fetch: " .. (err or "unknown"))
- return
- end
- local tmpPath = "/.cct_remote_run.lua"
- local f = fs.open(tmpPath, "w")
- f.write(content)
- f.close()
- printC(colours.lime, "Running…")
- shell.run(tmpPath, table.unpack(args, 3))
- fs.delete(tmpPath)
- elseif cmd == "del" or cmd == "delete" then
- -- ── del <code> ────────────────────────────────────────────
- local code = args[2]
- if not code then
- printError("Usage: cct_remote del <code>")
- return
- end
- printC(colours.yellow, "Deleting " .. code .. " …")
- local ok, err = paste_delete(code)
- if ok then
- printC(colours.lime, "Deleted: " .. code)
- else
- printError("Failed: " .. (err or "unknown"))
- end
- else
- -- ── help ──────────────────────────────────────────────────
- colour(colours.yellow)
- print("CC:T Remote – Pastebin & Remote CLI")
- reset()
- print("")
- colour(colours.white)
- print(" cct_remote daemon Connect & listen for commands")
- print(" cct_remote put <file> Upload file → get a code")
- print(" cct_remote get <code> [file] Download paste (print or save)")
- print(" cct_remote run <code> Download and run a paste")
- print(" cct_remote del <code> Delete a paste from the server")
- print("")
- colour(colours.lightGrey)
- print(" Set SERVER_HTTP / SERVER_WS at the top of this file.")
- reset()
- end
Advertisement
Add Comment
Please, Sign In to add comment