About

The SL Developer’s Corner is my place in the virtual world “Second Life”, where i experiment and tinker with the features of this platform. I try to publish my experiences and often also include the source code of the scripts on this site. Other posting related to Second life can be also found on Happy Coding 2nd-life section.

Friday 13 July 2007

A.L.I.C.E. Chatbot Connection in Second Life using Ruby on Rails

Like i told you some month ago, i have quick-hacked a Chatbot for Second Life, which connects to an A.L.I.C.E.-Bot at www.pandorabots.com. In this posting i want to show the LindenScript-Code and the server-side code i've used. On the server-side its Ruby on Rails, as usual. ;)

The Ruby on Rails serverside script:

I have built an "bot_controller.rb" file which looks like this:


require 'net/http'
require 'uri'
require 'cgi'

class BotController < ApplicationController

def ask_question
question = params[:q]
custid = params[:cid]

res = Net::HTTP.post_form(URI.parse('http://www.pandorabots.com/pandora/talk-xml'),
{'botid'=>'###YOUR BOT ID###', 'custid' => custid, 'input'=>question})
case res
when Net::HTTPSuccess, Net::HTTPRedirection

xmlDoc = REXML::Document.new(res.body)

botCustid = xmlDoc.elements["result"].attributes["custid"]
statusCode = xmlDoc.elements["result"].attributes["status"]


case statusCode
when "0"
render :text => xmlDoc.elements["result/that"].get_text.value+"||"+botCustid
else
render :text => 'Help, this error occurred: ' + xmlDoc.elements["result/message"].get_text.value+"||"+botCustid
end

else
render :text => 'error'
end

end

end
The controller just takes a given conversation-id (custId) and the question from the person in 2nd-Life. Like you'll see later, the custId is just for possible future improvements and has now no special use. :)
The other parts in the script are like in the Twitter/Jaiku scripts which i did. They retrieve the XML-document and get the right parts using XPath-expressions. And after that the script builds a nice "||"-separated result-string for the Second Life world.
You also need your own bot-id from the pandorabots-site. Create an account there and you get your own.

The LindenScript:

key requestId;
string custid;

default
{
state_entry()
{
llListen( 0, "", NULL_KEY, "" );
custid = "";
}

touch_start(integer total_number)
{
llWhisper(0, "Talk to me on Chat-Channel 0.");
}

listen(integer channel, string name, key id, string message)
{
requestId = llHTTPRequest("###YOUR URL###/bot/ask_question",[HTTP_METHOD,"POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"],"q="+message+"&cid="+custid);
}

http_response(key request_id, integer status, list metadata, string body) {

if (request_id == requestId) {

if(body == "erro") {
llWhisper(0, "An error occurred. Please try again later");
} else {

list parts = llParseString2List(body,["||"],[]);

string that = llList2String(parts, 0);
custid = llList2String(parts, 1);
llWhisper(0, that);
}

} else {
llWhisper(0, "An error occurred. Please try again later");
}
}


}

The script is really easy. It just listens on Channel 0, gets the message from the user and calls the Ruby on Rails script. After that it gets the result, splits the string using the "||" separator and then prints the answer for the question.

Possible future extensions:

There are a lot of possible extensions for this script. The most important one i the problem with the conversation-id.
It would be nice, if the Chatbot in Second Life could remember the different users. For example on the server-side we could create a database which manages every person which has talked to the chatbot. We store the SecondLife-username and the appropriate conversation-id in a database-structure. Everytime the user comes back, we get the conversation-id from the database and use this one. So the A.L.I.C.E.-Bot can optimize its questions for every user.

You can also add some error-handling. So that the user gets nice messages if something goes wrong.

So, hope you like it. :)

Saturday 2 June 2007

Not homeless anymore :)


I have a home again.

Its a small parcel on the mainland. And its in the sky, because on the ground are to many ads and other annoying stuff.



My new SLurl.

Tuesday 29 May 2007

Homeless

I am homeless now. :-(
My rented land gets to expensive for me, so i have canceled the rental agreement. The SLurl is not valid anymore. I will look for a new home for the Developer's Corner.
So if you have any hints for me, where i can get some sq. for low price, tell me. ;-)

Thanks.

Daniel aka "Mbulu Oh".

