LSL Advanced Scripting: Communication & External Data

Master HTTP requests, API integration, JSON parsing, email notifications, and dataserver functions to connect your virtual world with external services.

Advanced Level 4-5 Hours LSL-301 The School of Creation
LSL Advanced Scripting Communication

Course Overview

Take your LSL scripting to the next level by learning how to communicate beyond the boundaries of your virtual world. This advanced course teaches you to fetch data from external APIs, send emails, parse JSON responses, and utilize the powerful dataserver for notecard reading and inventory management.

What You'll Learn

  • Making HTTP requests to external APIs
  • Handling HTTP responses and status codes
  • Parsing JSON data with llJsonGetValue()
  • Sending email notifications from scripts
  • Reading notecards asynchronously with dataserver
  • Inventory management techniques
  • Error handling for network operations
  • Real-world integration examples

1. Understanding HTTP Requests in LSL

What is HTTP and Why Use It?

HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the web. In LSL, llHTTPRequest() allows your scripts to communicate with external web services, fetch real-time data, interact with APIs, and integrate your virtual world with the broader internet.

Common Use Cases:

  • Weather Systems: Fetch real-world weather data to mirror conditions in-world
  • Currency Exchange: Get live cryptocurrency or forex rates
  • News Feeds: Display headlines from RSS feeds or news APIs
  • Social Media: Post updates or fetch content from social platforms
  • Database Integration: Store and retrieve data from external databases
  • Authentication: Verify user credentials against external systems

The llHTTPRequest() Function

Syntax: key llHTTPRequest(string url, list parameters, string body)

Parameter Description
url The web address to send the request to (must be HTTPS)
parameters List of options like HTTP method, custom headers, timeout
body Data to send with POST/PUT requests (empty string for GET)
Returns A unique key to identify this request in the response

2. Making Your First HTTP Request

Simple GET Request Example

Let's create a script that fetches a random cat fact from a public API. This demonstrates the complete workflow: sending a request, handling the response, and displaying results.

// Simple HTTP GET Request Example
// Fetches a random cat fact from an API

// Global variable to store the API URL
string g_api_url = "https://catfact.ninja/fact";

// We'll store the request ID to make sure the response we get is the one we're expecting.
// We'll store the request ID to make sure the response we get is the one we're expecting.
key g_request_id;

default
{
    state_entry()
    {
        llSetText("Click me for a cat fact!", <1,1,1>, 1.0);
    }
    
    touch_start(integer total_number)
    {
        llSetText("Fetching fact...", <1,1,0>, 1.0);
        
        // Define the parameters for our request. We are doing a "GET" request.
        list http_params = [HTTP_METHOD, "GET"];
        
        // Send the request!
        // The body is empty because we are only getting data, not sending it.
        // We store the returned key in our global variable.
        g_request_id = llHTTPRequest(g_api_url, http_params, "");
    }
    
    // This event fires when the web server responds.
    http_response(key request_id, integer status, list metadata, string body)
    {
        // First, check if this response matches our request.
        // This is important if your script makes multiple different requests.
        if (request_id == g_request_id)
        {
            // Next, check if the request was successful. Status 200 means OK.
            if (status == 200)
            {
                // Success! The 'body' variable now contains the data.
                // For this API, it's a JSON string like:
                // {"fact":"A cat's brain is more similar to a human's brain than a dog's.","length":78}
                
                llOwnerSay("✅ API Response received!");
                llOwnerSay("Raw JSON: " + body);
                
                // Display success
                llSetText("✓ Fact received! Check chat.", <0,1,0>, 1.0);
            }
            else
            {
                // The request failed! Let's report the error.
                llSetText("Error: " + (string)status, <1,0,0>, 1.0);
                llOwnerSay("❌ HTTP request failed with status: " + (string)status);
            }
        }
    }
}

How This Script Works:

  1. Initialization: The script stores the API URL and displays "Click me" text above the object
  2. Touch Trigger: When touched, it sends an HTTP GET request and stores the request ID
  3. Response Handling: The http_response event fires when the server replies
  4. Validation: We check the request ID matches and status is 200 (success)
  5. Display: The raw JSON response is shown in chat

