24 November 2010

How easy is it to be evil?

Very easy. I got a notecard advertising a product to find out whether any user is online or not, by way of a chat command, for a mere L$150. I thought about it a while, and then used code snippets from the LL wiki to whip up a script.

The code to do the job is 129 lines of LSL, about half of them comments, and I didn't try hard to write compact code. (When I write programs, I come at them knowing that I'll have to come back and answer the question "Why the precise hell did you do it that way?!" in five years or so. When you look at it that way, clear and concise beats efficient but impenetrable every time.) The only magic here is in getting the key from the avatar name, and that I lifted straight from the LSL wiki.

There's a tiny but very loudly vocal minority of folks who claim that this code is evil, and that the Phoenix Viewer is evil for providing an equivalent function right in the profile display. Their ire at the Phoenix team is misdirected. If they don't think that this kind of thing is appropriate, then they need to convince LL to change the behavior of LSL. There's a JIRA to do just that, SVC-4823. It's only gotten 14 votes as I write this. Maybe, just maybe, the number of users who really care about this is a lot smaller than the loudmouthed kooks think.

In the meantime, I refuse to sit still while people call me evil for doing something that's both explicitly allowed by LL and done in many ways by many other people. This script is no more evil than a pistol is evil. It's the user, not the tool, that is good or evil; a tool simply is.

Here's the offending code, after the jump:


// Evil avatar status script
//
// This script may be dropped into any prim, either rezzed inworld, attached, or
// worn as a HUD. It listens on channel 666 (suitable for such an evil device, if
// you believe some kooks) for an avatar name, and returns the following information
// about that avatar: whether they are actually online or not, their UUID, and a
// clickable link to their profile.
//
// Copyright 2010 Tonya Souther
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

// Global variables.
string first_name;          // User's first name
string last_name;           // User's last name or "Resident"
key user_key;               // UUID of the requested user

default
{

    // This function runs whenever the script is initialized.
    state_entry()
    {
        // Start the listener on channel 666. Only accept messages from the owner.
        llListen(666, "", llGetOwner(), "");
        // Tell the owner the script is ready to use and how to use it.
        llOwnerSay("Evil avatar status script initialized.");
        llOwnerSay("Say an avatar's name on channel 666 to find out their status.");
        llOwnerSay("The name can be a first name and last name, or a username. If");
        llOwnerSay("no last name is specified, the name is treated as a username.");
        llOwnerSay("Example: '/666 Philip Linden'");
    }

    // This function runs whenever the object the script is in is rezzed, either
    //  inworld or as an attachment.
    on_rez(integer param)
    {
        // Force a reinitialization.
        llResetScript();
    }

    // This function runs when the listener hears something it's supposed to. For
    //  this script, that's when the user makes a request.
    listen(integer channel, string name, key id, string message)
    {
        // Acknowledge the request.
        llOwnerSay("Searching for " + message + ".");
        // Split the name into firstname and lastname parts.
        list name_parts = llParseString2List(message, [" "], []);
        first_name = llList2String(name_parts, 0);
        // Assume no last name is given.
        last_name = "Resident";
        if (llGetListLength(name_parts) > 1)
        {
            // A last name was specified, so use it.
            last_name = llList2String(name_parts, 1);
        }
        // Ask for the key for the supplied name.
        llHTTPRequest( "http://name2key.appspot.com/?name=" +
                        first_name + "%20" + last_name, [], "" );
        // The next step happens when the HTTP request is responded to.
    }

    // This function runs when an HTTP response arrives. For this script, it's the
    //  response to the request for the avatar's key.
    http_response(key request_id, integer status, list metadata, string body)
    {
        // Split the reply into its parts. They're separated by a colon.
        list reply_parts = llParseString2List(body, [":"], []);
        // Now split the first part of the reply into first and last names.
        string name = llList2String(reply_parts, 0);
        list name_parts = llParseString2List(name, [" "], []);
        string returned_fname = llList2String(name_parts,0);
        string returned_lname = llList2String(name_parts,1);
        if (llGetListLength(name_parts) >= 2)
        {
            // See if it was what we're asking about.
            if ((llToLower(first_name) == llToLower(returned_fname)) &&
                    (llToLower(last_name) == llToLower(returned_lname)))
            {
                // We got the name we asked for, so the other half of the reply is
                //  the associated UUID. Ask for the online status.
                user_key = llList2Key(reply_parts,1);
                llRequestAgentData(user_key, DATA_ONLINE);
                // We've collected everything; the last happens when the dataserver
                //  returns the requested data.
                return;
            }
        }
        // What we got back wasn't a first and last name, or wasn't the name we
        //  asked about. Something went wrong, and what we think is a key isn't
        //  likely to be useful. Complain and give up.
        llOwnerSay("That name wasn't found.");
    }

    // This function runs when the dataserver returns the requested data. For this
    //  script, that's when we find out if the user is online or not.
    dataserver(key queryid, string data)
    {
        // Convert the returned string into a boolean integer.
        integer user_online = (integer) data;
        // Set the online or offline status.
        string status = " is offline.";
        if (user_online)
        {
            status = " is online.";
        }
        // If the last name is "Resident", leave that out to lessen confusion.
        last_name = " " + last_name;
        if (last_name == " Resident")
        {
            last_name = "";
        }
        // Report what we found.
        llOwnerSay(first_name + last_name + status);
        llOwnerSay("Their UUID key is "+(string) user_key + " .");
        llOwnerSay("Click this link to see their profile: secondlife:///app/agent/" +
                    (string) user_key + "/about");
    }
}

If you want to use this, simply create a prim, save that text as a script, then drop it in the prim. The prim can be rezzed inworld, worn as a HUD, or worn on your avatar; if the latter, I'd recommend making it little and invisible.

2 comments:

  1. I generally find myself smarter after reading most of your posts, especially as I'm a damn fool English Lit major finding that I am intrigued by writing something so logical yet artistic as scripting. To that end, I am probably printing out the "Why the precise hell did you do it THAT way?!" part and sticking it to the top of my monitor :)

    ReplyDelete
  2. I think you're significantly mischaracterizing a significant proportion of the people opposed to putting these features in Firestorm.

    ReplyDelete