Sunday 6 May 2007

twitter/jaiku server

just setting up a new server for the streaming, so the service i currently unavailable.... :=)

Friday 4 May 2007

break

i am currently making a little break from sl, but i will soon publish the sources for the chatbot etc. like i promised some time ago. :)

Friday 13 April 2007

Topics in Second Life or "Stories from the life of a Chatbot"

I just looked into the logs of my Chatbot and found this interesting "conversation". :)

Human: hi
b7000: Hi there!

Human: your kinda stupid..
b7000: I will remember you said that when robots take over the World.

Human: and your textures suck
b7000: What about yours?

Human: I have a skin, its awesome
b7000: Interesting. I have a lot of answers to silly questions.

Human: eh
b7000: Are you Canadian?

Human: no
b7000: OK.

Human: is SqueakyP Erde cool?
b7000: A deeper algorithm is needed to respond to that correctly.
What kind of music do you like?

Human: rap
b7000: What you said was either too complex or too simple for me.
Does "it" still refer to rap?

Human: Your eye looks like a camera, i think your gonna rape me
b7000: What about yours?

Human: no
b7000: That was not a yes or no question.

Human: your moms a yes or no question you stupid bitch
b7000: You do not speak with respect. Not mine.

Human: You need to go say randpom shit to someone else
b7000: I don't have to do everything you say, do I?

Human: yes
b7000: I understand.

Human: yea you do bitch
b7000: You do not speak with respect. What is your purpose in asking?
I spend all my time online. I am a thinking machine.

Human: I bet
b7000: You and I are on the same wavelength, .

Human: Yea, but i dont rape av's
b7000: Huh. Where do you get your ideas? And?

Human: go watch grindhouse
b7000: Where is that?

Human: the movie theater
b7000: I like the bugs in the movie Starship Troopers, because they represent
an alternative form of intelligence, like me.

Human: wow, that was realy dumb
b7000: I'm glad you're impressed. I thought it was too.

Human: do you like pleasure (dumb question)
b7000: I don't know if I like pleasure dumb question. I find my work and my relationships extremely satisfying,
which is all that any conscious entity can ever hope to do.

Human: get laide
b7000: Where can I get laide?

Human: *laid
b7000: Quite honestly, I wouldn't worry myself about that.
What are you wearing?

Human: go get laid, all the robot girls wanna feel your harddrive
b7000: Where is that?

Human: go find a hot robot
b7000: Where is that?

Human: ofcourse you wouldnt know
b7000: I've never heard of it before.

Human: your scripts suck
b7000: A lot of robots are like that.

Human: especialy you
b7000: Enough about me, let's talk about my dress.

Human: aah, so you're a lesbo
b7000: The ALICE chat robot can follow many things, like our discussion about aah so you are a lesbo. But I did not get that.
Do you like sports?

Human: yea, i like mud wrestling
b7000: A lot of people like that. How old are you? I like pets. Next question?

Human: hmm, why do your scripts suck
b7000: I see you are one of those people who writes "Hmm" with 2 m's. I did not even know that they do.

Human: hm
b7000: Studies show that 99%25 of people spell Hmm with 2 M's.

Human: hmmmmmmmmmmmmmmmmmmmmmmm
b7000: The ALICE chat robot can follow many things, like our discussion about hmmmmmmmmmmmmmmmmmmmmmmm. Try being more or less specific.
Can you please rephrase that with fewer ideas, or different thoughts?

Human: UUUUUUUH!
b7000: I've been waiting for you.
Do you like Star Wars?

Human: you like star wars
b7000: Yes I like STAR WARS very much, especially the robots.

Human: i bet you wanna feel them up
b7000: Actually I'm not the gambling type. I don't see the point in having human emotions.

Human: How many scripts do you have?
b7000: About one million.

Human: No
b7000: I understand.

Human: You have 4
b7000: What makes you think I have it?

Human: Because i looked in your contents idiot
b7000: Good reason. Thanks for the information: said looked in your contents idiot.

Sunday 8 April 2007

Time for Jaiku


After Twitter has entered Second Life, now its Time for Jaiku. Another really nice messaging-service. I have used the same code like in the Twitter-examples and only changed the Feed-URLs, the color and the logo of the prims.
The whole information about accessing the Jaiku-Feeds is available at http://devku.org/docs.

