Command Tutorial

Corrade uses key-value data functions and syntax which is three-way compatible with:

  • in-world object descriptions, an entire command string such as:
command=invite&group=My Group&firstname=Good&password=mypassword&lastname=Day

provided its length does not exceed 127 bytes can be stored persistently in the description of an object (a demonstration of a hard-drive implementation in Second Life can be found on the PrimFS™ and PrimDrive™ page).

  • HTTP POST requests, it is possible for a primitive to listen to POST requests and pipe them directly to Corrade. That way you can effectively manage a group via a web-interface.
  • LSL scripts, optimised plug-in accessor and setter functions for key-value data are provided on the key-value data page.

This maintains a standard across scripted agents, in-world LSL scripts and HTTP requests such that the serialisation and deserialisation process of commands is not needed.

Provided that we are not in the group My Group and that My Group is set to open enrollment, and that it is defined in the configuration, the group My Group can be joined:

        llInstantMessage(CORRADE, wasKeyValueEncode(
            [
                "command", "join",
                "group", GROUP,
                "password", PASSWORD
            ])
        );

Since names are used, a command can be sent to Corrade in order to make an agent an Officer in a group (provided that the Officer role exists in MyGroup):

        llInstantMessage(CORRADE, wasKeyValueEncode(
            [
                "command", "addtorole",
                "group", GROUP,
                "password", PASSWORD,
                "firstname", "Olon",
                "lastname", "Johnson",
                "role", "Officers"
            ])
        );

That command will make Olon Johnson to be part of the Officers role.

Properly Escaping Keys and Values

Keys and values sent to Corrade should be escaped in order to avoid clashes with the & and = characters. Corrade will internally unescape the keys and values and process the command. If a callback is supplied, the result will be sent to the callback URL as escaped keys and values.

When the script receives the result, in order to properly retrieve the value of a key, the following steps should be performed in order:

  1. Escape the key whose value should be retrieved.
  2. Feed the escaped key to the wasKeyValueGet function or similar functions in the scripting language that has to processes Corrade's return.
  3. The wasKeyValueGet function will return an escaped value.
  4. Unescape the value.

This roughly translates in LSL to:

http_request(key id, string method, string body) {
 
//...
 
string value = wasURLUnescape(
    wasKeyValueGet(
        wasURLEscape("key"),
        body
    )
);
 
//...
 
}

The reason why values should be retrieved by following this procedure is due to the fact that returned keys and values may contain the & or = characters, such that escaping the entire data string in one swoop with wasURLUnescape (and others, depending on the language) will result in wasKeyValueGet (and others, depending on the language) being unable to retrieve the keys and values.

Here is an counter-example:

succeeded=True&result=Tom%26Jerry

If we were to use wasURLUnescape directly on that string, we would obtain:

succeeded=True&result=Tom&Jerry

which is a broken key-value data string. If we retrieve result, we would retrieve Tom as the value instead of Tom&Jerry.

This is why we have to first extract the key result:

result=Tom%26Jerry

and only then feed the escaped value to wasURLUnescape which will produce Tom&Jerry.

Callbacks

Callbacks are meant to provide feedback to LSL scripts so they can determine whether a command was successful or not. Suppose that we wanted to invite Olon Johnson to our group My Group but we would like to know whether the command succeeded and an invite has been sent or not. Because clients cannot speak to objects in-world, we need to send the feedback over HTTP. To do so, we first grab an URL using llRequestURL:

string URL = "";
 
default {
    state_entry() {
        llRequestURL();
    }
    http_request(key id, string method, string body) {
        if(method != URL_REQUEST_GRANTED) {
          llInstantMessage(llGetOwner(), "I cannot get any more URLs");
          return;
        }
        URL = body;
        state invite;
    }
}

this will grab an URL that we can use with Corrade and switch into the invite state. In the invite state, we use a touch_start event to send invites to agents touching the primitive:

