Peril_Dice/Update_Receiver.lsl
RebeccaNod1 342943eaf0 feat: v2.8.8 - GitHub Integration & External Updater System
- Replace Update_Checker.lsl with enhanced Update_Receiver.lsl
- Add Peril_Dice_Updater.lsl external updater box
- Integrate 'Check for Updates' into Admin Menu → Troubleshooting
- Professional dialog system with Install Update button
- Automatic updater box detection within 96m range
- Secure PIN-based script installation via llRemoteLoadScriptPin
- Manual JSON parsing for LSL 2048-character limit workaround
- Maintain backward compatibility with chat commands
- Update manifest to v2.8.8 with Update_Receiver.lsl entry

New Features:
- One-click automatic update installation
- Professional user experience matching commercial SL products
- External updater architecture for easy distribution
- Enhanced GitHub API integration with better error handling
2025-10-02 12:43:21 -05:00

443 lines
No EOL
17 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ====================================================================
// Update Receiver - GitHub Integration + External Updater Coordination
// ====================================================================
// VERIFIED LINKSET PLACEMENT: Link 1 (Root Prim) "Peril Dice Controller V2"
// Replaces Update_Checker.lsl with external updater box coordination
//
// This script:
// 1. Checks GitHub for new releases (same as before)
// 2. Scans for nearby Peril_Dice_Updater boxes
// 3. Coordinates automatic script installation via external updater
// 4. Provides professional one-click update experience
// ====================================================================
string CURRENT_VERSION = "2.8.7";
string GITHUB_API_URL = "https://api.github.com/repos/RebeccaNod1/Peril/releases/latest";
// External updater communication
integer UPDATER_CHANNEL = -7723847; // Must match Peril_Dice_Updater.lsl
integer SENSOR_RANGE = 96; // meters to scan for updater boxes
list nearbyUpdaters = [];
key selectedUpdater = NULL_KEY;
// Update process state
integer updateInProgress = FALSE;
integer updatePin = 0;
string availableVersion = "";
// HTTP operation tracking
key currentHttpRequest = NULL_KEY;
string currentOperation = "";
// Message constants - following existing Peril Dice patterns
integer MSG_ADMIN_MENU_RESPONSE = 888;
integer MSG_UPDATE_CHECK_REQUEST = 2100;
integer MSG_TOGGLE_VERBOSE_LOGS = 9999;
// Update dialog constants
integer UPDATE_DIALOG_CHANNEL = -77401; // Unique channel for update dialogs
integer updateDialogHandle = -1;
key pendingUpdateUser = NULL_KEY;
// Internal verbose logging
integer VERBOSE_LOGGING = FALSE;
// Memory usage reporting
reportMemoryUsage(string scriptName) {
integer memory = llGetUsedMemory();
integer freeMemory = llGetFreeMemory();
float memoryPercent = (float)memory / (memory + freeMemory) * 100.0;
llOwnerSay("📊 " + scriptName + ": " + (string)memory + " bytes used (" +
llGetSubString((string)memoryPercent, 0, 4) + "% memory)");
}
// Version comparison (handles "v2.8.6" and "2.8.6" formats)
integer isNewerVersion(string latestVersion, string currentVersion) {
// Remove 'v' prefix if present
if (llGetSubString(latestVersion, 0, 0) == "v") {
latestVersion = llGetSubString(latestVersion, 1, -1);
}
if (llGetSubString(currentVersion, 0, 0) == "v") {
currentVersion = llGetSubString(currentVersion, 1, -1);
}
return (latestVersion != currentVersion);
}
// Manual JSON parsing for LSL 2048-char limit
string extractJsonValue(string json, string keyName) {
string quote = "\"";
string searchPattern = quote + keyName + quote + ":" + quote;
integer start = llSubStringIndex(json, searchPattern);
if (start == -1) return "";
integer valueStart = start + llStringLength(searchPattern);
integer valueEnd = llSubStringIndex(llGetSubString(json, valueStart, -1), quote);
if (valueEnd == -1) return "";
return llGetSubString(json, valueStart, valueStart + valueEnd - 1);
}
// Scan for nearby updater boxes
scanForUpdaters() {
nearbyUpdaters = [];
llOwnerSay("🔍 Scanning for GitHub updater boxes within " + (string)SENSOR_RANGE + "m...");
llSensor("", NULL_KEY, SCRIPTED, SENSOR_RANGE, PI);
}
// Check GitHub for updates
checkForUpdates() {
currentOperation = "version_check";
llOwnerSay("🔍 Checking GitHub for Peril Dice updates...");
if (VERBOSE_LOGGING) {
llOwnerSay("📊 GitHub API: " + GITHUB_API_URL);
}
currentHttpRequest = llHTTPRequest(GITHUB_API_URL, [
HTTP_METHOD, "GET"
], "");
}
// Start update installation process
startUpdateInstallation() {
if (selectedUpdater == NULL_KEY) {
llOwnerSay("❌ No updater box selected. Please rez a Peril Dice Updater nearby.");
return;
}
// Generate unique access pin
updatePin = (integer)(llFrand(2000000000)) + 1000000000;
// Set remote script access pin
llSetRemoteScriptAccessPin(updatePin);
updateInProgress = TRUE;
llOwnerSay("🔄 Starting automatic update installation...");
llOwnerSay("📊 Requesting " + availableVersion + " from GitHub updater box...");
// Request update from external updater
llRegionSayTo(selectedUpdater, UPDATER_CHANNEL,
"UPDATE_REQUEST|" + CURRENT_VERSION + "|" + (string)updatePin);
// Set timeout for update process
llSetTimerEvent(300.0); // 5 minute timeout
}
// Complete update process
completeUpdate(string newVersion) {
updateInProgress = FALSE;
llSetRemoteScriptAccessPin(0); // Clear access pin
llSetTimerEvent(0.0); // Clear timer
llOwnerSay("🎉 Update installation complete!");
llOwnerSay("✅ Peril Dice is now running version " + newVersion);
llOwnerSay("💡 You can now delete the updater box");
// Update our version number
// Note: In practice, this script itself would have been updated
// CURRENT_VERSION = newVersion;
}
// Handle update failure
handleUpdateFailure(string error) {
updateInProgress = FALSE;
llSetRemoteScriptAccessPin(0); // Clear access pin
llSetTimerEvent(0.0); // Clear timer
llOwnerSay("❌ Update installation failed: " + error);
llOwnerSay("💡 You can try again or delete the updater box");
}
// Show update installation dialog
showInstallUpdateDialog(key userId) {
if (availableVersion == "") {
llOwnerSay("❌ No update available to install");
return;
}
if (llGetListLength(nearbyUpdaters) == 0) {
llOwnerSay("📦 No updater boxes found. Scanning...");
scanForUpdaters();
return;
}
pendingUpdateUser = userId;
// Close previous dialog listener
if (updateDialogHandle != -1) {
llListenRemove(updateDialogHandle);
}
// Start new dialog listener
updateDialogHandle = llListen(UPDATE_DIALOG_CHANNEL, "", userId, "");
string dialogText = "🆕 NEW VERSION AVAILABLE!\n\n";
dialogText += "📊 Current Version: v" + CURRENT_VERSION + "\n";
dialogText += "✨ Available Version: " + availableVersion + "\n\n";
dialogText += "🔄 Ready to install " + availableVersion + " automatically?\n";
dialogText += "📦 Found " + (string)llGetListLength(nearbyUpdaters) + " updater box(es) nearby";
list options = ["🚀 Install Update", "❌ Cancel", "📦 Scan Again"];
llDialog(userId, dialogText, options, UPDATE_DIALOG_CHANNEL);
}
// Show help commands
showUpdateCommands() {
llOwnerSay("=== 🔄 PERIL DICE UPDATE SYSTEM ===");
llOwnerSay("📱 Current Version: v" + CURRENT_VERSION);
llOwnerSay("🌐 Repository: github.com/RebeccaNod1/Peril");
llOwnerSay("📍 Location: Link 1 (Root Prim) - VERIFIED 84-prim structure");
llOwnerSay("");
llOwnerSay("🔄 NEW: Automatic Update System!");
llOwnerSay("1️⃣ Rez a 'Peril Dice Updater' box nearby");
llOwnerSay("2️⃣ Touch this game → Admin Menu → 'Check for Updates'");
llOwnerSay("3️⃣ Click 'Install Update' for one-click installation");
llOwnerSay("4️⃣ Delete updater box when complete");
llOwnerSay("");
llOwnerSay("💬 Manual commands still available:");
llOwnerSay(" /1 check - Check GitHub for updates");
llOwnerSay(" /1 scan - Scan for nearby updater boxes");
llOwnerSay(" /1 help - Show these commands");
}
default {
state_entry() {
reportMemoryUsage("Update Receiver");
llOwnerSay("🔄 Update Receiver ready - GitHub + External Updater integration");
llOwnerSay("📍 Location: Link 1 (\"Peril Dice Controller V2\") - VERIFIED structure");
llOwnerSay("💬 Say '/1 help' for commands or use Owner Menu → Troubleshooting");
llListen(1, "", llGetOwner(), "");
llListen(UPDATER_CHANNEL, "", NULL_KEY, "");
}
on_rez(integer start_param) {
// Reset state on rez
updateInProgress = FALSE;
updatePin = 0;
availableVersion = "";
nearbyUpdaters = [];
selectedUpdater = NULL_KEY;
currentOperation = "";
currentHttpRequest = NULL_KEY;
llResetScript();
}
listen(integer channel, string name, key id, string message) {
if (channel == 1 && id == llGetOwner()) {
// Owner chat commands
string msg = llToLower(llStringTrim(message, STRING_TRIM));
if (msg == "help") {
showUpdateCommands();
}
else if (msg == "check") {
checkForUpdates();
}
else if (msg == "scan") {
scanForUpdaters();
}
else {
llOwnerSay("❓ Unknown command. Say '/1 help' for available commands");
}
}
else if (channel == UPDATE_DIALOG_CHANNEL) {
// Handle update dialog responses
if (id != pendingUpdateUser) return; // Only respond to the user who triggered the dialog
if (message == "🚀 Install Update") {
llOwnerSay("🚀 Starting automatic update installation...");
startUpdateInstallation();
}
else if (message == "📦 Scan Again") {
llOwnerSay("🔍 Scanning for GitHub updater boxes...");
scanForUpdaters();
// Dialog will be shown again when scan finds updaters
}
else if (message == "❌ Cancel") {
llOwnerSay("❌ Update installation cancelled");
}
// Clean up dialog listener and pending user
if (updateDialogHandle != -1) {
llListenRemove(updateDialogHandle);
updateDialogHandle = -1;
}
pendingUpdateUser = NULL_KEY;
}
else if (channel == UPDATER_CHANNEL) {
// Communication with updater boxes
list parts = llParseString2List(message, ["|"], []);
string command = llList2String(parts, 0);
if (command == "UPDATER_AVAILABLE") {
string updaterVersion = llList2String(parts, 1);
llOwnerSay("📦 Found GitHub updater: " + name + " v" + updaterVersion);
// Add to list if not already there
if (llListFindList(nearbyUpdaters, [id]) == -1) {
nearbyUpdaters += [id];
}
// Auto-select if we don't have one selected
if (selectedUpdater == NULL_KEY) {
selectedUpdater = id;
llOwnerSay("✅ Selected updater: " + name);
}
}
else if (command == "UPDATE_STARTING") {
string version = llList2String(parts, 1);
llOwnerSay("🚀 Updater is installing " + version + " from GitHub...");
}
else if (command == "UPDATE_COMPLETE") {
string version = llList2String(parts, 1);
completeUpdate(version);
}
else if (command == "UPDATE_FAILED") {
string error = llList2String(parts, 1);
handleUpdateFailure(error);
}
else if (command == "UPDATE_BUSY") {
llOwnerSay("⏳ Updater is busy with another installation");
}
}
}
link_message(integer sender_num, integer num, string str, key id) {
// Handle verbose logging toggle
if (num == MSG_TOGGLE_VERBOSE_LOGS) {
VERBOSE_LOGGING = !VERBOSE_LOGGING;
if (VERBOSE_LOGGING) {
llOwnerSay("🔊 [UpdateReceiver] Verbose logging ENABLED");
} else {
llOwnerSay("🔊 [UpdateReceiver] Verbose logging DISABLED");
}
return;
}
// Handle update check requests from admin menu
if (num == MSG_UPDATE_CHECK_REQUEST) {
if (VERBOSE_LOGGING) {
llOwnerSay("🔄 [UpdateReceiver] Received update check request from admin menu");
}
// Store the requesting user for potential dialog
if (availableVersion != "") {
// We already know there's an update - show install dialog immediately
showInstallUpdateDialog(id);
} else {
// Check for updates first
checkForUpdates();
// We'll show the dialog in the HTTP response if an update is found
pendingUpdateUser = id;
}
return;
}
// Respond to admin menu status queries
if (num == MSG_ADMIN_MENU_RESPONSE && str == "update_status") {
llMessageLinked(LINK_SET, MSG_ADMIN_MENU_RESPONSE,
"update_version|v" + CURRENT_VERSION, id);
return;
}
}
sensor(integer num_detected) {
// Found scripted objects - check if any are updater boxes
nearbyUpdaters = [];
integer i;
for (i = 0; i < num_detected; i++) {
key detected = llDetectedKey(i);
string detectedName = llDetectedName(i);
// Ping to see if it's an updater box
llRegionSayTo(detected, UPDATER_CHANNEL, "PING_UPDATER");
}
// Wait for responses
llSetTimerEvent(3.0);
}
no_sensor() {
llOwnerSay("📭 No GitHub updater boxes found within " + (string)SENSOR_RANGE + "m");
llOwnerSay("💡 Please rez a 'Peril Dice Updater' box nearby to enable automatic updates");
}
timer() {
if (updateInProgress) {
// Update timeout
llOwnerSay("⏰ Update installation timed out");
handleUpdateFailure("Installation timeout - updater may have failed");
} else {
// Sensor response timeout
if (llGetListLength(nearbyUpdaters) == 0) {
llOwnerSay("📭 No GitHub updater boxes responded");
llOwnerSay("💡 Please rez a 'Peril Dice Updater' box nearby");
} else {
llOwnerSay("✅ Found " + (string)llGetListLength(nearbyUpdaters) + " GitHub updater box(es)");
}
}
llSetTimerEvent(0.0);
}
http_response(key request_id, integer status, list metadata, string body) {
if (request_id != currentHttpRequest) return;
currentHttpRequest = NULL_KEY;
if (currentOperation == "version_check") {
if (status == 200) {
if (VERBOSE_LOGGING) {
llOwnerSay("📊 GitHub response: " + (string)llStringLength(body) + " chars (LSL limit: 2048)");
}
// Extract version using manual parsing
string latestVersion = extractJsonValue(body, "tag_name");
string htmlUrl = extractJsonValue(body, "html_url");
string publishedAt = extractJsonValue(body, "published_at");
if (latestVersion == "") {
llOwnerSay("❌ Could not extract version from GitHub response");
return;
}
llOwnerSay("=== 🔍 UPDATE CHECK RESULTS ===");
llOwnerSay("📊 Current Version: v" + CURRENT_VERSION);
llOwnerSay("✨ Latest GitHub Release: " + latestVersion);
if (isNewerVersion(latestVersion, CURRENT_VERSION)) {
availableVersion = latestVersion;
llOwnerSay("🆕 NEW VERSION AVAILABLE!");
llOwnerSay("📅 Published: " + llGetSubString(publishedAt, 0, 9));
llOwnerSay("🌐 View Release: " + htmlUrl);
llOwnerSay("");
// Check for updater boxes
if (llGetListLength(nearbyUpdaters) == 0) {
llOwnerSay("📦 Scanning for GitHub updater boxes...");
scanForUpdaters();
} else {
llOwnerSay("✅ GitHub updater box ready!");
llOwnerSay("🚀 Click 'Install Update' to automatically install " + latestVersion);
}
// Show install dialog if request came from admin menu
if (pendingUpdateUser != NULL_KEY) {
showInstallUpdateDialog(pendingUpdateUser);
pendingUpdateUser = NULL_KEY;
}
} else {
llOwnerSay("✅ You have the latest version!");
llOwnerSay("📦 GitHub updater system is ready for future updates");
pendingUpdateUser = NULL_KEY; // Clear pending user
}
} else {
llOwnerSay("❌ Update check failed (HTTP " + (string)status + ")");
if (status == 403) {
llOwnerSay(" ⏳ GitHub rate limited - try again in a few minutes");
}
}
}
currentOperation = "";
}
}