Previous Section  < Day Day Up >  Next Section

Hack 40 Keep Tabs on People

figs/moderate.gif figs/hack40.gif

Keep track of when visitors to a channel last spoke and what they said.

People aren't always online and, even if they are, they aren't necessarily sitting in front of their computers. Getting a hold of your friends on IRC is difficult if you don't know if they are present and paying attention. Many people leave their IRC clients online all the time and do not set their Away status when they are absent, either on purpose or because they forget. In these cases, a good indicator of whether a user is likely to be present or not is to know when she was last active and what she last said.

If a user has recently said something, there is a good chance that he is still around, possibly reading your messages. If a user hasn't spoken for several hours, it is likely that he has popped out to the shops, gone to bed, or otherwise wandered away. Sometimes the last message written by the user confirms the suspicion.

6.2.1 !seen

Remembering when a user last spoke and what she said is a fairly trivial task. Some users include this kind of functionality in their IRC clients by means of third-party scripts or plug-ins.

A common way to invoke these scripts is to type !seen nickname. A typical response includes what the user last said and when he said it. However, for such functionality to be useful, it is important that the client is permanently connected to the IRC server. For this reason, it can be worth creating a standalone IRC bot that implements "!seen" functionality.

6.2.2 The Code

This bot will maintain a collection of nicknames that it has seen, along with what they last said and when they said it. Users will be allowed to query this information with the !seen command.

In Java, the HashMap class makes it easy to remember details pertaining to each nickname. The keys of the HashMap can be nicknames, and the values can contain the last message sent by each nickname and the time it was sent. So each time the bot sees anybody say something, it must put a new entry into the HashMap, overwriting any previous entry if necessary. When a user says !seen nickname, the bot can look up the nickname in the HashMap and quickly respond with the last message sent by that user. If there is no corresponding entry in the HashMap, this means the bot has not seen that user say anything.

This has been implemented in SeenBot.java :

import org.jibble.pircbot.*;

import java.util.*;



public class SeenBot extends PircBot {

    

    // This maps nicknames to when they were last seen and what they said.

    private HashMap lastSeen = new HashMap( );

    

    public SeenBot(String name) {

        setName(name);

    }

    

    public void onMessage(String channel, String sender,

            String login, String hostname, String message) {



        // Add this nickname and message to the lastSeen map.        

        lastSeen.put(sender.toLowerCase( ), getDate( ) + ", saying " + message);

        

        message = message.trim( ).toLowerCase( );

        

        // Check for people calling the !seen command.

        if (message.startsWith("!seen ")) {

            String nick = message.substring(6).trim( );

            String seen = (String) lastSeen.get(nick.toLowerCase( ));

            if (seen != null) {

                // Tell the channel when this nickname was last seen.

                sendMessage(channel, nick + " was last seen on " + seen);

            }

            else {

                sendMessage(channel, "I haven't seen " + nick + " on this server.");

            }

        }

    }

    

    private String getDate( ) {

        // Return the current date as a String.

        return new Date( ).toString( );

    }



}

Notice that the nicknames are converted to lowercase before being placed into the HashMap. This is because string comparisons are case sensitive when looking for keys in the HashMap.

This bot is capable of joining more than one channel. When a user issues a !seen command, it will respond with the most recent activity, regardless of which channel it was in.

Create a class in SeenBotMain.java to tell the bot to connect to a server and join some channels:

public class SeenBotMain {

    

    public static void main(String[] args) throws Exception {

        SeenBot bot = new SeenBot("SeenBot");

        bot.setVerbose(true);

        bot.connect("irc.freenode.net");

        

        // Join multiple channels.

        bot.joinChannel("#irchacks");

        bot.joinChannel("#test");

    }

    

}

6.2.3 Running the Hack

Compile the bot:

C:\java\SeenBot> javac -classpath pircbot.jar;. *.java

Run it:

C:\java\SeenBot> java -classpath pircbot.jar;. SeenBotMain

Figure 6-1 shows the SeenBot in action, responding to !seen commands in a channel.

Figure 6-1. SeenBot in action
figs/irch_0601.gif


6.2.4 Hacking the Hack

This bot will suffice for general use, but it does have some limitations at the moment. As stated earlier, such a bot is useful only if it is permanently connected to the IRC server. But let's face it, machines can crash and connections can sometimes drop. So some kind of persistence is required to be sure that the bot never misses a message.

One way of achieving persistence is to save the contents of the HashMap each time it changes. Writing the contents to a file means that they will still be accessible after a system crash or similar problem. Iterating through the HashMap and saving each entry in a plain text file is probably the best method, as it is easy to read. However, because String and HashMap both implement the Serializable interface, the easiest way is to use the ObjectOutputStream class from the java.io package.

Add this save method to SeenBot.java:

public void save( ) throws IOException {

    // Write the HashMap contents to the file brain.dat.

    FileOutputStream fileOut = new FileOutputStream("brain.dat");

    ObjectOutputStream out = new ObjectOutputStream(fileOut);

    out.writeObject(lastSeen);

    out.flush( );

    fileOut.close( );

}

Restoring the object is equally simple. Add this load method to SeenBot.java:

public void load( ) throws IOException, ClassNotFoundException {

    // Read the HashMap contents from the file brain.dat.

    FileInputStream fileIn = new FileInputStream("brain.dat");

    ObjectInputStream in = new ObjectInputStream(fileIn);

    lastSeen = (HashMap) in.readObject( );

    fileIn.close( );

}

Both of these methods will throw an IOException if something goes wrong. It would probably be a good idea to call the load method each time you start the bot and to call the save method every time the lastSeen HashMap is modified.

Another way of improving the bot is to make it aware of other IRC events—not all interactions are made using normal channel messages. Making the bot remember action, notice, join, and quit events will make the bot more useful. Here is an example of making the bot remember quit events:

public void onQuit(String sourceNick, String sourceLogin,

        String sourceHostname, String reason) {



    lastSeen.put(sourceNick.toLowerCase( ), getDate( ) +

            ", quitting from the server with the reason " + reason);

}

If you take care to observe the various ways a user can exhibit activity on an IRC network, you'll end up with a bot that can tell you where and when a user was last active—perhaps with even more accuracy than a human observer!

    Previous Section  < Day Day Up >  Next Section