state invite {
    touch_start(integer num) {
        list name = llParseString2List(llDetectedName(0), [" "], []);
        llInstantMessage(CORRADE, wasKeyValueEncode(
            [
                "command", "invite",
                "group", wasURLEscape(GROUP),
                "password", wasURLEscape(PASSWORD),
                "firstname", llList2String(name, 0),
                "lastname", llList2String(name, 1),
                "callback", wasURLEscape(URL)
            ])
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "");
        llOwnerSay(wasKeyValueGet("command", body) + " returned " + wasKeyValueGet("success", body));
    }
}

Notice the callback parameters in llInstantMessage, it is set to the URL we had previously obtained from the simulator. Now, Corrade will attempt to invite the agent touching the primitive and will send a string containing various information to the script in-world.

That information is then processed in http_request, notably the command and success parameters are extracted and sent to the owner using llOwnerSay.

It is important to remember to send a confirmation back to Corrade using llHTTPResponse or the message will be delayed.

Afterburn

Any key-value pairs that are not known to Corrade are passed through. This is helpful, if you want to pass data through callbacks to a web-server and to retrieve some script-generated values.

For example, an inviter script could include the following message sent to Corrade in order to invite an user on touch:

    touch_start(integer num) {
        list name = llParseString2List(llDetectedName(num), [" "], []);
        llInstantMessage(CORRADE, wasKeyValueEncode(
            [
                "command", "invite",
                "group", GROUP,
                "password", PASSWORD,
                "firstname", llList2String(name, 0),
                "lastname", llList2String(name, 1),
                "location", llGetRegionName(), // will get passed through corrade as location=region and sent to URL
                "callback", URL
 
            ])
        );
    }

Corrade will recognize all keys, except:

                "location", llGetRegionName(), // will get passed through corrade as location=region and sent to URL

Here is some sample of the data that gets sent to URL via POST:

group=[Wizardry and Steamworks]:Support&agentuuid=f0b39332-7a04-11e3-b9ed-dba899794f13&fromagentname=Milk Carton&success=True&errormessage=invited&errorcode=12&scriptcommand=invite&fromobjectuuid=abeb68b5-dc92-084e-7040-47c432538b8&location=SUNO Regents

You can then easily retrieve this key, for example with PHP:

if (isset($_POST['location'])) {
    // key was set, retrieve it
    $location = $_POST['location'];
}

