I wanted to try my hand at scripting some RPC between in-world and off-world services, so I implemented an online status indicator that I could include on a web site somewhere that would display an "I'm online" or "I'm offline" message as appropriate.
This is a script that you drop into any in-world object. It opens up an RPC channel and listens for a request, and it simply returns a "1" if the object's owner is in-world, or "0" if not.
You obviously need code on a server somewhere to talk to it. I've included sample code in python at the end of the script.
This is my first RPC script, so I'd appreciate it if someone with some experience could look it over and make sure I'm doing things correctly. Specifically, I am not certain if I'm closing the RPC channel at the right time. Also, there is no real error-handling code. Not sure if I'm catching everything that needs to be caught.
And, the way I set it up is that you manually query the script for its RPC channel ID, then include that on your server-side code. I am not certain how long this ID will be valid. I know that if you change the script, or drop it into a different object, the ID will change. Anything else I should be aware of?
Any other suggestions are welcome, too!
Thanks!
--Kitto
CODE
// Remote Online Status Service
// remote-online-status.lsl
//
// Version: 0.1
//
// IM: Kitto Mandala
// February, 2005
// Copyright (c) 2005 Kitto Mandala
//
// Place script in any in-world object. Will register and respond to RPC
// messages to determine the object owner's online status. For RPC client
// to work properly, it will need the key of this script's RPC channel. See
// LISTEN_COMMAND and the related listen() event for getting the key.
//
// NOTE: This script does not work on its own! You'll need code somewhere
// in the real-world to talk to Linden's RPC service, and interpret the
// response. See the bottom of this script for sample python code.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// CONSTANTS
// say: "/24 key" to ask script to dump the key of its rpc channel
// change this channel and command to something unique if you use
// this script
integer LISTEN_CHANNEL = 24;
string LISTEN_COMMAND = "key";
// rpc response codes
integer RPC_OK = 0;
integer RPC_ERROR = 1;
// GLOBAL VARIABLES -- grr, hate these
// rpc listen channel
key gChannel;
// -----------------------------------------------------------------------
// wrappers to send/receive agent data
// I use a wrappers to minimize the use of global variables which
// clutter the namespace and make programs hard to maintain. These
// *are* global variables, but you shouldn't use them outside of their
// intended scope, which is the following two functions.
key _agent_data_query_id;
key _agent_data_channel;
key _agent_data_message_id;
agent_data_request(key who, integer data, key channel, key message_id) {
_agent_data_channel = channel;
_agent_data_message_id = message_id;
_agent_data_query_id = llRequestAgentData(llGetOwner(), DATA_ONLINE);
}
agent_data_receive(key query_id, string data) {
if (query_id == _agent_data_query_id) {
llRemoteDataReply(_agent_data_channel, _agent_data_message_id, data, RPC_OK);
}
}
// -----------------------------------------------------------------------
default {
state_entry() {
llOpenRemoteDataChannel();
}
remote_data(integer type, key channel, key message_id,
string sender, integer ival, string sval) {
if (type == REMOTE_DATA_CHANNEL) {
// channel created
gChannel = channel;
state Listening;
}
}
}
state Listening {
state_entry() {
llListen(LISTEN_CHANNEL, "", NULL_KEY, LISTEN_COMMAND);
llWhisper(0, "Now listening...");
}
remote_data(integer type, key channel, key message_id,
string sender, integer ival, string sval) {
if (type == REMOTE_DATA_REQUEST) {
// got an rpc request
agent_data_request(llGetOwner(), DATA_ONLINE, channel, message_id);
}
}
dataserver(key query_id, string data) {
agent_data_receive(query_id, data);
}
listen(integer channel, string name, key id, string message){
if (id == llGetOwner() && message == LISTEN_COMMAND) {
llWhisper(0, "My rpc channel is: " + (string) gChannel);
}
else {
llWhisper(0, "You are not the owner of this script.");
}
}
state_exit() {
llCloseRemoteDataChannel(gChannel);
}
}
// -----------------------------------------------------------------------
// Sample python code for your server
//
// You'll probably want to use this somewhere when responding to an img
// request, and return the appropriate "online" or "offline" image.
// def get_online_status(key_id):
// """Get the online status of a user by talking to the remove-online-status.lsl
// script identified by key_id. Returns True if user is online, False if not.
// There is no robust error-handling; if there's a communication problem, will
// simply return False.
// """
//
// from xmlrpclib import ServerProxy
//
// client = ServerProxy("http://xmlrpc.secondlife.com/cgi-bin/xmlrpc.cgi")
// try:
// reply = client.llRemoteData({'Channel': key_id, 'IntValue': 0, 'StringValue': ''})
// except:
// # problem with parameters or other communication problem
// # you might want to catch specific exceptions here while you're debugging
// reply = {}
//
// # assume offline status
// status = None
// try:
// if reply['IntValue'] == 0: # no error
// status = reply['StringValue']
// except KeyError:
// # we didn't get a valid reply
// pass
//
// if status == '1':
// # note that this is a string comparison, not integer
// return True
// else:
// return False