oreilly.comSafari Books Online.Conferences.
Articles Radar Books  

A More Sensitive Mail Notifier
Pages: 1, 2

Sending Our Presence

Before the Jabber server will send us presence notifications from other JIDs, we must send our presence information. Along with sending a request to retrieve our roster (which is stored on the Jabber server), a client usually sends its (initial) presence as soon as it's connected and authenticated. In our case we don't really need to retrieve our roster as we're not going to do anything with the information, so all we do is send our presence, which as mentioned above, is represented by



<presence/>

To send our initial presence using Net::Jabber, we can call the PresenceSend() method of the Net::Jabber::Client class, like this:

$connection->PresenceSend();

Receiving the Presence of Others

So far, so good. But how do we request the presence for JIDs in our roster? Well, we don't, directly. The Jabber server sends them to us, as a result of us sending it our presence. So you could say that sending our presence is inherently the request. But the point is that we have to wait for the Jabber server to send us the presence information for anyone we've subscribed to.

Wait for? How? With the Process() method of the Net::Jabber::Client class. In a way, one could say that Process is to Net::Jabber as MainLoop is to Perl/Tk. Process() blocks either indefinitely (if no argument is provided) or for a number of seconds, e.g. Process(2) will block for two seconds and then return. Being from the Jabber server, the data will be in XML format, and will represent one of three main data types:

Related Articles:

Incoming Mail Notifier Using Jabber

The Jabber Jihad: Universal Instant Messaging With audio

Jabber Works: Here's How

XML Messaging With Jabber

Search O'Reilly Net for "jabber"

Instant Messaging topic area


More from the OpenP2P.com
  • presence information
  • message content
  • information/query data

We haven't covered the third type, but the first two should be familiar. What we are looking for is presence information data. But how do we know which data type we are receiving and how do we handle it? Net::Jabber provides us with a callback mechanism that allows us to set handlers, in the form of subroutines, for the different data types and it will call the relevant handler when data is received.

As we're only interested in presence information, we'll do the following:

Define Store for Presence Info

Before starting, define a hash to hold presence information received, keyed on JID:

use vars qw/%presence/;

Set Up Handler

After we're connected, set up a handler subroutine to process incoming presence data:

$connection->SetCallBacks( "presence" => \&handle_presence );

Define the handler subroutine thus:

sub handle_presence {
  my $presence = Net::Jabber::Presence->new(@_);
  my $jid = $presence->GetFrom(); 
  $jid =~ s!\/.*$!!;  # remove any resource suffix from JID
  $presence{$jid} = $presence->GetShow() || 'normal';
}

The handle_presence subroutine will pass data (@_) from the handler distributor, which is in turn passed directly to the constructor for a Presence object via the Net::Jabber::Presence class. This object affords access to all the presence information we require - who the presence is for (GetFrom(), which returns a "full" JID with potentially a trailing resource identifier, which we remove with the regular expression shown); and what their presence is (GetShow(), which is not set for "normal" presence - the default - which I therefore set to "normal" to have a nonfalse value in the hash entry).

Wait for Data

After the call to PresenceSend(), call the Process() method to wait for as many as 2 seconds for presence data to be sent from the Jabber server (for users we're subscribed to -- in notify@yak's case this is just dj@yak):

die "Uh-oh - something has gone wrong with the connection\n"
  unless(defined($connection->Process(2)));

Conditional Message

Now we have all the information we need to decide whether it's appropriate to send a notification to dj@yak - it's in the %presence hash, and we've decided to only construct and send the message if the presence for dj@yak is "normal" or "chat."

my $r = RECIPIENT;
if ($presence{$r} =~ /(normal|chat)/) {
  # code to construct and send message
}

If the Jabber server doesn't send us any presence information for the recipient, then it most likely means that the recipient isn't connected (presence information is only sent for connected Jabber users). This means that the hash entry for dj@yak won't exist, and the above condition will take care of the first what-if mentioned at the start of this article.

Summary

As an overview, the new version of the script in its entirety, with comments, is here:

# notify.pl
# Version 2
# Email notification script to Jabber client
# Only send notification if recipient is willing to receive

use strict;
use Mail::Internet;
use Net::Jabber;

# Hash to hold presence info received from Jabber server
# ------------------------------------------------------
use vars qw/%presence/;

# Declare our constants
# ---------------------
use constant RECIPIENT => 'dj@yak';      # Jabber ID to be notified
use constant SERVER    => 'yak';         # Jabber server to connect to
use constant PORT      => 5222;          # Port to connect to
use constant USER      => 'notify';      # user this script connects as
use constant PASSWORD  => 'notify';      # password associated with USER
use constant RESOURCE  => 'perlscript';

# Read and parse email and extract header info
# --------------------------------------------
my $header = Mail::Internet->new(*STDIN)->head()->header_hashref();
chomp $header->{$_}[0] foreach keys(%{$header});