3. Parsing JSON Data with llJsonGetValue()

Understanding JSON Format

JSON (JavaScript Object Notation) is a lightweight data format used by most modern APIs. It structures data as key-value pairs, making it easy to read and parse.

Example JSON response:

{
  "fact": "Cats can rotate their ears 180 degrees.",
  "length": 42,
  "verified": true
}

The llJsonGetValue() Function

Syntax: string llJsonGetValue(string json, list specifiers)

  • json: The raw JSON string from the API response
  • specifiers: A list acting as a "path" to the value you want (e.g., ["fact"])
  • Returns: The extracted value as a string, or JSON_INVALID if not found

Enhanced Example: Parsing and Displaying Data

// HTTP Request with JSON Parsing
// Extracts specific data from API response

string g_api_url = "https://catfact.ninja/fact";
key g_request_id;

default
{
    state_entry()
    {
        llSetText("Touch for cat fact", <1,1,1>, 1.0);
        llOwnerSay("Cat Fact Fetcher Ready!");
    }
    
    touch_start(integer total_number)
    {
        llSetText("Fetching...", <1,1,0>, 1.0);
        list http_params = [HTTP_METHOD, "GET"];
        g_request_id = llHTTPRequest(g_api_url, http_params, "");
    }
    
    http_response(key request_id, integer status, list metadata, string body)
    {
        if (request_id == g_request_id)
        {
            if (status == 200)
            {
                // Parse the JSON to extract just the "fact" field
                string fact = llJsonGetValue(body, ["fact"]);
                
                // Check if parsing was successful
                if (fact != JSON_INVALID)
                {
                    // Display the extracted fact
                    llSay(0, "🐱 Cat Fact: " + fact);
                    llSetText("Touch for another fact", <0,1,0>, 1.0);
                    
                    // Optional: Extract the length field
                    string lengthStr = llJsonGetValue(body, ["length"]);
                    integer length = (integer)lengthStr;
                    llOwnerSay("Fact length: " + (string)length + " characters");
                }
                else
                {
                    llOwnerSay("❌ Failed to parse JSON data");
                    llSetText("Parse error", <1,0,0>, 1.0);
                }
            }
            else
            {
                llOwnerSay("❌ HTTP Error: " + (string)status);
                llSetText("Request failed", <1,0,0>, 1.0);
            }
        }
    }
}

Parsing Nested JSON

For complex JSON with nested objects, use multiple specifiers:

{
  "user": {
    "name": "JohnDoe",
    "stats": {
      "level": 42
    }
  }
}
// To get the level value:
string level = llJsonGetValue(json, ["user", "stats", "level"]);
// Returns: "42"

4. Sending Email Notifications with llEmail()

Email Integration in LSL

The llEmail() function allows scripts to send email messages to real-world email addresses. This is perfect for alerts, notifications, logs, and connecting in-world events to external systems.

Syntax: llEmail(string address, string subject, string message)

Parameter Description
address The recipient's email address
subject Email subject line (max 255 characters)
message Email body content (max 4096 characters)

Example: Security Alert System

// Security Alert Email Notifier
// Sends email when unauthorized person enters area

string g_owner_email = "your-email@example.com";
key g_owner_key;

default
{
    state_entry()
    {
        g_owner_key = llGetOwner();
        llSensorRepeat("", "", AGENT, 10.0, PI, 5.0); // Scan every 5 seconds
        llOwnerSay("Security system active - Email alerts enabled");
    }
    
    sensor(integer num_detected)
    {
        integer i;
        for (i = 0; i < num_detected; i++)
        {
            key detected_key = llDetectedKey(i);
            
            // If it's not the owner, send alert
            if (detected_key != g_owner_key)
            {
                string intruder_name = llDetectedName(i);
                string location = llGetRegionName() + " " + (string)llGetPos();
                
                string subject = "âš ī¸ Security Alert - Unauthorized Entry";
                string message = "SECURITY BREACH DETECTED!\n\n"
                    + "Intruder: " + intruder_name + "\n"
                    + "Location: " + location + "\n"
                    + "Time: " + llGetTimestamp() + "\n"
                    + "Key: " + (string)detected_key;
                
                llEmail(g_owner_email, subject, message);
                llOwnerSay("âš ī¸ Email alert sent about: " + intruder_name);
                
                // Prevent spam - wait before next email
                llSleep(60.0);
            }
        }
    }
    
    no_sensor()
    {
        // Area is clear
    }
}

