Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: IRC Hacks

Hacking IRC, Part 2

Author's note: If you're a hardened IRC addict, you'll no longer have to start up a separate process to perform a mathematical calculation, nor will you have to scramble across your desk to find a real calculator. The answers can lie much closer to home on IRC.

Moderate Hack #61

Perform Feats of Math


Lose your calculator. Evaluate mathematical expressions with a bot that uses the Java Expression Parser.

How many times have you been desperate to find out the answer to a simple sum, only to discover that you can't remember where you left your calculator? One solution could be to fire up a calculator application on your computer, but for IRC users, a solution can be found even closer to home.

Java Expression Parser

The Java Expression Parser (JEP) is an excellent tool for parsing and evaluating mathematical expressions. This is ideal for use by bots, because it can take a string as input, parse it, and evaluate the answer. This hack is based on the 2.24 release, which is available for free at http://www.singularsys.com/jep.

JEP input can contain the usual +, -, *, and / symbols, along with ^ to raise powers. JEP contains a set of standard constants, such as pi, e, and i, and a standard set of functions, such as sqrt, sin, abs, and so on. JEP supports implicit multiplication, where 2pi is automatically interpreted as meaning 2* pi. Expressions like sqrt(-1) will not break JEP, as it can handle complex arithmetic and give answers with both a real and imaginary part.

The Code

This bot will make use of the JEP and PircBot packages. It will respond to any channel messages of the form calcexpression. If it is able to parse the expression correctly, it will give the answer; otherwise, it will say it is unable to do so.

Save the following code as MathBot.java:

import org.jibble.pircbot.*;
import org.nfunk.jep.*;

public class MathBot extends PircBot {
    
    // This JEP object will be used for the calculations.
    private JEP jep;
    
    public MathBot(String name) {
        setName(name);

        // Set up the JEP object's capabilities.
        jep = new JEP( );
        jep.addStandardConstants( );
        jep.addStandardFunctions( );
        jep.setAllowUndeclared(false);
        jep.setImplicitMul(true);
        jep.addComplex( );
    }
    
    public void onMessage(String channel, String sender,
            String login, String hostname, String message) {
    
        message = message.trim( ).toLowerCase( );

        // Check for the "calc" command.
        if (message.startsWith("calc ")) {
            String expression = message.substring(5);
            
            // Default answer.
            String answer = "Unable to parse your input.";
            
            try {
                jep.parseExpression(expression);
                if (!jep.hasError( )) {
                    String real = String.valueOf(jep.getValue( ));
                    String complex = String.valueOf(jep.getComplexValue( ));
                    answer = real;
                    
                    // Remove the decimal point if the number is integral.
                    if (real.endsWith(".0")) {
                        answer = real.substring(0, real.length( ) - 2);
                    }
                    
                    if (!complex.endsWith(" 0.0)")) {
                        answer = "Complex " + complex;
                    }
                }
            }
            catch (Exception e) {
                // Do nothing.
            }
            
            sendMessage(channel, sender + ": " + answer);
        }
        
    }

}

A main method is required to create the bot and tell it to join a channel; we'll call it MathBotMain.java:

public class MathBotMain {
    
    public static void main(String[] args) throws Exception {
        MathBot bot = new MathBot("MathBot");
        bot.setVerbose(true);
        bot.connect("irc.freenode.net");
        bot.joinChannel("#irchacks");
    }
    
}

Running the Hack

Compile the bot:

C:\java\MathBot> javac -classpath jep-2.24.jar;pircbot.jar;. *.java

Run it like so:

C:\java\MathBot> java -classpath jep-2.24.jar;pircbot.jar;. MathBotMain

You can see the bot in action in Figure 9-4.


Figure 9-4. Using MathBot in a channel

Author's note: Many people use IRC bots to act as interfaces to other systems or protocols. This article shows you how to write an IRC bot that constantly monitors a news server, waiting for new posts. When a new post turns up, it will alert everyone in the IRC channel.

Expert Hack #65

Announce Newsgroup Posts


Moderated newsgroups are updated only every so often. Have an IRC bot check for new posts instead of wasting your time.

Usenet discussion groups (or newsgroups, as some like to call them) form a worldwide bulletin board system that can be accessed through the Internet. A user can post a message to a newsgroup that will then be seen by anyone else who reads that newsgroup.

