Sales Agent Tool
![]() |
Note: Due to licensing issues, all contributions to this wiki have stopped and the articles that we posted are just being maintained. This is one of the projects that has gone further and for updates you are cordially invited to the project page on our wiki. |
Created by Kira Komarov.
Web-Related Derivation Tree
- Web
Movement-Related Integration Tree
- Web
Introduction
Land-selling agencies in Second Life have employees which need to be paged in order to assist visitors in case they are interested to buy land. The following scripts facilitate that by using an out-of-world SQLite database and a few scripts.
Overview
+-----------+ | SQLite DB | +-----+-----+ | +--------+--------+ | registerURL.php | +----------------+ +--------+--------+ +- - -+ [K] Sales Sign | | | +----------------+ +----------+----------+ | +----------------+ | [K] Agent Registry +<- - >+- - -+ [K] Sales Sign | +----------+----------+ | +----------------+ | | +----------------+ +- - - -+- - - -+ +- - -+ [K] Sales Sign | | | | +----------------+ +--+--+ +--+--+ +-----+ . | HUD | | HUD | | HUD | ... . +-----+ +-----+ +-----+ .
The SQLite DB should be placed along with the registerURL.php script in the same directory. The land signs and the [K] Agent Registry send HTTP requests to the SQLite database in order to register a short URL. We have done this previously in order to map a Linden URL to an identifier in order to retrieve it at a later time.
Setting Up
The procedure for setting up is quite simple:
- Drop the registerURL.php script and the database created using the code below on a HTTP server.
- Place the [K] Agent Registry somewhere public so both you and your employees can see it.
- Create a notecard called Agents containing sales agent avatar IDs and names, and place it in the same primitive as the [K] Agent Registry (a sample can be found below).
- Create a HUD with the script in the [K] Sales Code section and distribute them to your sales agents.
- Create sales signs based on [K] Sales Sign and place them on your land where you sales signs are.
Sample Notecard
All the agents listed in the Agents notecard should contain all your sales agents line-by-line. The format is given by the avatar key and the name of the avatar separated by a hash (#) sign. You can see an example below:
1ad33407-a792-476d-a5e3-06007c0802bf#Kira Komarov
SQLite Database
BEGIN TRANSACTION; CREATE TABLE "urls" ( "URL" text NOT NULL, PRIMARY KEY("URL") ); COMMIT;
Code: registerURL.php
<?php ////////////////////////////////////////////////////////// // [K] Kira Komarov - 2012, License: GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html // // for legal details, rights of fair usage and // // the disclaimer and warranty conditions. // ////////////////////////////////////////////////////////// // For privacy, you need to generate a password. You can do that // by issuing the following command on an *nix-like system and // replace "super-duper secret password" with a secret shared between // this PHP script and the LSL counterpart: // sh$ echo "super-duper secret password" | sha1sum | awk '{ print $1 }' $apiKey = 'A1364788-B702-4413-B937-7A57E32B43CE'; if(!isset($_GET['apiKey']) || $_GET['apiKey'] != $apiKey) { throw new Exception('Invalid API key.'); return; } if(!isset($_GET['action'])) { throw new Exception('No action specified.'); return; } switch($_GET['action']) { case "register": if(!isset($_GET['llURL']) && !isset($_GET['shortURL'])) return; $llURL = $_GET['llURL']; $shortURL = $_GET['shortURL']; $db = new PDO('sqlite:customURL.sqlite'); $q = $db->prepare("REPLACE INTO url_table(llURL, shortURL) VALUES(:llURL, :shortURL)"); try { $q->execute(array(':llURL' => $llURL, ':shortURL' => $shortURL)); } catch(PDOException $e) { die($e->getMessage()); } print $shortURL; break; case "retrieve": if(!isset($_GET['shortURL'])) return; $shortURL = $_GET['shortURL']; $db = new PDO('sqlite:customURL.sqlite'); $q = $db->prepare("SELECT llURL FROM url_table WHERE shortURL=:shortURL LIMIT 1"); try { $q->execute(array(':shortURL' => $shortURL)); $res = $q->fetch(PDO::FETCH_OBJ); } catch(PDOException $e) { die($e->getMessage()); } print $res->llURL; break; default: break; } ?>
Code: Sales Sign
<lsl> ////////////////////////////////////////////////////////// // [K] Kira Komarov - 2011, License: GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html // // for legal details, rights of fair usage and // // the disclaimer and warranty conditions. // //////////////////////////////////////////////////////////
////////////////////////////////////////////////////////// // CONFIGURATION // ////////////////////////////////////////////////////////// // Permanent short URL map name using external database. string SHORT_URL = "sales";
// API key - can be anything, used as a password. string API_KEY = "A1364788-B702-4413-3425-7A57E32B43CE";
// Sleep, measured in seconds before paging an agent. integer SLEEP_DELAY = 10; ////////////////////////////////////////////////////////// // INTERNALS // //////////////////////////////////////////////////////////
key nQuery = NULL_KEY; integer gItra = 0; string llURL = ""; list blockList = [];
default {
state_entry() { nQuery = llHTTPRequest("=" + API_KEY + "&shortURL=" + SHORT_URL, [HTTP_METHOD, "GET"], ""); return; } http_response(key reqID, integer status, list metadata, string body) { if (nQuery != reqID) return; llHTTPResponse(nQuery, 200, "OK"); llURL = body; state scan; } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); }
}
state scan {
state_entry() { llSetText("Scanning...", <1,1,1>, 1.0); integer comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF + 41413) ^ 0xBFFFFFFF; llListen(comChannel, "[K] Sales Sign", "", ""); llSensorRepeat("", "", AGENT, 5, TWO_PI, 1); llSetTimerEvent(300); } listen(integer channel, string name, key id, string messages) { list msg = llParseString2List(messages, ["="], []); if(llList2String(msg, 0) != "lock") return; if(llListFindList(blockList, (list)llList2String(msg, 1)) >=0) return; blockList += llList2Key(msg, 1); } timer() { llHTTPRequest(llURL, [HTTP_METHOD, "POST"], "ping"); blockList = []; llSetTimerEvent(300); } http_response(key id, integer status, list metadata, string body) { if(body == "pong") return; llResetScript(); } sensor(integer num) { nQuery = llDetectedKey(0); if(llListFindList(blockList, (list)nQuery) >=0) return; blockList += nQuery; state detected; } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); }
}
state detected {
state_entry() { llSetText("Detected agent, sleeping...", <1,1,1>, 1.0); llSensorRepeat("", nQuery, gItra=AGENT, 5, TWO_PI, SLEEP_DELAY); } sensor(integer num) { if(gItra--) return;
if(llSubStringIndex(llURL, "http://") == -1) { llResetScript(); } llSetText("Paging...", <1,1,1>, 1.0); integer comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF + 41413) ^ 0xBFFFFFFF; llRegionSay(comChannel, "lock=" + (string)nQuery); llHTTPRequest(llURL, [HTTP_METHOD, "POST"], "visitor=" + (string)nQuery + "|" + llKey2Name(nQuery) + "|" + "secondlife:///app/parcel/" + llList2String(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_ID]), 0) + "/about"); } no_sensor() { state scan; } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); }
}
</lsl>
Code: Sales Hud
<lsl> ////////////////////////////////////////////////////////// // [K] Kira Komarov - 2011, License: GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html // // for legal details, rights of fair usage and // // the disclaimer and warranty conditions. // //////////////////////////////////////////////////////////
////////////////////////////////////////////////////////// // CONFIGURATION // ////////////////////////////////////////////////////////// // Permanent short URL map name using external database. string SHORT_URL = "sales";
// API key - can be anything, used as a password. string API_KEY = "A1364788-B702-4413-B937-7A57E32B43CE"; ////////////////////////////////////////////////////////// // INTERNALS // //////////////////////////////////////////////////////////
key nQuery = NULL_KEY;
string llURL = "";
key _owner = NULL_KEY;
default {
state_entry() { llSetColor(<1,0,0>, ALL_SIDES); nQuery = NULL_KEY; _owner = llGetOwner(); llURL = ""; state off; } on_rez(integer num) { _owner = llGetOwner(); llResetScript(); } changed(integer change) { _owner = llGetOwner(); } attach(key id) { _owner = llGetOwner(); llResetScript(); }
}
state on {
state_entry() { nQuery = llHTTPRequest("http://was.fm/SIM/registerURL.php?action=retrieve&apiKey=" + API_KEY + "&shortURL=" + SHORT_URL, [HTTP_METHOD, "GET"], ""); return; } http_response(key reqID, integer status, list metadata, string body) { if(nQuery == reqID && body == "ACK") { llSetColor(<0,1,0>, ALL_SIDES); llOwnerSay("ON"); return; } if (nQuery == reqID && llSubStringIndex(body, "http://") >= 0) { llHTTPResponse(nQuery, 200, "OK"); llURL = body; nQuery = llHTTPRequest(llURL, [HTTP_METHOD, "POST"], "signon=" + (string)_owner + "#" + llKey2Name(_owner)); return; } } touch_start(integer num) { state off; } on_rez(integer num) { _owner = llGetOwner(); llResetScript(); } changed(integer change) { _owner = llGetOwner(); } attach(key id) { _owner = llGetOwner(); llResetScript(); }
}
state off {
state_entry() { nQuery = llHTTPRequest("http://was.fm/SIM/registerURL.php?action=retrieve&apiKey=" + API_KEY + "&shortURL=" + SHORT_URL, [HTTP_METHOD, "GET"], ""); return; } http_response(key reqID, integer status, list metadata, string body) { if(nQuery == reqID && body == "ACK") { llSetColor(<1,0,0>, ALL_SIDES); llOwnerSay("OFF"); return; } if (nQuery == reqID && llSubStringIndex(body, "http://") >= 0) { llHTTPResponse(nQuery, 200, "OK"); llURL = body; nQuery = llHTTPRequest(llURL, [HTTP_METHOD, "POST"], "signoff=" + (string)_owner + "#" + llKey2Name(_owner)); return; } } touch_start(integer num) { state on; } on_rez(integer num) { _owner = llGetOwner(); llResetScript(); } changed(integer change) { _owner = llGetOwner(); } attach(key id) { _owner = llGetOwner(); llResetScript(); }
}
</lsl>
Code: Agent Registry
<lsl> ////////////////////////////////////////////////////////// // [K] Kira Komarov - 2011, License: GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html // // for legal details, rights of fair usage and // // the disclaimer and warranty conditions. // //////////////////////////////////////////////////////////
////////////////////////////////////////////////////////// // CONFIGURATION // ////////////////////////////////////////////////////////// // Permanent short URL map name using external database. string SHORT_URL = "sales";
// API key - can be anything, used as a password. string API_KEY = "A1364788-B702-4413-B937-7A57E32B43CE"; ////////////////////////////////////////////////////////// // INTERNALS // //////////////////////////////////////////////////////////
integer nLine = 0; list aList = []; list vList = []; key nQuery = NULL_KEY; string nName = "Agents"; string llURL = ""; key salesAgent = NULL_KEY;
default {
state_entry() { llSetText("Reading agent list...", <1,1,1>, 1.0); integer itra = llGetInventoryNumber(INVENTORY_NOTECARD)-1; do { if(llGetInventoryName(INVENTORY_NOTECARD, itra) == nName) jump notecard; } while(--itra>=0); llSetText("Failed to find Agents notecard.", <1,1,1>, 1.0); return;
@notecard;
nQuery = llGetNotecardLine(nName, nLine); }
dataserver(key id, string data) { if(id != nQuery) return; if(data == EOF) { llSetText("Notecard read...", <1,1,1>, 1.0); state url; return; } if(data == "") jump next; aList += data;
@next;
nQuery = llGetNotecardLine(nName, ++nLine); } changed(integer change) { llReleaseURL(llURL); llURL = ""; llResetScript(); } on_rez(integer pin) { llReleaseURL(llURL); llURL = ""; llResetScript(); }
}
state url {
state_entry() { llSetText("Requesting URL...", <1,1,1>, 1.0); llReleaseURL(llURL); llURL = ""; llSetTimerEvent(5); llRequestURL(); } on_rez(integer param) { llReleaseURL(llURL); llURL = ""; llResetScript(); } timer() { if(llURL != "") { llSetTimerEvent(0); nQuery = llHTTPRequest("http://was.fm/SIM/registerURL.php?action=register&apiKey=" + API_KEY + "&shortURL=" + SHORT_URL + "&llURL=" + llURL, [HTTP_METHOD, "GET"], ""); return; } } http_request(key id, string method, string body) { if (method == URL_REQUEST_GRANTED) { llURL = body; return; } if(method == URL_REQUEST_DENIED) { llReleaseURL(llURL); llResetScript(); return; } } http_response(key reqID, integer status, list metadata, string body) { if (nQuery == reqID && body == SHORT_URL) { llHTTPResponse(nQuery, 200, "OK"); state agents; } }
}
state agents {
state_entry() { llSetTimerEvent(1); } http_request(key id, string method, string body) { if(method != "POST") return; list data = llParseString2List(body, ["="], []); string action = llList2String(data, 0);
////////////////////////////////////////////////////////////////////////////////////// // Sign ping-pong to make sure all links are established. // ////////////////////////////////////////////////////////////////////////////////////// if(action == "ping") { llHTTPResponse(id, 200, "pong"); return; } string agentLine = llList2String(data, 1); ////////////////////////////////////////////////////////////////////////////////////// // Any signon or signoff requests must be matched to the agents the notecard. // // Furthermore, a sales agent cannot be a visitor so we knock that possiblity off. // ////////////////////////////////////////////////////////////////////////////////////// if(((action == "signon" || action == "signoff") && !~llListFindList(aList, (list)agentLine)) || (action == "visitor" && ~llListFindList(aList, (list)agentLine))) return; ////////////////////////////////////////////////////////////////////////////////////// key agent = llList2String(llParseString2List(agentLine, ["#"], []), 0); if(action == "visitor") { // Store agent key over state change. nName = agent; // jump to online checks state onlineagents; } if(action == "signoff") { nLine = llListFindList(vList, (list)agentLine); if(nLine != -1) { vList = llDeleteSubList(vList, nLine, nLine); } llHTTPResponse(id, 200, "ACK"); llSetTimerEvent(1); // alarm, keep it portable 1s return; } if(action == "signon") { if(llListFindList(vList, (list)agentLine) == -1) { vList += agentLine; } llHTTPResponse(id, 200, "ACK"); llSetTimerEvent(1); // alarm, keep it portable 1s return; } } timer() { llSetTimerEvent(0); if(!llGetListLength(vList)) { llSetText("No agents registered.", <1,1,1>, 1.0); return; } list nList = []; integer itra = llGetListLength(vList)-1; do { nList += llKey2Name(llList2Key(llParseString2List(llList2String(vList, itra), ["#"], []), 0)); } while(--itra>=0); llSetText("Registered sales agents: \n" + llDumpList2String(nList, "\n"), <1,1,1>, 1.0); llSetTimerEvent(60); } changed(integer change) { llReleaseURL(llURL); llURL = ""; llResetScript(); } on_rez(integer param) { llReleaseURL(llURL); llURL = ""; llResetScript(); }
}
////////////////////////////////////////////////////////// // We use the list of sales agents as a queue, // // dequeueing and checking whether they are online and // // enqueueing them back if they are online and active. // // This achieves a permutation of all online agents. // ////////////////////////////////////////////////////////// state onlineagents {
state_entry() { llSetText("Checking for online agent...", <1,1,1>, 1.0); // pop stack salesAgent = llList2Key(llParseString2List(llList2String(vList, 0), ["#"], []), 0); vList = llDeleteSubList(vList, 0, 0); // ask for online status nQuery = llRequestAgentData(salesAgent, DATA_ONLINE); }
dataserver(key id, string data) { if(nQuery != id) return; if(data == "0") { // dequeue stack again - agent is offline, discard them vList = llDeleteSubList(vList, 0, 0); // extract next agent salesAgent = llList2Key(llParseString2List(llList2String(vList, 0), ["#"], []), 0); // query online status nQuery = llRequestAgentData(salesAgent, DATA_ONLINE); return; } if(data == "1") { // send visitor message llInstantMessage(salesAgent, llKey2Name(nName) + " ( secondlife:///app/agent/" + nName + "/about ) is visiting the land parcel at: " + llList2String(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_NAME]), 0)); // push stack vList += (string)salesAgent + "#" + llKey2Name(salesAgent); state agents; } } changed(integer change) { llReleaseURL(llURL); llURL = ""; llResetScript(); } on_rez(integer param) { llReleaseURL(llURL); llURL = ""; llResetScript(); }
}
</lsl>