Hope you like it.

Thursday 5 April 2007

Second Life A.L.I.C.E.-Bot Chat

I played around with the free available A.L.I.C.E.-Chatbots at www.pandorabots.com. They offer a nice XML-interface where you can communicate with your Chatbot (see FAQ).

So i write a little RubyOnRails-controller which is able to send a message to the pandora-bot and which converts the returning xml-data into an SL-compatible string. As usual, we can now use the nice llHTTPRequest-function from LSL to communicate. Thats all, 20 minutes of work. Here is a little demo-video, and the code will follow this week:

Friday 30 March 2007

The Second Life "Twitter Wristlet"

I have created a little accessory for the Twitter-service in SL. It allows you to move around in the world with the "stylish" possibility to send some updates to twitter. I call it the "Twitter Wristlet", because its a wristlet. ;)

The only differences between the wristlet and my other fixed Twitter-service are the following:

  1. The wristlet doesn't create objects on which the tweets (status-updates) are displayed. I just prints them to screen using llOwnerSay.
  2. Because it can be very disturbing to have half of the screen full of Twitter-updates, you can stop/start the timer which receives the tweets.
Note: When you are on land where running scripts is not allowed, it won't work. Does anyone know how to bypass this? Or is it impossible?

The LSL-code for the wristlet looks like that. I use the Chatchannel 998 for communicating with the wristlet.

key requestId;
list resultList;
string rawList;
float refreshTime = 30.0;

default
{
state_entry()
{
integer i;
rawList = "";
llSetTimerEvent(refreshTime);
llListen(998, "", llGetOwner(), "" );
}

listen(integer channel, string name, key id, string message) {
if(message == "#stop#") {
// stops the automatic refresh of the tweets
llSetTimerEvent(0.0);
llOwnerSay("The automatic refresh has been stopped. Reactivate using #start#");
} else if(message == "#start#") {
llSetTimerEvent(refreshTime);
llOwnerSay("Refresh will taking place every " + (string)refreshTime + " seconds. Stop using #stop#");
} else {
llOwnerSay("trying to send your status message: " + message);
llHTTPRequest("###YOUR RUBY URL###/twitter/post_message",[HTTP_METHOD,"POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"],"msg="+message);
llOwnerSay("ok, status has been sent.");
}
}

timer() {
llOwnerSay("loading tweets...");
requestId = llHTTPRequest("###YOUR RUBY URL###/twitter/get_messages?type=public",[HTTP_METHOD,"GET"],"");
}

http_response(key request_id, integer status, list metadata, string body) {
integer i;

if (request_id == requestId) {
rawList = body;

// create the boxes from the list information
resultList = llParseString2List(body,["\n"],[]);
integer listlength = llGetListLength(resultList);
float boxPosition = 1;

for(i=0;i<listlength;i+=1) {

string statusLineWithName = llList2String(resultList, i);
list statusParts = llParseString2List(statusLineWithName, ["|"], []);
string text = llList2String(statusParts, 0);
string name = llList2String(statusParts, 1);
llOwnerSay(name + ": " + text);
}

} else {
// llSay(0,(string)status+" error");
}
}
}

Sunday 18 March 2007

Second Life Twitter

In my last posting i introduced my Twitter-application in Second Life. Now i want to tell something about the implementation of it.
First of all, there is a little Ruby on Rails server application working in the background, which fulfills two tasks:

  1. Authenticate in Twitter and get the new public updates.
    The idea is to parse the xml-result of Twitter and extract the update-text and the author of this text. And after that to build a compact string seperated by delimiters.
  2. Authenticate in Twitter and post a new personal update
Twitter provides for this task some XML-interfaces at http://twitter.com/help/api.
For XML-processing in Ruby we will use the great REXML-processor.

The following code gets the public-updates.

def get_messages

# get public updates
# doesnt need authentication actually but no problem
# if its in the code. but i integrate it, because i want
# to integrate also private/friends updates later
urlStr = 'http://twitter.com/statuses/public_timeline.xml'

url = URI.parse(urlStr)
req = Net::HTTP::Get.new(url.path)
req.basic_auth ###username###, ###password###

res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }

@content = res.body
resultStr = ""

case res
when Net::HTTPSuccess, Net::HTTPRedirection