Some newsgroups have very infrequent postings or may even be moderated. Moderated newsgroups require a moderator to approve all postings before they end up on the newsgroup. In either case, the only way you can see whether there are new posts is to actually open up your newsreader and take a look. This is a waste of time if there aren't any new messages, so why not make an IRC bot to do it for you?

Connecting to a News Server

Newsreaders communicate with newsgroup servers with NNTP (Network News Transfer Protocol). This is a text-based protocol and is quite easy to understand, so it's not too difficult to make a bot that talks to a newsgroup server. The default port for NNTP is 119.

You can try using Telnet to connect to port 119 of a newsgroup server and issue the GROUPnewsgroup command. If the group exists, the server will reply with a 211 response, showing how many messages there are, followed by the range of the message IDs. You can then request information about all of these posts by entering XOVERlower-upper, where lower and upper define the range of message IDs to request. Figure 10-2 shows a request for the last three messages from the newsgroup alt.irc:


Figure 10-2. Connecting to a newsgroup server with Telnet

The Code

Now that you know how to request message details via NNTP, you can encapsulate this into a separate class that is responsible for getting these details. This class will use its count field to keep track of the most recent message on the newsgroup, so each time it receives a response to the XOVER command, it can tell if new messages have arrived. The getNewSubjects method will then be responsible for returning an array of these new messages.

Create the file NntpConnection.java:

import java.util.*;
import java.net.*;
import java.io.*;

public class NntpConnection {

    private BufferedReader reader;
    private BufferedWriter writer;
    private Socket socket;
    private int count = -1;
    
    public NntpConnection(String server) throws IOException {
        socket = new Socket(server, 119);
        reader = new BufferedReader(
                new InputStreamReader(socket.getInputStream( )));
        writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream( )));
        reader.readLine( );
        writeLine("MODE READER");
        reader.readLine( );
    }
    
    public void writeLine(String line) throws IOException {
        writer.write(line + "\r\n");
        writer.flush( );
    }
    
    public String[] getNewSubjects(String group) throws IOException {
        String[] results = new String[0];
        
        writeLine("GROUP " + group);
        String[] replyParts = reader.readLine( ).split("\\s+");
        if (replyParts[0].equals("211")) {
            int newCount = Integer.parseInt(replyParts[3]);
            
            int oldCount = count;
            if (oldCount == -1) {
                oldCount = newCount;
                count = oldCount;
            }
            else if (oldCount < newCount) {
                writeLine("XOVER " + (oldCount + 1) + "-" + newCount);
                if (reader.readLine( ).startsWith("224")) {
                    LinkedList lines = new LinkedList( );
                    String line = null;
                    while (!(line = reader.readLine( )).equals(".")) {
                        lines.add(line);
                    }
                    results = (String[]) lines.toArray(results);
                    count = newCount;
                }
            }
        }
        return results;
    }
    
}

The IRC bot will be written so that it spawns a new Thread to continually poll the newsgroup server. Performing this checking in a new Thread means that the bot is able to carry on doing essential tasks like responding to server pings. This new Thread is able to send messages to the IRC channel, as the sendMessage method in the PircBot class is thread-safe.

The bot will also store the time it last found new articles and made an announcement. If it has been less than 10 minutes since the last announcement, the bot will not bother saying anything. This is useful when lots of messages are arriving on a moderated newsgroup, as these tend to arrive in large clusters in a short time.

Create the bot in a file called NntpBot.java:

import org.jibble.pircbot.*;

public class NntpBot extends PircBot {

    private NntpConnection conn;
    private long updateInterval = 10000; // 10 seconds.
    private long lastTime = 0;
    
