// ============================================================ // [HappyBums] WebRelay v1.1 // HTTP command listener for the Diaper system. // Receives POST commands from the web control panel and // translates them into linked messages that Core understands. // // Must be in the same prim as Core and Persistence. // Communicates via llMessageLinked on LM_CHANNEL = 1001. // // On startup: // - Requests a simulator URL via llRequestURL(). // - On URL grant, sends URL to Core via WEB_RELAY_URL linked // message so Core can include it in heartbeat POSTs to PHP. // - Re-requests URL on CHANGED_REGION or URL expiry. // // Core must handle one new linked message FROM WebRelay: // WEB_RELAY_URL| Store and use in next webPost() heartbeat // // Core must send one new linked message TO WebRelay after init: // WEB_RELAY_INIT| So WebRelay knows the access UUID to // validate incoming requests against // // Incoming POST commands from PHP (JSON body): // { "cmd": "wet", "access_uuid": "" } // { "cmd": "change", "access_uuid": "" } // { "cmd": "change_lock", "access_uuid": "", "value": "0|1|2" } // { "cmd": "hud_lock", "access_uuid": "", "value": "0|1" } // { "cmd": "autowet", "access_uuid": "", "value": "" } // { "cmd": "msg", "access_uuid": "", "value": "" } // // Linked messages sent TO Core on valid command: // WEB_CMD|wet // WEB_CMD|change // WEB_CMD|change_lock|0 (0=unlock 1=wearer-lock 2=owner-lock) // WEB_CMD|hud_lock|0 (0=unlock 1=lock) // WEB_CMD|autowet|1800 (value in seconds, 0=disabled) // WEB_CMD|msg| // // ============================================================ integer LM_CHANNEL = 1001; string g_sim_url = ""; // URL from llRequestURL() key g_url_req = NULL_KEY; // pending URL request key string g_hud_uuid = ""; // access UUID from Core (WEB_RELAY_INIT) integer g_ready = FALSE; // TRUE when both URL and UUID are known // ============================================================ // Request a fresh simulator URL // ============================================================ requestURL() { if (g_sim_url != "") { llReleaseURL(g_sim_url); g_sim_url = ""; } g_ready = FALSE; g_url_req = llRequestURL(); } // ============================================================ // Broadcast our URL to Core so it goes into the next heartbeat // ============================================================ broadcastURL() { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_RELAY_URL|" + g_sim_url, NULL_KEY); } // ============================================================ // Send a JSON response to an HTTP caller // ============================================================ respond(key req, integer status, string jsonBody) { llHTTPResponse(req, status, jsonBody); } // ============================================================ // Validate and dispatch a command from PHP // ============================================================ dispatch(key req, string body) { string cmd = llJsonGetValue(body, ["cmd"]); string uuid = llJsonGetValue(body, ["access_uuid"]); string value = llJsonGetValue(body, ["value"]); // Reject if JSON was unparseable if (cmd == JSON_INVALID || uuid == JSON_INVALID) { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid JSON body"])); return; } // Validate access UUID against the one Core gave us if (g_hud_uuid == "" || uuid != g_hud_uuid) { respond(req, 403, llList2Json(JSON_OBJECT, ["status", "error", "message", "Access denied"])); return; } if (cmd == "wet") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|wet", NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else if (cmd == "change") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|change", NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else if (cmd == "change_lock") { if (value == "0" || value == "1" || value == "2") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|change_lock|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for change_lock"])); } } else if (cmd == "hud_lock") { if (value == "0" || value == "1") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|hud_lock|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for hud_lock"])); } } else if (cmd == "autowet") { // PHP sends minutes; Core expects seconds integer mins = (integer)value; if (mins >= 0) { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|autowet|" + (string)(mins * 60), NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for autowet"])); } } else if (cmd == "msg") { if (value != "" && value != JSON_INVALID) { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|msg|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Empty message"])); } } else if (cmd == "announce_full") { if (value == "0" || value == "1" || value == "2") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|announce_full|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for announce_full"])); } } else if (cmd == "announce_leak") { if (value == "0" || value == "1" || value == "2") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|announce_leak|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for announce_leak"])); } } else if (cmd == "announce_change") { if (value == "0" || value == "1" || value == "2") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|announce_change|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for announce_change"])); } } else if (cmd == "notify_wearer") { if (value == "0" || value == "1") { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|notify_wearer|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Invalid value for notify_wearer"])); } } else if (cmd == "owner_title") { if (value != "" && value != JSON_INVALID) { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|owner_title|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Empty owner title"])); } } else if (cmd == "state_names") { if (value != "" && value != JSON_INVALID) { llMessageLinked(LINK_ROOT, LM_CHANNEL, "WEB_CMD|state_names|" + value, NULL_KEY); respond(req, 200, llList2Json(JSON_OBJECT, ["status", "ok"])); } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Empty state names"])); } } else { respond(req, 400, llList2Json(JSON_OBJECT, ["status", "error", "message", "Unknown command: " + cmd])); } } // ============================================================ // Default state // ============================================================ default { state_entry() { g_sim_url = ""; g_hud_uuid = ""; g_ready = FALSE; requestURL(); } on_rez(integer param) { llResetScript(); } attach(key id) { if (id != NULL_KEY) llResetScript(); } changed(integer change) { if (change & CHANGED_REGION) { llOwnerSay("[Diaper/WebRelay] Region changed — re-requesting simulator URL."); requestURL(); } } http_request(key req, string method, string body) { // ---- URL grant/deny response from llRequestURL() ---- if (req == g_url_req) { if (method == URL_REQUEST_GRANTED) { g_sim_url = body; g_url_req = NULL_KEY; g_ready = (g_hud_uuid != "" && g_sim_url != ""); if (g_ready) broadcastURL(); llOwnerSay("[Diaper/WebRelay] Simulator URL ready."); } else if (method == URL_REQUEST_DENIED) { g_url_req = NULL_KEY; llOwnerSay("[Diaper/WebRelay] URL request denied: " + body + ". Web commands will not work until a URL is available."); } return; } // ---- Incoming command POST from PHP ---- if (method != "POST") { respond(req, 405, llList2Json(JSON_OBJECT, ["status", "error", "message", "Method not allowed"])); return; } if (!g_ready || g_hud_uuid == "") { respond(req, 503, llList2Json(JSON_OBJECT, ["status", "error", "message", "HUD not ready"])); return; } dispatch(req, body); } link_message(integer sender, integer num, string msg, key id) { if (num != LM_CHANNEL) return; list parts = llParseString2List(msg, ["|"], []); string cmd = llList2String(parts, 0); // Core sends WEB_RELAY_INIT once it has the HUD UUID from Persistence if (cmd == "WEB_RELAY_INIT") { g_hud_uuid = llList2String(parts, 1); g_ready = (g_hud_uuid != "" && g_sim_url != ""); if (g_ready) broadcastURL(); } } }