Combining HTTP + Email: Weather Alert System

// Weather Alert System
// Fetches weather data and emails severe weather warnings

string g_weather_api = "https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=NewYork";
string g_alert_email = "your-email@example.com";
key g_request_id;

default
{
    state_entry()
    {
        llSetText("Weather Monitor Active", <0,1,1>, 1.0);
        // Check weather every hour
        llSetTimerEvent(3600.0);
        llOwnerSay("Weather alert system initialized");
    }
    
    timer()
    {
        // Fetch current weather
        list params = [HTTP_METHOD, "GET"];
        g_request_id = llHTTPRequest(g_weather_api, params, "");
    }
    
    http_response(key request_id, integer status, list metadata, string body)
    {
        if (request_id == g_request_id && status == 200)
        {
            // Parse weather condition
            string condition = llJsonGetValue(body, ["current", "condition", "text"]);
            string temp = llJsonGetValue(body, ["current", "temp_c"]);
            
            // Check for severe conditions
            if (llSubStringIndex(llToLower(condition), "storm") != -1 ||
                llSubStringIndex(llToLower(condition), "thunder") != -1)
            {
                string subject = "âš ī¸ Severe Weather Alert";
                string message = "WEATHER WARNING\n\n"
                    + "Condition: " + condition + "\n"
                    + "Temperature: " + temp + "°C\n"
                    + "Time: " + llGetTimestamp();
                
                llEmail(g_alert_email, subject, message);
                llSay(0, "đŸŒŠī¸ Severe weather detected! Email sent.");
            }
        }
    }
}

5. The Dataserver: Reading Notecards & Inventory

What is the Dataserver?

The dataserver is LSL's mechanism for asynchronously reading data from notecards, getting inventory information, and retrieving object/avatar details without blocking script execution.

Reading Notecards Line-by-Line

Notecards are perfect for storing configuration data, dialog text, lists of names, or any text-based information. The llGetNotecardLine() function reads one line at a time.

Syntax: key llGetNotecardLine(string name, integer line)

// Notecard Reader Script
// Reads and displays all lines from a notecard

string g_notecard_name = "config";
key g_notecard_query;
integer g_line_number = 0;
list g_notecard_lines;

default
{
    state_entry()
    {
        llOwnerSay("Reading notecard: " + g_notecard_name);
        
        // Start reading from line 0
        g_notecard_query = llGetNotecardLine(g_notecard_name, g_line_number);
    }
    
    dataserver(key query_id, string data)
    {
        // Check this is our query
        if (query_id == g_notecard_query)
        {
            // Check if we've reached the end (EOF = End Of File)
            if (data != EOF)
            {
                // Store this line
                g_notecard_lines += [data];
                llOwnerSay("Line " + (string)g_line_number + ": " + data);
                
                // Read the next line
                g_line_number++;
                g_notecard_query = llGetNotecardLine(g_notecard_name, g_line_number);
            }
            else
            {
                // Finished reading all lines
                llOwnerSay("✅ Notecard read complete!");
                llOwnerSay("Total lines: " + (string)llGetListLength(g_notecard_lines));
            }
        }
    }
}

Practical Example: Configuration System

// Configuration Reader
// Reads key=value pairs from notecard

string g_config_notecard = "settings";
key g_query;
integer g_line = 0;

// Store configuration values
string g_admin_name;
integer g_max_users;
float g_timer_interval;

