print("========================================") print(" BonziWORLD ComputerCraft Bridge") print("========================================") print("") if not http then print("[-] HTTP API is disabled.") return end if not http.websocket then print("[-] WebSocket API is unavailable.") print("[-] You need a newer CC:Tweaked.") return end write("Server URL: ") local serverUrl = read() write("Room ID: ") local roomInput = read() write("Bot Name: ") local botName = read() if not botName or botName == "" then botName = "CC_Bot" end serverUrl = serverUrl:gsub("^https?://", ""):gsub("/+$", "") local room = tonumber(roomInput) or roomInput local wsUrl = "wss://" .. serverUrl .. "/socket.io/?EIO=4&transport=websocket" local ws = nil local socketConnected = false local loggedIn = false local shuttingDown = false local messageQueue = {} local pingInterval = 25000 local pingTimeout = 20000 local APP_HEARTBEAT = 10 local SEND_DELAY = 0.20 local LOGIN_DELAY = 1.5 local reconnectDelay = 2 local lastPacketTime = 0 local function now() return os.epoch("utc") end local function socketEvent(eventName, data) return "42" .. textutils.serialiseJSON({ eventName, data }) end local function rawSend(packet) if not ws or not socketConnected then return false end local ok, err = pcall(function() ws.send(packet) end) if not ok then print("\n[-] WebSocket send failed:\n " .. tostring(err)) return false end return true end local function sendChat(message) if not message or message == "" then return false end return rawSend(socketEvent("talk", { text = message, say = message })) end local function sendApplicationHeartbeat() return rawSend(socketEvent("talk", { text = "", say = "" })) end local function sendLogin() print("[>] Login: " .. botName) print("[>] Room: " .. tostring(room)) return rawSend(socketEvent("login", { name = botName, room = room })) end local function processPacket(packet) if not packet then return end lastPacketTime = now() if packet:sub(1, 1) == "0" then local ok, data = pcall(textutils.unserialiseJSON, packet:sub(2)) if ok and type(data) == "table" then pingInterval = tonumber(data.pingInterval) or pingInterval pingTimeout = tonumber(data.pingTimeout) or pingTimeout print("[+] Engine.IO ping interval: " .. tostring(pingInterval) .. "ms") print("[+] Engine.IO ping timeout: " .. tostring(pingTimeout) .. "ms") end rawSend("40") return end if packet == "2" then rawSend("3") return end if packet == "3" then return end if packet:sub(1, 1) == "1" then print("\n[!] Server closed Engine.IO.") socketConnected, loggedIn = false, false return end if packet == "40" then if not socketConnected then socketConnected = true print("[+] Socket.IO connected.") end return end if packet:sub(1, 2) == "42" then local ok, decoded = pcall(textutils.unserialiseJSON, packet:sub(3)) if ok and type(decoded) == "table" then local eventName, payload = decoded[1], decoded[2] if eventName == "talk" and type(payload) == "table" then local sender = payload.name or "User" local text = payload.text or payload.say or "" if text ~= "" and sender ~= botName then print("\n[" .. sender .. "]: " .. text) write("> ") end end end return end print("[?] " .. packet) end local function closeSocket() socketConnected, loggedIn = false, false if ws then pcall(function() ws.close() end) end ws = nil end local function connect() print("\n----------------------------------------") print("[*] Connecting...\n[*] " .. serverUrl) print("----------------------------------------") local newWs, err = http.websocket(wsUrl, { ["User-Agent"] = "Mozilla/5.0 (CraftOS)", ["Origin"] = "https://" .. serverUrl }) if not newWs then print("[-] WebSocket connection failed:\n " .. tostring(err)) return false end ws = newWs socketConnected, loggedIn = false, false lastPacketTime = now() print("[+] WebSocket connected.") local deadline = now() + 10000 while now() < deadline do local packet = ws.receive(1) if packet then processPacket(packet) if packet:sub(1, 1) == "0" then break end end end if not socketConnected then local socketDeadline = now() + 10000 while now() < socketDeadline and not socketConnected do local packet = ws.receive(1) if packet then processPacket(packet) end end end if not socketConnected then print("[-] Socket.IO connection failed.") closeSocket() return false end if not sendLogin() then print("[-] Could not send login.") closeSocket() return false end print("[*] Waiting for room registration...") local loginDeadline = now() + math.floor(LOGIN_DELAY * 1000) while now() < loginDeadline do local remaining = loginDeadline - now() local timeout = math.min(0.25, remaining / 1000) if timeout > 0 then local packet = ws.receive(timeout) if packet then processPacket(packet) end end end loggedIn = true print("\n========================================") print(" BRIDGE ONLINE") print("========================================") print("Bot : " .. botName) print("Room : " .. tostring(room)) print("========================================") print("") return true end local function inputLoop() while not shuttingDown do write("> ") local message = read() if not message then break end if message == "exit" then shuttingDown = true print("\n[*] Shutting down...") break end if message ~= "" then table.insert(messageQueue, message) print("[queued] " .. tostring(message)) end end end local function networkLoop() local nextHeartbeat = 0 local nextSend = 0 while not shuttingDown do if not ws or not socketConnected or not loggedIn then if connect() then reconnectDelay = 2 nextHeartbeat = now() + (APP_HEARTBEAT * 1000) nextSend = 0 else if shuttingDown then break end print("[*] Reconnecting in " .. tostring(reconnectDelay) .. " seconds...") os.sleep(reconnectDelay) reconnectDelay = math.min(reconnectDelay * 2, 30) end end if ws and socketConnected and loggedIn then local packet, reason = ws.receive(0.25) if packet then processPacket(packet) elseif reason and reason ~= "Timed out" then print("\n[!] WebSocket receive:\n " .. tostring(reason)) closeSocket() end if loggedIn and #messageQueue > 0 and now() >= nextSend then local message = messageQueue[1] if sendChat(message) then table.remove(messageQueue, 1) print("[sent] " .. tostring(message)) nextSend = now() + math.floor(SEND_DELAY * 1000) else print("[!] Send failed; keeping message queued.") closeSocket() end end if loggedIn and now() >= nextHeartbeat then if sendApplicationHeartbeat() then nextHeartbeat = now() + APP_HEARTBEAT * 1000 else print("[!] Application heartbeat failed.") closeSocket() end end local silentFor = now() - lastPacketTime local maxSilence = math.max(60000, pingInterval + pingTimeout + 30000) if silentFor > maxSilence then print("\n[!] Connection dead. No packet for " .. math.floor(silentFor / 1000) .. "s. Reconnecting...") closeSocket() end else os.sleep(0.1) end end end print("[*] Starting bridge...") print("[*] Automatic reconnect enabled.") print("[*] Message queue enabled.\n") parallel.waitForAny(inputLoop, networkLoop) shuttingDown = true loggedIn, socketConnected = false, false if ws then pcall(function() ws.close() end) end ws = nil print("\n========================================") print(" BRIDGE STOPPED") print("========================================")