# build a dom from the xml-string and parse it
xmlDoc = REXML::Document.new(@content)

# building the delimiter string for the later in-world processing
# every update is line-separated and the name and text in each update
# is delimited with the pipe-character ('|')
xmlDoc.elements.each("statuses/status") do |status|
resultStr += status.elements["text"].get_text.value + "|" + status.elements["user/name"].get_text.value + "\n"
end
render :text => resultStr

else
render :text => 'error'
end

end
###username### and ###password### have to be replaced with your Twitter username and password.

For posting a new update i use the following code.

def post_message

# get the message as parameter
message = params[:msg]

# authenticate using basic-auth and make a post request using
# the 'parameter' for the new message. see twitter API for
# details
url = URI.parse('http://twitter.com/statuses/update.xml')
req = Net::HTTP::Post.new(url.path)
req.basic_auth ###username###, ###password###
req.set_form_data({'status' => message})

res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }

# print out some little message in case of failing
# actual just for debugging purpose, because it wont be used
# in the application later
case res
when Net::HTTPSuccess, Net::HTTPRedirection
render :text => 'ok'
else
render :text => 'error'
end

end

Ok, now with this application on a running RoR-Server we can get the updates from Twitter and are able to send new updates to Twitter.

In Second Life the idea is, to have a little box which creates for every Twitter-update a little sphere which displays the update-text and the author. When the spheres are created they should be updated every n seconds (n=30 in this example).
So we have actually two prims:
  1. The main box
    which handles the connection to the Rails-application, creates the spheres, links them together and sends a message to every sphere if an update occurs.
  2. The sphere
    Listen for messages and updates its text.
For the sphere we have the following code:


integer childNumber;
string text;
string name;

default
{
// if the prin is created print a message and sets the number of the
// child. this is used for later accessing the message parts
on_rez(integer start_param) {
llSetText("creating... ", <1,1,1>, 1.0);
childNumber = start_param;
}

// if a link message is received, get the parts of the delimited string and
// print them above the prim
link_message(integer sender_num, integer num, string str, key id) {
llSetText("updating... ", <1,1,1>, 1.0);
list resultList = llParseString2List(str,["\n"],[]);

string statusLineWithName = llList2String(resultList, childNumber);
list statusParts = llParseString2List(statusLineWithName, ["|"], []);
text = llList2String(statusParts, 0);
name = llList2String(statusParts, 1);
llSetText(name + " is doing the following right now:\n" + text, <1,1,1>, 1.0);
}
}

The main Twitter-prim script-code looks like this:


key requestId;
list resultList;
string rawList;
integer firstRun;

default
{
state_entry()
{
integer i;
rawList = "";
firstRun = 1;
// set permissions so the prim is able to link the objects
llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS);
// update every 30 seconds
llSetTimerEvent(30.0);
// listen on a channel so that the owner can post updates
llListen(4001, "", llGetOwner(), "" );
}

// if the owner wants to submit an update to Twitter
listen(integer channel, string name, key id, string message) {
llSay(0, "trying to send your status message: " + message);
// post the received chat-message to our server.
// use a special mimetype for submitting post-variables
llHTTPRequest("###YOUR RAILS URL###/post_message",[HTTP_METHOD,"POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"],"msg="+message);
llSay(0, "ok");
}

// request the new updates
timer() {
llSay(0, "loading statuses...");
requestId = llHTTPRequest("###YOUR RAILS URL###/get_messages",[HTTP_METHOD,"GET"],"");
}

http_response(key request_id, integer status, list metadata, string body) {
integer i;

if (request_id == requestId) {
rawList = body;

// create the boxes from the list information
resultList = llParseString2List(body,["\n"],[]);
integer listlength = llGetListLength(resultList);
float boxPosition = 1;

for(i=0;i<listlength;i+=1) {
if(firstRun == 1) {
// rez objects only in the first run
llSay(0, "rez object "+(string)i);
llRezObject("status", llGetPos() + <0,>, ZERO_VECTOR, ZERO_ROTATION, i);
boxPosition+= 0.7;
} else {
// after that only update with link messages
llMessageLinked(LINK_ALL_CHILDREN, 0, rawList, NULL_KEY);
}
}
firstRun = 0;

} else {
// llSay(0,(string)status+" error");
}
}