    public NntpBot(String ircServer, final String ircChannel, final String
            newsServer, final String newsGroup) throws Exception {
        setName("NntpBot");
        setVerbose(true);
        setMessageDelay(5000);
        
        conn = new NntpConnection(newsServer);
        
        connect(ircServer);
        joinChannel(ircChannel);
        
        new Thread( ) {
            public void run( ) {
                boolean running = true;
                while (running) {
                    try {
                        String[] lines = conn.getNewSubjects(newsGroup);
                        if (lines.length > 0) {
                            long now = System.currentTimeMillis( );
                            if (now - lastTime > 600000) {  // 10 minutes.
                                sendMessage(ircChannel, "New articles posted 
to " + newsGroup);
                            }
                            lastTime = now;
                        }
                        for (int i = 0; i < lines.length; i++) {
                            String line = lines[i];
                            String[] lineParts = line.split("\\t");
                            String count = lineParts[0];
                            String subject = lineParts[1];
                            String from = lineParts[2];
                            String date = lineParts[3];
                            String id = lineParts[4];
                            // Ignore the other fields.
                            sendMessage(ircChannel, Colors.BOLD +
                                    "[" + newsGroup + "] " + subject +
                                    Colors.NORMAL + " " + from + " " + id);
                        }
                        try {
                            Thread.sleep(updateInterval);
                        }
                        catch (InterruptedException ie) {
                            // Do nothing.
                        }
                    }
                    catch (Exception e) {
                        System.out.println("Disconnected from news server.");
                    }
                }
            }
        }.start( );
    }
    
}

Note that the Thread is started from the NntpBot constructor and no PircBot methods are overridden—there is no need for this bot to respond to user input, unless you want to modify it to do so.

The main method now just has to construct an instance of the bot, as the constructor also tells the bot to connect to the IRC server.

Create the main method in NntpBotMain.java:

public class NntpBotMain {
    