# Create a new Jabber client and connect
# --------------------------------------
my $connection = Net::Jabber::Client->new();
$connection->Connect( "hostname" => SERVER,
                      "port"     => PORT )
   or die "Cannot connect ($!)\n";

# Identify and authenticate with the server
# -----------------------------------------
my @result = $connection->AuthSend( "username" => USER,
                                    "password" => PASSWORD,
                                    "resource" => RESOURCE );
if ($result[0] ne "ok") {
  die "Ident/Auth with server failed: $result[0] - $result[1]\n";
}

# Set presence callback
# ---------------------
$connection->SetCallBacks( "presence" => \&handle_presence );

# Send our presence
# -----------------
$connection->PresenceSend();

# Wait for data
# -------------
die "Uh-oh - something has gone wrong with the connection\n"
  unless(defined($connection->Process(2)));

# Send notification only if appropriate
# -------------------------------------
my $r = RECIPIENT;
if ($presence{$r} =~ /(normal|chat)/) {

  # Create a message and build it up
  # --------------------------------
  my $msg = Net::Jabber::Message->new();
  $msg->SetMessage( "to"      => RECIPIENT,
                    "subject" => "Email from $header->{From}[0]",
                    "body"    => join("\n", "Subject: $header->{Subject}[0]",
                                            "Date: $header->{Date}[0]") );
  # Send the message
  # ----------------
  $connection->Send($msg);

}

# Disconnect from the Jabber server
# ---------------------------------
$connection->Disconnect();

exit;



# Presence data handler
# ---------------------
sub handle_presence {
  my $presence = Net::Jabber::Presence->new(@_);
  my $jid = $presence->GetFrom(); 
  $jid =~ s!\/.*$!!;  # remove any resource suffix from JID
  $presence{$jid} = $presence->GetShow() || 'normal';
}

You can see that with not many changes, the simple e-mail notification script has become much more useful. This is primarily due to the great implementation of the Jabber protocol in Net::Jabber, and the design of Jabber itself.

A Note on Net::Jabber 1.0021

The Perl script in this article uses the Net::Jabber module version 1.0020. There is a newer version (1.0021) that has recently become available on CPAN.

If you wish to use this newer version, there are two things you must bear in mind:

  • Extra data passed to callback routines

    The CallBack() method in version 1.0021 of Net::Jabber's Protocol class now delivers an extra piece of data to the callback subroutines in your code. The extra piece of data is a scalar representing a Jabber session ID, which is part of the bigger picture that Net::Jabber is moving toward for the future.

    In the case of our script here, we have one callback subroutine - handle_presence(). If you want to use version 1.0021 of Net::Jabber the extra piece of data must be shifted off the parameter stack before calling the Net::Jabber::Presence constructor (new()), so do this:

    Avoid confusion by clearly stating your requirement to use version 1.0021:

    use Net::Jabber 1.0021;
    

    Modify the callback(s) using shift:

    # Presence data handler
    # ---------------------
    sub handle_presence {
      shift; # remove the Session ID from the parameter stack
      my $presence = Net::Jabber::Presence->new(@_);
      my $jid = $presence->GetFrom(); 
      $jid =~ s!\/.*$!!;  # remove any resource suffix from JID
      $presence{$jid} = $presence->GetShow() || 'normal';
    }
    

    Failure to remove the session ID before calling the constructor will result in problems at runtime as the constructor is not expecting this scalar Jabber session ID.

  • Debug output appears on STDOUT

    The IQ class in Net::Jabber 1.0021 contains a method, MergeX(), that has a rogue debug line that prints debug output to the screen. The quickest way to turn this off is to comment out the line that does this; it's line 642 in Net/Jabber/IQ.pm and looks like this:

    &Net::Jabber::printData("old: \$self->{IQ}",$self->{IQ});
    

Of course, it's not necessary to upgrade to version 1.0021 to run the scripts in this series - you can happily stay with version 1.0020.

Final Words

This example, across the two articles, has only really scratched the surface of what is possible with small scripts and the Jabber system. I hope it has inspired you to create your own solutions and systems using Jabber.


DJ Adams is the author of O'Reilly's Programming Jabber book.


Discuss this article in the O'Reilly Network General Forum.

Return to OpenP2P.com.

 





P2P Weblogs

Richard Koman Richard Koman's Weblog
Supreme Court Decides Unanimously Against Grokster
Updating as we go. Supremes have ruled 9-0 in favor of the studios in MGM v Grokster. But does the decision have wider import? Is it a death knell for tech? It's starting to look like the answer is no. (Jun 27, 2005)

> More from O'Reilly Developer Weblogs


More Weblogs
FolderShare remote computer search: better privacy than Google Desktop? [Sid Steward]

Data Condoms: Solutions for Private, Remote Search Indexes [Sid Steward]

Behold! Google the darknet/p2p search engine! [Sid Steward]

Open Source & The Fallacy Of Composition [Spencer Critchley]