Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- lemonphone_server.lua
- -- Dedicated Lemonphone server node
- -- Run on any CC computer with a modem. Saves registry to /lemonphone_registry
- local REG_PATH = "/lemonphone_registry"
- local GOSSIP_INTERVAL = 10
- local FORWARD_TIMEOUT = 2
- -- load/save registry
- local registry = {} -- phone -> {server = id, lastSeen = os.time()}
- local function saveRegistry()
- local f = fs.open(REG_PATH, "w")
- if f then
- f.write(textutils.serialize(registry))
- f.close()
- end
- end
- local function loadRegistry()
- if fs.exists(REG_PATH) then
- local f = fs.open(REG_PATH, "r")
- local data = f.readAll()
- f.close()
- local ok, t = pcall(textutils.unserialize, data)
- if ok and type(t) == "table" then registry = t end
- end
- end
- loadRegistry()
- -- host service so clients can discover this server
- rednet.host("lemonphone_server", tostring(os.getComputerID()))
- -- knownServers: map serverID -> lastHeard
- local knownServers = {}
- local function markServer(id)
- knownServers[id] = os.time()
- end
- -- gossip: periodically broadcast our registry summary (not full content, just phone -> owner mappings)
- local function gossipLoop()
- while true do
- local summary = {}
- for phone, info in pairs(registry) do
- summary[phone] = info.server
- end
- rednet.broadcast(summary, "lemonphone_gossip")
- sleep(GOSSIP_INTERVAL)
- end
- end
- -- helper: assign phone number
- local function genHex4()
- local n = math.random(0, 0xFFFF)
- return string.format("%04X", n)
- end
- local function makePhone(serviceType)
- serviceType = tostring(serviceType or 2)
- return serviceType .. "-" .. genHex4() .. "-" .. genHex4()
- end
- -- handle registration requests
- local function handleRegister(id, msg)
- -- msg = { type="register_request", desired_service = 2, requested_id = maybe }
- local serviceType = tonumber(msg.desired_service) or 2
- local phone
- repeat
- phone = makePhone(serviceType)
- until not registry[phone]
- registry[phone] = { server = os.getComputerID(), lastSeen = os.time(), ownerClient = msg.clientID } -- ownerClient optional
- saveRegistry()
- markServer(os.getComputerID())
- rednet.send(id, { type = "register_response", ok = true, phone = phone }, "lemonphone_control")
- end
- -- forward message to owner server
- local function forwardToOwner(ownerServer, payload)
- if ownerServer == os.getComputerID() then
- return false, "owner local" -- shouldn't call this; use local delivery
- end
- local ok, err = pcall(function()
- rednet.send(ownerServer, payload, "lemonphone_control")
- end)
- if not ok then
- return false, err
- end
- return true
- end
- -- local delivery attempt (store messages for later if not reachable)
- local stored = {} -- phone -> list of payloads
- local function deliverLocally(phone, payload)
- -- if ownerClient present and online, rednet.send directly to that client
- local info = registry[phone]
- if not info then
- table.insert(stored, payload)
- return false
- end
- local ownerClient = info.ownerClient
- if ownerClient and type(ownerClient) == "number" then
- local ok = pcall(function() rednet.send(ownerClient, payload, "lemonphone_dm") end)
- if ok then return true end
- end
- -- else store
- stored[phone] = stored[phone] or {}
- table.insert(stored[phone], payload)
- return false
- end
- -- server main listener: handles control messages, forwards, and gossip
- local function serverListener()
- while true do
- local sender, msg, proto = rednet.receive()
- if proto == "lemonphone_control" then
- if msg and msg.type == "register_request" then
- handleRegister(sender, msg)
- elseif msg and msg.type == "server_forward" then
- -- forward to local owner
- local target = msg.dest_phone
- if registry[target] and registry[target].server == os.getComputerID() then
- -- deliver to local ownerClient if known
- deliverLocally(target, { type="dm_incoming", from_phone = msg.from_phone, text = msg.text })
- else
- -- target unknown locally: maybe store
- stored[target] = stored[target] or {}
- table.insert(stored[target], msg)
- end
- end
- elseif proto == "lemonphone_gossip" then
- -- msg is summary table phone -> ownerServer
- for phone, owner in pairs(msg or {}) do
- if not registry[phone] then
- registry[phone] = { server = owner, lastSeen = os.time() }
- else
- -- keep more recent mapping
- registry[phone].server = owner
- registry[phone].lastSeen = os.time()
- end
- end
- saveRegistry()
- elseif proto == "lemonphone_global" then
- -- global chat relay: rebroadcast to local clients and other servers
- -- we simply rebroadcast the message locally (other servers will pick up via rednet.broadcast too)
- rednet.broadcast(msg, "lemonphone_global")
- end
- -- mark server if sender is server
- if sender and tostring(sender) and tostring(sender) ~= tostring(os.getComputerID()) then
- -- heuristics: if the sender hosts lemonphone_server, mark it
- local ok, serv = pcall(rednet.lookup, "lemonphone_server")
- if ok and type(serv) == "table" then
- markServer(sender)
- end
- end
- end
- end
- -- periodic cleanup of stale registry entries
- local function cleanupLoop()
- while true do
- local now = os.time()
- for phone, info in pairs(registry) do
- if now - (info.lastSeen or now) > 60 * 60 * 24 then
- registry[phone] = nil
- end
- end
- saveRegistry()
- sleep(600)
- end
- end
- -- small HTTP-ish GET endpoint simulation is omitted for simplicity; communication is via rednet only
- -- run main loops in parallel
- print("Lemonphone server starting, id = " .. os.getComputerID())
- parallel.waitForAny(gossipLoop, serverListener, cleanupLoop)
Advertisement
Add Comment
Please, Sign In to add comment