The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Net::Chat::Daemon - run a daemon that is controlled via instant messaging

ABSTRACT

This package is intended to serve as a superclass for objects that want to communicate via IM messages within a distributed network of client nodes and a coordinator, without dealing with the complexities or implementation details of actually getting the messages from place to place.

It pretends to be protocol-neutral, but for now and the conceivable future will only work with a Jabber transport. (It directly uses the message objects and things that Jabber returns.)

Note that this package will NOT help you implement an instant messaging server. This package is for writing servers that communicate with other entities via instant messages -- servers written using this package are instant messaging *clients*.

SYNOPSIS

  package My::Server;
  use base 'Net::Chat::Daemon';
  sub handleHello {
    return "hello to you too";
  }
  sub handleSave {
    my ($filename, $file) = @_;
    return "denied" unless $filename =~ /^[.\w]+$/;
    open(my $fh, ">/var/repository/$filename") or return "failed: $!";
    print $fh $file;
    close $fh or return "failed: $!";
    return "ok";
  }
  sub someMethod {
    my ($self, @args) = @_;
    .
    .
    .
  }
  sub new {
    my ($class, $user, %options) = @_;
    return $class->SUPER::new(%options,
                              commands => { 'callMethod' => 'someMethod',
                                            'save' => \&handleSave });
  }

  package main;
  my $server = My::Server->new('myuserid@jabber.org');
  $server->process();

  # or to do it all in one step, and retry connections for 5 minutes
  # (300 seconds) before failing due to problems reaching the server:

  My::Server->run('myuserid@jabber.org', retry => 300);

When you run this, you should be able to send a message to userid@jabber.org saying "hello" and get a response back, or "callMethod a b c" to call the method with the given arguments. To use the "save" command, you'll need to use a command-line client capable of sending attachments in the format expected by this server (it currently does not use any standard file-sending formats). The jabber command packaged with this module can do this via the -a command-line option.

A note on the implementation: when I first wrote this, it was really only intended to be used with Jabber. The code hasn't been fully restructured to remove this assumption.

WARNING

The Net::Chat::Daemon name is most likely temporary (as in, I don't like it very much, but haven't come up with anything better.) So be prepared to change the name if you upgrade.

API

new($user, %options)

To implement a server, you need to define a set of commands that it will respond to. See getHandler, below, for details on how commands are registered. The part that's relevant to this method is that you can pass in a commands option, which is a hash ref mapping command names to either subroutines or method names. When the server receives a message, it will carve up the message into a command name and whitespace-separated arguments. See onRequest, below, for details.

Methods that are invoked by being values in the commands hash will also be given the usual $self parameter at the beginning of the parameter list, of course.

The $user argument to the new() method is something like jabber://userid@jabber.org/someresource or just userid@jabber.org/someresource (who are we kidding?) Theoretically, this allows a future subclass to work with yahoo://userid, but don't hold your breath.

run($user, %options)

Create a daemon with the given options, and loop forever waiting for messages to come in. If the IM system dies, exit out with an error unless the 'retry' option is given, in which case it will be interpreted as the maximum number of seconds to retry, or zero to retry forever (this is often a good idea.)

If you want your server to exit gracefully, define your own command that calls exit(0).

push_callback($type, $callback, [$id])
unshift_callback($type, $callback, [$id])
remove_callback($type, $id)

Add or remove callback for the event $type. remove_callback() is only useful if an $id was passed into push_callback or unshift_callback.

Valid types: message available unavailable error

onMessage($msg, %extra)

This method will be invoked as a callback whenever a regular chat message is received. The default implementation is to relay the message to onRequest, but this may be overridden in a subclass to distinguish between the two.

onReply($msg, %extra)

This method will be invoked as a callback whenever a chat message is received in reply to a previous request. The default implementation is to relay the message to onMessage above, but this may be overridden in a subclass to distinguish between the two.

setCommand($name, $command)

Set the callback associated with a command. If a string is passed in, it will be treated as a method on the current object (the object that setCommand was called on). The arguments to the method will be the words in the command string. If a closure is passed in, it will be invoked directly with the words in the command string. The $self object will not be passed in by default in this case, but it is easy enough to define your command like

  $x->setCommand('doit' => sub { $x->doit(@_) })

Note that all commands are normally set up when constructing the server, but this method can be useful for dynamically adding new commands. I use this at time to temporarily define commands within some sort of transaction.

getHandler($name)

Get the handler for a given command. The normal way to do this is to pass in a 'commands' hash while constructing the object, where each command is mapped to the name of the corresponding method.

Alternatively, you can simply define a method named handleSomething, which will set the command 'something' (initial letter lower-cased) to call the handleSomething method. (So 'handleSomeThing' would create the command 'someThing'.)

Also, if you ask for help on a command, it will call the method 'helpXxx' where 'xxx' is the name of the command. If no such method exists, the default response will be "(command) args..." (accurate but hardly helpful).

showHelp([$command])

Return a help message listing out all available commands, or detailed help on the one command passed in.

onRequest($msg, %extra)

This method will be invoked as a callback whenever a request is received. As you know if you've read the documentation for onMessage and onReply, by default all messages go through this handler.

The default implementation of onRequest parses the message into a command and an array of arguments, looks up the appropriate handler for that command, invokes the handler with the arguments, then sends back a reply message with the return value of the handler as its text.

If any files are attached to the message, they are extracted and appended to the end of the argument list.

An example: if you send the message "register me@jabber.org ALL" to the server, it will look up its internal command map. If you defined a handleRegister method, it will call that. Otherwise, if you specified the command 'register' in the commands hash, it will call whatever value if finds there. Two arguments will be passed to the handler: the string "me@jabber.org", and the string "ALL".

checkMaster($sid, $presence)

Internal: presence unavailable callback - exit if the master exited

process([$timeout])

Wait $timeout seconds for more messages to come in. If $timeout is not given or undefined, block until a message is received.

Return value: 1 = data received, 0 = ok but no data received, undef = error

waitUntilAllHere($nodes)

This method is used for things like test harnesses, where you might want to wait until a set of nodes are all alive and active before starting the test case. You pass in a list of users, and this method will wait until all of them have logged into the server.

Implementation: wait until receiving presence notifications from the given list of nodes. Works by temporarily adding new presence callbacks, and periodically pinging nodes that haven't come up yet.

Arguments: $nodes - reference to an array of user descriptors (eg jids)

I suppose I ought to add a timeout argument, but right now, this will block until all nodes have reported in.

onSyncLogin($sid, $presence)

Callback used when synchronizing with a bunch of nodes. Notified when someone logs in who we care about.

onSyncLogout($sid, $presence)

If a node disappears while we are waiting for everyone to gather, then re-set its waiting flag.

onSyncError($sid, $message)

Watch for 404 errors coming back while waiting for all nodes to be present.

SEE ALSO

Net::Chat::Jabber, Net::Jabber, Net::XMPP

AUTHOR

Steve Fink <sfink@cpan.org>

Send bug reports directly to me. Include the module name in the subject of the email message.

COPYRIGHT AND LICENSE

Copyright 2004 by Steve Fink

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.