With Corrade it is possible to detect the shape gender of a given avatar. The following example script will scan the entire region for avatars and report their gender on local chat, along with their names and keys. Note that in order for the script to work, Corrade has to be on the same simulator as this script.
The script detects the shape gender and one should take into account that does not necessarily make an avatar either female or male in some cases. For example, for an animal avatar or a Sci-Fi avatar, it may be uncertain whether the avatar is male or female. Some creators just use either gender when designing avatars such that the gender of the shape does not necessarily reflect the actual gender of the avatar.
Create a new primitive and add a notecard named configuration
in the newly created primitive with the following contents:
############################# START CONFIGURATION ############################ # This is a configuration notecard based on the key-value data Wizardry and Steamworks reader. # Everything following the "#" character is ignored along with blank lines. Values can be set for various # parameters in a simple and understandable format with the following syntax: # KEY = "VALUE" # Every time you change this configuration notecard, the script will reset itself and the new configuration # will be read from this notecard. # The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini # This is the UUID of the Corrade bot. corrade = "1272a407-1af3-47f7-abcd-79f81d28637d" # The name of the group - it can also be the UUID of the group. group = "My Group" # The password for the group. password = "mypassword" # This represents the scan interval to detect avatars. Note that this script does not rescan avatars. interval = "10" # This represents the amount of free memory measured in bytes that the script will attempt to maintain. # After the memory usage of this script exceeds this amount, the script will start culling the list of # detected avatars in an attempt to not overflow and keep the script running. memory = "1024" ############################# END CONFIGURATION #############################
and configure the settings to match your Corrade configuration. After that, add the script from the code section into the same primitive where you placed the configuration
notecard.
/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// // // This is a script that uses the Corrade Second Life / OpenSim bot which // is able to detect the gender of avatar shapes. You can find more details // about Corrade at: http://grimore.org/secondlife/scripted_agents/corrade // // This script works together with a "configuration" notecard that must be // placed in the same primitive as this script. The purpose of this script // is to demonstrate detecting avatar genders with Corrade and you are free // to use, change, and commercialize it provided that you follow the terms // of the CC BY 2.0 license at: https://creativecommons.org/licenses/by/2.0 // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueGet(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; list a = llParseString2List(data, ["&", "="], []); integer i = llListFindList(a, [ k ]); if(i != -1) return llList2String(a, i+1); return ""; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueEncode(list data) { list k = llList2ListStrided(data, 0, -1, 2); list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); data = []; do { data += llList2String(k, 0) + "=" + llList2String(v, 0); k = llDeleteSubList(k, 0, 0); v = llDeleteSubList(v, 0, 0); } while(llGetListLength(k) != 0); return llDumpList2String(data, "&"); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// // escapes a string in conformance with RFC1738 string wasURLEscape(string i) { string o = ""; do { string c = llGetSubString(i, 0, 0); i = llDeleteSubString(i, 0, 0); if(c == "") jump continue; if(c == " ") { o += "+"; jump continue; } if(c == "\n") { o += "%0D" + llEscapeURL(c); jump continue; } o += llEscapeURL(c); @continue; } while(i != ""); return o; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// // unescapes a string in conformance with RFC1738 string wasURLUnescape(string i) { return llUnescapeURL( llDumpList2String( llParseString2List( llDumpList2String( llParseString2List( i, ["+"], [] ), " " ), ["%0D%0A"], [] ), "\n" ) ); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// list wasCSVToList(string csv) { list l = []; list s = []; string m = ""; do { string a = llGetSubString(csv, 0, 0); csv = llDeleteSubString(csv, 0, 0); if(a == ",") { if(llList2String(s, -1) != "\"") { l += m; m = ""; jump continue; } m += a; jump continue; } if(a == "\"" && llGetSubString(csv, 0, 0) == a) { m += a; csv = llDeleteSubString(csv, 0, 0); jump continue; } if(a == "\"") { if(llList2String(s, -1) != a) { s += a; jump continue; } s = llDeleteSubList(s, -1, -1); jump continue; } m += a; @continue; } while(csv != ""); // postcondition: length(s) = 0 return l + m; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// string wasListToCSV(list l) { list v = []; do { string a = llDumpList2String( llParseStringKeepNulls( llList2String( l, 0 ), ["\""], [] ), "\"\"" ); if(llParseStringKeepNulls( a, [" ", ",", "\n", "\""], [] ) != (list) a ) a = "\"" + a + "\""; v += a; l = llDeleteSubList(l, 0, 0); } while(l != []); return llDumpList2String(v, ","); } // corrade data string CORRADE = ""; string GROUP = ""; string PASSWORD = ""; integer INTERVAL = 0; integer MEMORY = 0; // for holding the callback URL string callback = ""; // for notecard reading integer line = 0; // key-value data will be read into this list list tuples = []; // store names and uuids for gender detect list names = []; list uuids = []; // store the keys of detected agents in // order to prevent scanning them again list found = []; // temporary storage over event handler scope string name = ""; key uuid = NULL_KEY; default { state_entry() { if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { llOwnerSay("Sorry, could not find a configuration inventory notecard."); return; } // DEBUG llOwnerSay("Reading configuration file..."); llGetNotecardLine("configuration", line); } dataserver(key id, string data) { if(data == EOF) { // invariant, length(tuples) % 2 == 0 if(llGetListLength(tuples) % 2 != 0) { llOwnerSay("Error in configuration notecard."); return; } CORRADE = llList2String( tuples, llListFindList( tuples, [ "corrade" ] ) +1); if(CORRADE == "") { llOwnerSay("Error in configuration notecard: corrade"); return; } GROUP = llList2String( tuples, llListFindList( tuples, [ "group" ] ) +1); if(GROUP == "") { llOwnerSay("Error in configuration notecard: group"); return; } PASSWORD = llList2String( tuples, llListFindList( tuples, [ "password" ] ) +1); if(PASSWORD == "") { llOwnerSay("Error in configuration notecard: password"); return; } INTERVAL = llList2Integer( tuples, llListFindList( tuples, [ "interval" ] ) +1); if(INTERVAL == 0) { llOwnerSay("Error in configuration notecard: interval"); return; } MEMORY = llList2Integer( tuples, llListFindList( tuples, [ "memory" ] ) +1); if(MEMORY == 0) { llOwnerSay("Error in configuration notecard: memory"); return; } // DEBUG llOwnerSay("Read configuration notecard..."); state url; } if(data == "") jump continue; integer i = llSubStringIndex(data, "#"); if(i != -1) data = llDeleteSubString(data, i, -1); list o = llParseString2List(data, ["="], []); // get rid of starting and ending quotes string k = llDumpList2String( llParseString2List( llStringTrim( llList2String( o, 0 ), STRING_TRIM), ["\""], [] ), "\""); string v = llDumpList2String( llParseString2List( llStringTrim( llList2String( o, 1 ), STRING_TRIM), ["\""], [] ), "\""); if(k == "" || v == "") jump continue; tuples += k; tuples += v; @continue; llGetNotecardLine("configuration", ++line); } on_rez(integer num) { llResetScript(); } changed(integer change) { if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { llResetScript(); } } } state url { state_entry() { // DEBUG llOwnerSay("Requesting URL..."); llRequestURL(); } http_request(key id, string method, string body) { if(method != URL_REQUEST_GRANTED) return; callback = body; // DEBUG llOwnerSay("Got URL..."); state detect; } on_rez(integer num) { llResetScript(); } changed(integer change) { if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { llResetScript(); } } } state detect { state_entry() { // DEBUG llOwnerSay("Detecting if Corrade is online..."); llSetTimerEvent(5); } timer() { llRequestAgentData((key)CORRADE, DATA_ONLINE); } dataserver(key id, string data) { if(data != "1") { // DEBUG llOwnerSay("Corrade is not online, sleeping..."); llSetTimerEvent(30); return; } state scan; } on_rez(integer num) { llResetScript(); } changed(integer change) { if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { llResetScript(); } } } state scan { state_entry() { // DEBUG llOwnerSay("Scanning agents..."); list tmp = llGetAgentList(AGENT_LIST_REGION, []); do { key id = llList2Key(tmp, 0); // skip avatars that we have previously detected if(llListFindList(found, (list)id) != -1) jump continue; uuids += id; names += llKey2Name(id); @continue; tmp = llDeleteSubList(tmp, 0, 0); } while(llGetListLength(tmp) != 0); // recurse over timer, start. llSetTimerEvent(1); } timer() { // Pause the timer. llSetTimerEvent(0); // if the scanning list is empty, switch to detect if(llGetListLength(names) == 0) state detect; // pop the first name and UUID off the stack name = llList2String(names, 0); uuid = llList2Key(uuids, 0); names = llDeleteSubList(names, 0, 0); uuids = llDeleteSubList(uuids, 0, 0); // get the full name and send it to Corrade for scanning list full = llParseString2List(name, [" "], []); llInstantMessage(CORRADE, wasKeyValueEncode( [ "command", "getavatardata", "group", wasURLEscape(GROUP), "password", wasURLEscape(PASSWORD), "firstname", wasURLEscape( llList2String( full, 0 ) ), "lastname", wasURLEscape( llList2String( full, 1 ) ), "sift", wasURLEscape( wasListToCSV( [ "match", wasURLEscape( ",Index,31,([^,$]+)" ) ] ) ), "data", "VisualParameters", "callback", wasURLEscape(callback) ] ) ); } http_request(key id, string method, string body) { llHTTPResponse(id, 200, "OK"); if(wasURLUnescape( wasKeyValueGet("success", body)) != "True") return; // request succeeded, so grab the // sex from the visual parameters integer sex = (integer) wasURLUnescape( wasKeyValueGet( "data", body ) ); // at this point we know the following: // - the name of the scanned avatar stored in "name" // - the UUID of the scanned avatar stored in "uuid" // - the gender of the avatar shape: // - if sex is 0, then the avatar has a female shape // - otherwise, the avatar has a male shape if(sex != 0) { llSay(0, "The avatar " + name + "(" + (string)uuid + ")" + " has a male shape!"); jump continue_2; } llSay(0, "The avatar " + name + "(" + (string)uuid + ")" + " has a female shape!"); @continue_2; // this keeps the amount of free memory available to the script above a specified treshold if (llGetFreeMemory() < MEMORY) found = llDeleteSubList(found, 0, 0); // to prevent scanning the same avatar again, add the uuid to the found list found += uuid; // Resetart the timer. llSetTimerEvent(INTERVAL); } on_rez(integer num) { llResetScript(); } changed(integer change) { if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { llResetScript(); } } }