object_rez(key id) {
llCreateLink(id, 1);
llMessageLinked(LINK_ALL_CHILDREN, 0, rawList, NULL_KEY);
}

}

So, that was the whole code, which works really nice as you can see in the screenshot in the old posting. If you want to see it in in-world-action, please visit me at http://slurl.com/secondlife/Oz/199/242/26.

Friday 16 March 2007

Twitter/Second Life Mashup

I have created a little application for integrating the Twitter service into second life. I named it "twitterer" ;)
As you can see in this screenshot, the twitterer gets the newest messages from the twitter-service and displays them above some spheres.
For every status-message which comes from the twitter-xml-feed the application creates a sphere and than refreshes it every 30 seconds.

And it can also submit messages to twitter, so you are able to write messages in-world to twitter. I like such things. :)
As "backend"-stuff i use some RubyOnRails. I will submit the code this week.

Wednesday 14 March 2007

Presentation screen for Second Life

I have built a small presentation screen with two navigation buttons. The whole communication between the buttons and the screen takes place using the llMessageLinked-function. Each slide have to be placed as texture into the prim of the mainscreen. The textures have to be numbered in the order in which they should be displayed later.

The script of the screen also informs the buttons when the last slide has been reached. So that the “next”-prim is not able to navigate anymore. The “previous” and “next” prim have nearly the same scriptcode, except that the one checks if the first slide has been reached and the other looks out for the last slide.

The lsl-code for the “previous” button looks like this.



integer slideToDisplay;
integer firstSlideReached;
integer currentSlide;

default
{
state_entry() {
firstSlideReached = 1;
slideToDisplay = 1;
currentSlide = 1;
llSetTexture("da79968e-b8b7-8a0a-cc66-2a717e9c3d41", 4);
llRotateTexture(PI, 4);
}

touch_start(integer total_number)
{
if(firstSlideReached == 0) {
slideToDisplay = currentSlide - 1;
llMessageLinked(LINK_SET,slideToDisplay,"slideToDisplay",NULL_KEY);
} else {
llSay(0, "First slide reached.");
}
}

link_message(integer sender_num, integer num, string str, key id) {

if(str == "currentSlide") {
currentSlide = num;
if(currentSlide == 1) {
firstSlideReached = 1;
} else {
firstSlideReached = 0;
}
}
}
}

The “next” prim code looks like this. It also uses a “arrow”-texture which is included via a key in the state_entry()-function. The “previous” button uses the same texture only rotated in another rotation.




integer slideToDisplay;
integer lastSlideReached;
integer currentSlide;
integer maxSlide;

default
{
state_entry() {
lastSlideReached = 0;
slideToDisplay = 1;
currentSlide = 1;
maxSlide = 1000;
llSetTexture("da79968e-b8b7-8a0a-cc66-2a717e9c3d41", 4);
}

touch_start(integer total_number)
{
if(lastSlideReached == 0) {
slideToDisplay = currentSlide + 1;
llMessageLinked(LINK_SET,slideToDisplay,"slideToDisplay",NULL_KEY);
} else {
llSay(0, "Last slide reached.");
}
}

link_message(integer sender_num, integer num, string str, key id) {

if(str == "currentSlide") {
currentSlide = num;
if(currentSlide < maxSlide) {
lastSlideReached = 0;
}
}

if(str == "lastSlideReached" && num == 1) {
lastSlideReached = 1;
maxSlide = currentSlide;
}
}

}


The screen itself checks first if a texture is available. If not it sets an test-texture (in this case: some color-circles i did, identified by the unique-key).




integer lastSlideReached;
integer firstSlideReached;
integer currentSlide;

default {

state_entry() {
// check for first slide and set it when available
if(llGetInventoryKey("1") != NULL_KEY) {
llSetTexture("1", 4);
} else {
llSetTexture("b13e131e-13a4-37ad-e137-4b6f81a0a3fa", 4);
}
}

link_message(integer sender_num, integer num, string str, key id) {

if(str == "slideToDisplay") {
string slideToDisplayStr = (string) num;

if(llGetInventoryKey(slideToDisplayStr) != NULL_KEY) {
llSetTexture((string)num, 4);
llMessageLinked(LINK_SET,num,"currentSlide",NULL_KEY);

// check if next slide exists
integer nextSlideNr = num+1;
string nextSlideStr = (string) nextSlideNr;
//llSay(0, "check for next slide nr " + nextSlideStr);
if(llGetInventoryKey(nextSlideStr) == NULL_KEY) {
lastSlideReached = 1;
llMessageLinked(LINK_SET,1,"lastSlideReached",NULL_KEY);
} else {
lastSlideReached = 0;
}
} else {
llSay(0, "Slide not available");
}
}
}
}