    public static void main(String[] args) throws Exception {
        NntpBot bot = new NntpBot("irc.freenode.net", "#irchacks",
                "news.kent.ac.uk", "ukc.misc");
    }
    
}

Note that the constructor arguments specify which IRC server to connect to, which channel to join, which newsgroup server to connect to, and which newsgroup to monitor. If you want, you could make the bot more flexible by using the command-line arguments (args) to specify the name of the server, channel, and so forth.

Running the Hack

Compile the bot like so:

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

You can then run the bot by entering:

C:\java\NntpBot> java -classpath .;pircbot.jar NntpBotMain

The Results

When you run the bot, it will connect to the IRC server and join the channel you specified. Each time a new post appears in the newsgroup, the bot will announce the post details, as shown in Figure 10-3. These details include title, author, email address, and message ID.


Figure 10-3. Running NntpBot on a local newsgroup server

Now you can keep an eye on your moderated newsgroups without having to keep your news client running in the background.

Author's note: Even if you're a busy person, there's still no need to have hundreds of console windows open at the same time. Console-based IRC clients are perfectly amenable to being used with the GNU screen tool, and can even help you hide your IRC windows from your boss.

Moderate Hack #92

Use IRC Within screen


If you're regularly on the move, you need a way to keep track of IRC while away from your computer. Run a console-based IRC client in screen as a simple yet powerful solution.

If you're running a text mode (console) IRC client on a remote system, it can be annoying having to reconnect if your connection drops or if you have to move to another machine. When you reconnect, you will no longer see the messages that were sent when you were last connected.

GNU screen provides a neat solution to this problem. It allows you to disconnect from a terminal session without quitting your running programs. You can then log in and resume the screen session at a later time, giving the appearance that you never disconnected.

screen is provided as a package on most Unix-based systems. If it isn't already installed, install the screen package or download and install it from source at http://www.gnu.org/software/screen/screen.html.

Starting screen is amazingly simple, yet many people overlook the usefulness of it. At a shell prompt, simply type:

% screen

If you get a startup message, just press Enter. You should then see a shell prompt. This is just like any other shell, but with one difference—every time you press Ctrl-A, it will be intercepted by screen. All of screen's commands are accessed by typing a different letter after this key. screen provides a short summary of the commands if you press Ctrl-A followed by the ? key. These combinations are often abbreviated to the form ^A ?.

Probably the most useful command is the one that lets you "detach" from a screen session. Typing ^A d will detach your session, leaving the programs running inside the screen just as they were. To reattach to the session, just type:

% screen -r

You should now see the screen as you left it. You can also log out completely, and later log back in and reattach. By default, screen will also detach sessions when the terminal is closed, so screen sessions survive network connections dying and closing the terminal window. If for some reason your connection dies and the screen isn't detached, screen -r is not enough to reattach. You will need to run the command screen -r-d to detach and then reattach. Also, if you are running more than one screen, you need to give the pid (process ID) or name of the screen process that you want to reattach to after the -r parameter.

TIP: If you are using the BitchX IRC client, detaching is even easier. Simply type /detach to detach your client, then run the scr-bx command to bring your session back again. However, this feature is nowhere near as powerful as screen and won't detach automatically.

screen's other great strength is that it lets you run more than one program inside one terminal window. This makes it easy to leave several programs running and access all of them from another location, even if you are restricted to a very slow connection. This is achieved by supporting virtual windows inside the screen session. You can create a new window by pressing ^A c. Once you have more than one window, you can use ^A n or ^A <space> to go to the next window, and ^A p to go to the previous window. This feature is made even more flexible because screen allows windows to be split. This means you can see more than one window on the screen at a time. To split the screen, type ^A S. This one is case sensitive, so you will need to hold down the Shift key as well. This splits the window into two, and you should see a new blank window in the bottom half of the screen. Pressing ^A <tab> will change to this new window, and you can either change to another existing window by pressing ^A n, or you can create a new window. If you want to get rid of the split windows, ^A Q will hide all the inactive windows.

The screenshot in Figure 14-8 shows screen with a split window, displaying irssi in a channel where system logs are sent to IRC, and the screen manual page in the bottom half.


Figure 14-8. Screen with a split window

If you have played with the split-window feature, you may have noticed you can have a window visible in several split windows at the same time. This is actually a very useful feature because screen allows you to attach to a screen session more than once. This is called multiple display mode, and you can use it to display the same window on multiple terminals, or you can display a different window on different terminals. To use it, simply add the -x option to the reattach command, so it becomes:

% screen -r -x

screen also has support for copy and paste from one window to another. Type ^A [ to start the copy, move the cursor with the arrow keys, and press Enter to start copying; then move the cursor to the end of the text you want to copy and press Enter again. The text that you have copied will be stored in memory until you use ^A ] to paste it. When you are selecting the text, there are some other keys that you can use. For example, pressing / will allow you to search within the text buffer, and Page Up and Page Down will scroll a full screen.

More relevant to IRC is a script that checks that your IRC client is running so you don't even have to manually restart if it crashes or if the system you're running it on is rebooted.

This makes use of the cron facility found on most Unix systems, along with a little bit of Bourne shell scripting.

To edit your user's crontab, run this command:

% crontab -e

You can then create a new line in your crontab:

*/5 * * * * IRC=`screen -ls | grep -v Dead | grep "\\.irc"`;
    if [ "x$IRC" = "x" ]; then screen -dmS irc irssi;fi

This causes the script to be run every five minutes. When it runs, it checks the output of screen -ls for a session called irc. If it doesn't find it, it starts screen in detached mode (with the options -dm) and names the session irc (option -S). screen will run the command irssi once it has started. If you want to use a different IRC client, you could replace the irssi with whatever you use to start your IRC client.

screen also has a command line as well as key shortcuts. You can access the command line via ^A :. For example, to change the title of a window (this is useful when you're using split screens), you would type ^A :titlenew-title.

If you are paranoid about security, you can password-protect reattaching to your screen by running the password command on the screen command line. You can do this by typing ^A :password and following the prompts. If you want to make this permanent after setting the password, edit the file ~/.screenrc (create it if it doesn't exist) and type password followed by ^A ] (this pastes the contents of the paste buffer). Your line should look something like password NSQuRKGNxIEbw. Whenever someone runs screen-r from now on, they will be prompted for the password. The security provided by this is in addition to that provided by your login password, but it won't deter someone who is determined to get past if they have access to your system account.

There isn't enough room to cover all of screen's features here; however, screen has a very good manual page so man screen will tell you lots more, such as how to remap keys to suit your tastes and how to allow multiple users to share a screen session. With screen, it's easy to run multiple IRC clients and access them from anywhere in the world.

Quick key reference:

^A c

Create window.

^A d

Detach.

^A n or ^A <space>

Next window.

^A p or ^A <backspace>

Previous window.

^A <number key>

Change to that window number.

^A [ or ^A <escape>

Start a copy.

^A ]

Paste copy buffer.

^A S

Split window.

^A Q

Hide inactive windows.

^A :

Enter a screen command.

To get a quick list of all of screen's key bindings, press ^A? at any time.

David Leadbeater

Copyright © 2009 O'Reilly Media, Inc.