default
{
    state_entry()
    {
        llOwnerSay("Loading configuration...");
        g_query = llGetNotecardLine(g_config_notecard, g_line);
    }
    
    dataserver(key query_id, string data)
    {
        if (query_id == g_query)
        {
            if (data != EOF)
            {
                // Skip empty lines and comments
                data = llStringTrim(data, STRING_TRIM);
                if (data != "" && llGetSubString(data, 0, 0) != "#")
                {
                    // Parse key=value format
                    integer equals_pos = llSubStringIndex(data, "=");
                    if (equals_pos != -1)
                    {
                        string key = llStringTrim(llGetSubString(data, 0, equals_pos - 1), STRING_TRIM);
                        string value = llStringTrim(llGetSubString(data, equals_pos + 1, -1), STRING_TRIM);
                        
                        // Process configuration
                        if (key == "admin_name")
                            g_admin_name = value;
                        else if (key == "max_users")
                            g_max_users = (integer)value;
                        else if (key == "timer_interval")
                            g_timer_interval = (float)value;
                        
                        llOwnerSay("Config: " + key + " = " + value);
                    }
                }
                
                // Read next line
                g_line++;
                g_query = llGetNotecardLine(g_config_notecard, g_line);
            }
            else
            {
                llOwnerSay("✅ Configuration loaded successfully!");
                llOwnerSay("Admin: " + g_admin_name);
                llOwnerSay("Max Users: " + (string)g_max_users);
                llOwnerSay("Timer: " + (string)g_timer_interval + "s");
            }
        }
    }
}

Example notecard content (save as "settings"):

# Configuration File
# Lines starting with # are comments

admin_name=JohnDoe
max_users=50
timer_interval=30.0

Getting Inventory Count

// Inventory Counter

default
{
    state_entry()
    {
        integer texture_count = llGetInventoryNumber(INVENTORY_TEXTURE);
        integer sound_count = llGetInventoryNumber(INVENTORY_SOUND);
        integer notecard_count = llGetInventoryNumber(INVENTORY_NOTECARD);
        integer script_count = llGetInventoryNumber(INVENTORY_SCRIPT);
        
        llOwnerSay("đŸ“Ļ Inventory Report:");
        llOwnerSay("Textures: " + (string)texture_count);
        llOwnerSay("Sounds: " + (string)sound_count);
        llOwnerSay("Notecards: " + (string)notecard_count);
        llOwnerSay("Scripts: " + (string)script_count);
    }
    
    touch_start(integer num)
    {
        // List all notecards
        integer i;
        integer count = llGetInventoryNumber(INVENTORY_NOTECARD);
        llSay(0, "Notecards in inventory:");
        
        for (i = 0; i < count; i++)
        {
            string name = llGetInventoryName(INVENTORY_NOTECARD, i);
            llSay(0, (string)(i+1) + ". " + name);
        }
    }
}

6. Advanced Techniques & Best Practices

Error Handling & Timeouts

Production scripts should handle network failures gracefully:

// Robust HTTP Request with Timeout

string g_api_url = "https://api.example.com/data";
key g_request_id;
float g_request_time;
float g_timeout = 30.0; // 30 second timeout

default
{
    state_entry()
    {
        llSetTimerEvent(1.0); // Check every second
    }
    
    touch_start(integer num)
    {
        list params = [
            HTTP_METHOD, "GET",
            HTTP_CUSTOM_HEADER, "User-Agent", "LSL-Script/1.0"
        ];
        
        g_request_id = llHTTPRequest(g_api_url, params, "");
        g_request_time = llGetTime();
        llOwnerSay("Request sent, waiting for response...");
    }
    
    timer()
    {
        // Check if request has timed out
        if (g_request_id != NULL_KEY)
        {
            if ((llGetTime() - g_request_time) > g_timeout)
            {
                llOwnerSay("❌ Request timed out after " + (string)g_timeout + " seconds");
                g_request_id = NULL_KEY; // Reset
            }
        }
    }
    
    http_response(key request_id, integer status, list metadata, string body)
    {
        if (request_id == g_request_id)
        {
            g_request_id = NULL_KEY; // Clear timeout check
            
            if (status == 200)
            {
                llOwnerSay("✅ Success: " + body);
            }
            else if (status == 404)
            {
                llOwnerSay("❌ Error 404: Resource not found");
            }
            else if (status == 500)
            {
                llOwnerSay("❌ Error 500: Server error");
            }
            else
            {
                llOwnerSay("❌ HTTP Error: " + (string)status);
            }
        }
    }
}