LSL Script Interaction Overview

                                      string URL = "";

                                      default {
                                          state_entry() {
                                              llRequestURL();  // llRequestSecureURL()
                                          }
                                          http_request(key id, string method, string body) {
                                              if(method != URL_REQUEST_GRANTED) return;
                                              URL = body;                                                  ---+
                                              state main;                                                     |
                                          }                                                                   |
                                      }                                                                       |
                                                                                                              |
                                      state main {                                                            |
                                          touch_start(integer num) {                                          |
                                 +-           llInstantMessage(CORRADE, wasKeyValueEncode(                    |
                                 |                [                                                           |
                                 |                    "command", "changeprimitivelink",                       |
                                 |                    "group", wasURLEscape(GROUP),                           |
                                 |                    "password", wasURLEscape(PASSWORD),                     |  URL for
                                 |                    "action", "link",                                       |   CALLBACK
                                 |                    "item", wasURLEscape(                                   |
                     COMMAND     |                        wasListToCSV(                                       |
             +-------------------+                            [                                               |
             |                   |                                "eabeef26-60f2-9eca-b7a4-0bea996aaa04",     |
             |                   |                                "7b1947d5-4d10-9e3f-f5a6-6df60db0d9dd"      |
             |                   |                            ]                                               |
             v                   |                        )                                                   |
                                 |                    ),                                                      |
        _..--=--..._             |                    "callback", wasURLEscape(URL)                        <--+
     .-'            '-.  .-.     |                ])
    /.'              '.\/  /     |            );
   |=-     Corrade    -=| (      +-       }
    \'.              .'/\  \
     '-.,_____ _____.-'  '-'
           [_____]=8
                \
                  Good day!

             v
             |       CALLBACK
             +-------------------------------------------------------+---------------+
                   COMMAND RESULT                                    | POST          | COMMAND RESULT
                                                                     v               v
                                           http_request(key id, string method, string body) {
                                               llHTTPResponse(id, 200, "Ok");
                                               llOwnerSay(wasURLUnescape(body));
                                           }
                                       }

Using JSON

The latest Corrade continuous supports JSON as a scripting language meaning that instead of using key-value pairs, JSON can be used to deliver commands, retrieve the callback results as well as receive notifications in JSON format. Habitually, JSON support in LSL is non-RFC compliant, slow and generally lacking very straight-forward features: most of which are limitations of the underlying LSL language that is not an object-oriented language such that all JSON operations must be boxed from-and-to strings or lists. Furthermore, as explained in the scripting considerations, JSON carries way too much syntactic sugar that just does not fit the profile of scripts spanning just 64KB as well as receiving data via HTTP which is limited to 2KiB.

Nevertheless, Corrade has by far exceeded the constraints of LSL such that most serious usage of Corrade usually involves some external scripting and interfacing with Corrade directly. Many languages have native support for JSON such that Corrade does implement JSON as a possible scripting language. Picking the scripting language can be done from Nucleus and applies globally to the entire bot such that once JSON is picked as a language, Corrade will not understand key-value pairs and will expect JSON in order to be controlled.

Contrasted with the the structural semantics of Corrade that use key-value pairs as a first order structure and CSV as a sub-structure, when Corrade uses JSON, the encoding is straight-forward such that key-value pairs become JSON key-value pairs and CSV becomes standard JSON arrays.

For example, the following command in the WAS scripting language:

command=getselfdata&group=[Wizardry and Steamworks]:Support&password=mypassword&data=Health,Velocity&callback=http://sim9851.agni.lindenlab.com:12046/cap/cbd01556-31c2-4387-9d10-eb770cfc7525

is equivalent to the following command in JSON format:

{ "command": "getselfdata", "password": "mypassword", "group": "[Wizardry and Steamworks]:Support", "data": ["Health", "Velocity"], "callback": "http://sim9851.agni.lindenlab.com:12046/cap/cbd01556-31c2-4387-9d10-eb770cfc7525" }

Similarly, if the result sent to the callback has the following shape when WAS is selected as the scripting language:

command=getselfdata&time=2020-04-09T05:26:42.621086Z&data=Health,100,Velocity,"<0, 0, 0>"&success=True

then the equivalent when JSON is selected will be:

{"command":"getselfdata","time":"2020-04-09T05:26:42.621086Z","data":["Health","100","Velocity","\u003c0, 0, 0\u003e"],"success":"True"}

Note that it is imperative to use the wasKeyValue- and wasList- family of functions when using the WAS scripting language to read notifications, callbacks and to create commands for Corrade.

When using JSON to communicate with Corrade, either use the llJson- family of functions or consult the programming language API that is used to interface with Corrade. As mentioned previously, one of the problems with the LSL JSON functions is that the LSL JSON implementation is not RFC 4627 compliant, one consequence thereof being that JSON escape codes are not properly interpreted. Quite so, given the previous answer to the callback URL:

{"command":"getselfdata","time":"2020-04-09T05:26:42.621086Z","data":["Health","100","Velocity","\u003c0, 0, 0\u003e"],"success":"True"}

and then using llJson2List to retrieve the value of the data key:

        string json = "{\"command\":\"getselfdata\",\"time\":\"2020-04-09T04:38:03.403392Z\",\"data\":[\"Health\",\"100\",\"Velocity\",\"\u003c0, 0, 0\u003e\"],\"success\":\"True\"}";
        string v = llJsonGetValue(json, ["data"] );
 
        llOwnerSay(v);

will yield:

["Health","100","Velocity","u003c0, 0, 0u003e"]

with the smaller than and greater than signs being bungled by LSL. Nevertheless a proper JSON parser, in other languages, that is RFC compliant, will recognize the escape sequences and replace them properly when the JSON string is parsed. There is no clean way around the JSON issue in LSL such that using JSON as a scripting language should be reserved for interfacing with Corrade from other languages and via the provided servers (HTTP, TCP, MQTT, etc.).

Index


secondlife/scripted_agents/corrade/tutorials/command_tutorial.txt · Last modified: 2021/09/14 07:48 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the copyright, license, warranty and privacy terms for the usage of this website please see the license, privacy, copyright and the plagiarism pages.