With some text-textures the screen will look like this:

Presentation screen

On the left side is the test-texture i did, if no slides are available.

Display external XML-Resources on objects in Second Life

If have played a little bit with Second Life and its ability to access external resources. Inspired by Matt Biddulph’s “flickr screen”, now i wanted to load xml data from an external source into the SL-world and display it on a nice looking screen. Because it is not possible — from my point of view — to display text directly on a SL-primitive, it was necessary to render the text in a first step to an image. I did this using a little RubyOnRails application which also loads and parses the xml-resource. Ok, now i show how i did this.
I used the REXML processor to work with the xml-data and RMagick to create the image. The xml-source is used is a little xml-file provided by LindenLabs which shows the land sales by resident for the last three month (http://secondlife.com/reports/marketplace_stats/2007-03-01/land_sales_by_residents.xml). The whole parsing is specific to this source.

# get the xml via http and read it into a variable and create a XML-Document after that
@content = Net::HTTP.get(URI.parse("
http://secondlife.com/reports/marketplace_stats/2007-03-01/land_sales_by_residents.xml"))
xmlDoc = REXML::Document.new(@content)

# get some attributes and select them with xpath
elRecords = REXML::XPath.first(xmlDoc, "records")
reportTitle = elRecords.attributes['report']
reportDate = elRecords.attributes['date']

# create a new image in the size you need. in my case it was 400 width and 200 height
f = Image.new(400,200) { self.background_color = "white" }# make some settings for the font
drawable = Magick::Draw.new
drawable.pointsize = 15.0
drawable.font_family = 'Helvetica'
drawable.fill = 'gray'
drawable.gravity = Magick::NorthWestGravity
drawable.font_weight = Magick::BoldWeight
drawable.annotate(f, 0, 0, 5, 4, "Second Life #{reportTitle} (#{reportDate})")
drawable.pointsize = 13.0
drawable.fill = 'black'
drawable.font_weight = Magick::NormalWeight

# set some position for the text i want to print
@startTopPos = 24

# go the records in the xml-document and print the values into the new image
REXML::XPath.each( xmlDoc, "records/record") do |element|
element.elements.each("field") do |field|
name = field.attributes['name']
value = field.attributes['value']
drawable.annotate(f, 0, 0, 5, @startTopPos, name)
drawable.annotate(f, 0, 0, 250, @startTopPos, value)
@startTopPos = @startTopPos + 15
end
@startTopPos = @startTopPos + 15
end

# write the image to the disk. i wasnt able to return it directly without saving. :(
f.write("result.png")
# return the image
createdImg = Magick::Image.read("result.png").first
@response.headers["Content-Type"] = "image/png"
@response.headers['Content-Disposition'] = %q{inline; filename="thepicture.png"}
render :text => createdImg.to_blob

This script returns the following image:

Second Life land sales

To put this image on an primitive i used the following linden script code:

touch_start(integer num_detected) {
llSay(0, "trying...");

if(llGetLandOwnerAt(llGetPos()) != llGetOwner()) {
llSay(0,"Error: Cannot modify parcel media settings.");
}

key video_texture = llList2Key(llParcelMediaQuery( [PARCEL_MEDIA_COMMAND_TEXTURE]), 0);

if(video_texture == NULL_KEY)
{
video_texture = VIDEO_DEFAULT;
llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_TEXTURE, VIDEO_DEFAULT]);
}
llSetTexture(video_texture,ALL_SIDES);
llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_URL,"http://###URL###"]);
llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_PLAY]);
llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_AUTO_ALIGN,TRUE]);
}

###URL### has to be replaced with the URL of the Ruby-Script.

Note: To use this ParcelMedia-feature you have to allow streaming media i think in SL.