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.

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.

4 comments:

Anonymous said...

Thanks for the nice post!

SuezanneC Baskerville said...

Would it be possible to use the online Ruby service Heroku to do the Ruby part of this sort of stuff?

daniel said...

Hi, i never heard of "Heroku" up to now. ;) But i think, yes. I should work.

Anonymous said...
This comment has been removed by a blog administrator.