POST Requests with Data

// Sending data to an API

string g_api_url = "https://api.example.com/submit";
key g_request_id;

default
{
    touch_start(integer num)
    {
        // Prepare JSON data to send
        string json_data = llList2Json(JSON_OBJECT, [
            "username", llKey2Name(llDetectedKey(0)),
            "action", "login",
            "timestamp", llGetTimestamp()
        ]);
        
        list params = [
            HTTP_METHOD, "POST",
            HTTP_MIMETYPE, "application/json",
            HTTP_CUSTOM_HEADER, "Authorization", "Bearer YOUR_TOKEN_HERE"
        ];
        
        g_request_id = llHTTPRequest(g_api_url, params, json_data);
        llOwnerSay("Sending data: " + json_data);
    }
    
    http_response(key request_id, integer status, list metadata, string body)
    {
        if (request_id == g_request_id)
        {
            if (status == 200 || status == 201)
            {
                llOwnerSay("✅ Data submitted successfully!");
                llOwnerSay("Response: " + body);
            }
            else
            {
                llOwnerSay("❌ Submit failed: " + (string)status);
            }
        }
    }
}

Performance Tips

  • Cache API responses instead of making repeated requests for the same data
  • Use timers to limit request frequency (avoid API rate limits)
  • Parse JSON efficiently - extract only the data you need
  • Handle errors gracefully - don't crash on network failures
  • Log important events but avoid spam
  • Test with public APIs before moving to production systems

7. Hands-On Projects

Challenge Yourself!

Project 1: Cryptocurrency Price Ticker

Create a floating text display that shows live Bitcoin prices. Use the CoinGecko API to fetch prices every 5 minutes.

  • Fetch data from: https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd
  • Parse the JSON response
  • Display price with llSetText()
  • Update automatically with llSetTimerEvent()
Project 2: Guest Book System

Build a visitor registration system that stores names and timestamps in a notecard and emails you a summary daily.

  • Touch to register visitor name and time
  • Write data to a notecard using llEmail() trick
  • Read back the complete guest list
  • Send daily email summary to owner
Project 3: Weather-Responsive Environment

Create an object that changes appearance based on real-world weather using the WeatherAPI service.

  • Fetch weather for a specific city
  • Change color based on conditions (blue=clear, gray=cloudy, dark=rain)
  • Play appropriate sounds
  • Display temperature and condition text
Project 4: Configuration-Driven Dialog System

Build a multi-page dialog menu system where all text and options are loaded from a notecard configuration file.

  • Read menu structure from notecard
  • Parse different menu pages
  • Display with llDialog()
  • Support nested submenus

🎓 Course Summary & Next Steps

What You've Mastered

Technical Skills

  • Making HTTP GET and POST requests
  • Handling API responses and status codes
  • Parsing JSON data structures
  • Sending email notifications
  • Reading notecards asynchronously
  • Managing inventory programmatically
  • Error handling and timeouts

Practical Applications

  • Integrating external APIs and services
  • Building data-driven systems
  • Creating notification mechanisms
  • Configuration management
  • Real-world data integration
  • Automated monitoring systems
  • Dynamic content generation

Continue Your Learning Journey

Now that you've mastered advanced communication, consider exploring:

  • LSL Expert Level: Optimization, memory management, and performance tuning
  • Database Integration: Connect to MySQL, PostgreSQL, or MongoDB via custom APIs
  • Permissions & Security: Advanced access control and encryption
  • Multi-Script Communication: Link messages and cross-script coordination
  • Web Services: Building your own API endpoints for LSL consumption

Ready to Put Your Skills to Work?

Join Alife Virtual and start building advanced interactive experiences!

Continue Your Education