Corrade uses key-value data functions and syntax which is three-way compatible with:
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).
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.
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:
wasKeyValueGet
function will return an escaped 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 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.
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']; }
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)); } }
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.).