Therapy Bots

The Perl Journal August 2003

By Moshe Bar

Moshe is a systems administrator and operating-system researcher and has an M.Sc. and a Ph.D. in computer science. He can be contacted at moshe@moelabs.com.

Ihave always liked the ideas of bots (Internet robots) wading through the Internet and working for us humans. Things get especially interesting when you give bots the gift of artificial intelligence (AI).

For many people, AI is still synonymous with Eliza, the therapist-feigning program written by Josef Weizenbaum in the mid '60s. Weizenbaum's Eliza was an attempt at fooling the Turing Test. The test itself is fairly complex, but it is usually given in this simplified form:

A person sits in front of two terminals (screens with keyboards). One of these terminals lets the person communicate with a real flesh-and-blood human, the other with a computer program. The person can type any question to either/both of the two entities at the other end of the terminals—there is no restriction on what the person can ask! If, after a certain period (say, an hour), the person can't tell which terminal leads to the human and which to the computer, then the computer program could be referred to as intelligent.

Now, I often have to use IRC chat to manage my OpenSource project openMosix (see http://www.openmosix.org/). People usually have quite serious and intelligent discussions in our channel. But if you have ever logged into one of the more general chats, you might have noticed the almost total absence of intelligence in certain discussions. So, while computers have not really become more intelligent (in any substantial form) in these last 40 years, it seems that certain layers of society have collectively become less intelligent. One might be inclined to suspect that a computer might soon pass the Turing Test just because the human on the other side is more stupid than the program.

I decided to test this assertion by writing a Perl program to log into IRC channels and pretend to be a human, while I watch the resulting comical onslaught from my IRC chat client.

There are some ready-made bots in Perl out there on the Internet, but all the fun is in writing these things and seeing them run. There are also plenty of Perl modules which manage connections to IRC chats. So, if you have a favorite one, use that instead of the raw IO::Socket connections, which I prefer to use because they give me more control over what is happening with the connection.

So let's write a simple IRC bot. Usually, I start things with establishing a connection. Something like this:

#!/usr/bin/perl
use IO::Socket;
$sock = IO::Socket::INET->new(
        PeerAddr => 'irc.freenode.net', 
        PeerPort => 6667, 
        Proto => 'tcp' ) or die "could establish a connection"; 

Any public IRC server can be used here. I use irc.freenode.net. By standard, all connections go to port 6667-7000. If you have problems, try to find a different server on that network. To make things easier, you can make the PeerAddr a variable, which is specified by an argument from the command line.

Once we have established a connection to a server, we need to login to the IRC server. We use the $sock handle to communicate with the server. If you use a telnet irc.freenode.net 6667, you will find out what the server sends you upon a newly established connection:

bash-2.05a$ telnet irc.freenode.net 6667
Trying 65.39.204.5...
Connected to earth.peeringsolutions.com.
Escape character is '^]'.
NOTICE AUTH :*** Looking up your hostname...
NOTICE AUTH :*** Checking ident
NOTICE AUTH :*** No identd (auth) response
NOTICE AUTH :*** Found your hostname

The key here is the line with NOTICE AUTH in it. This is when we need to login to the irc server. To do this we send:

NICK bots_nick 
USER bots_ident 0 0 :bots name 

Remember to send a line break after the bots_nick and a line break at the end. So, in the while loop, we will add something like this:

while($line = <$sock>){
    print $line;
    if($line =~ /(NOTICE AUTH).*(checking ident)/i){
            print $sock "NICK MosheBar\nUSER bot 0 0 :I am human\n";
            last;
    }
} 

If you run a simple program with the aforementioned code, you will almost certainly notice the server closing the connection after a while. Why? Because many servers will ask for a ping to make

sure the client is active. Most ready-made bots you find on the Internet will stumble on this. To handle server-side ping requests, again, we need to analyze the IRC chat behavior with a telnet session. Notice that the server will ask for a ping before it asks about NICKSERV registration/identification, so we need to stop this loop after it mentions NICKSERV.

In the following code section, notice the numbers in the last if statement. They help us identify when we are ready to send our nickname incantation.

while($line = <$sock>){
        print $line;    
        #use next line if the server asks for a ping
        if($line =~ /^PING/){
                print $sock "PONG :" . (split(/ :/, $line))[1];
        }
        if($line =~ /(376|422)/i){
                print $sock "NICKSERV :identify nick_password\n";
                last;
        }
}

The NICKSERV :identify nick_password< line is a good example of an irc command. This is just a simple irc command.

The command is NICKSERV and the arguments are identify nick_password where nick_password is the actual password for this nick. The line ends in a line break and all irc commands are in upper case. When there is a colon before something, it means it is a multiple word argument (it has spaces in it). This is how we will handle the possible ping and the nickserv identification.

Once we are done with identification and login, it's time to join a channel. You'll notice the IRC server printing out many lines of text. You join a channel by doing something like this:

print $sock "JOIN #channel\n"; 

Notice, there is no colon before #channel. This is because it does not have any spaces in it. And the JOIN command is in all caps. For a full list of commands try reading a tutorial on the IRC protocol.

All we need to do now is read the messages users send to the channel and respond to them. The format of a priv_msg is as follows:

:nick!ident@hostname.com PRIVMSG #channel :the line of text 

It is wise to separate them into variables.

:$nick!$hostname $type $channel :$text 

Let's not forget that most IRC servers will ping us from time to time, and we must reply with a PONG and the same characters we were pinged with. The routine in Listing 1 will handle that.

Breathing Life into the Bot

Once we have the IRC chat part of our bot working, it is now time to imbue it with some intelligence. A good start is the Perl module Chatbot::Eliza, which provides a simple implementation of Weizenbaum's Eliza therapist. Listing 2 is a small program that shows how to use the module for interactive therapy.

It is slightly funnier to watch Eliza be its own therapist. Listings 3 and 4 show the server and client, respectively.

As you can see, the $eliza->transform($ans) line uses the module's capabilities to get an answer from Eliza based on a text string. We can use this capability with our bot to answer lines written by users in IRC channels. We split the chat messages into their components, and all we have to do is:

$msg = $eliza->transform($text)

and we have a reply ready to send back to the IRC channel.

I leave it to the reader to complete a bot with the components described in this article.

I have let the bot loose on a few IRC channels and sometimes it is really hard to distinguish which species, the human or the computer, is more intelligent. It seems Weizenbaum never anticipated that humans could fall so low when using new communication media.

TPJ

Listing 1

while ($line = <$sock>) {
    ($command, $text) = split(/ :/, $line);  #$text is the stuff from the
                                             #ping or the text from the server
    if ($command eq 'PING'){
        #while there is a line break - many different ways to do this
        while ( (index($text,"\r") >= 0) || (index($text,"\n") >= 0) ){ 
               chop($text); 
        }
        print $sock "PONG $text\n";
        next;
    }
    #done with ping handling
    
    ($nick,$type,$channel) = split(/ /, $line); #split by spaces
    
    ($nick,$hostname) = split(/!/, $nick); #split by ! to get nick and 
                                           #hostname seperate
    $nick =~ s/://; #remove :'s
    $text =~ s/://;
    
    #get rid of all line breaks.  Again, many different ways of doing this.
    $/ = "\r\n";
    while($text =~ m#$/$#){ chomp($text); }
    
    #end of parsing, now for actions
} 

Back to Article

Listing 2

Server.pl
#!/usr/bin/perl -w
use Chatbot::Eliza;
$SIG{INT} = sub { print "\nreceived signal!!! \n\n\n";
print "Do you wish to interrupt this program? [Y/n]: ";
$ans = <STDIN>;
if ($ans eq 'n' or $ans eq 'N') {exit 0};

};
$| = 1;

my $bot = Chatbot::Eliza->new;
$bot->command_interface();

Back to Article

Listing 3

#!/usr/bin/perl -w

use strict;
use Chatbot::Eliza;
use IO::Socket::INET;

print ">> Therapy Server Program <<\n";
my $response;
my $eliza;
# Create a new socket
my $lsocket = IO::Socket::INET->new(
                      LocalPort => 1111,
                      Proto => 'tcp',
                      Listen => 1,
                      Reuse => 1
                    ) or die "Cannot open socket $!\n";

my $socket = $lsocket->accept;
$eliza = new Chatbot::Eliza;
my $def_msg="\nReceiving message from client.....\n";
print "Eliza is ready. What's troubling you? ";
my $text = '';
while( 1 )
{
    $socket->recv( $text , 256 );
    if( $text ne '' )
    {
        print "\npatient: $text \n" ;
        sleep 4;
        my $resp = $eliza->transform($text);
        $socket->send( $resp);
        print "\ndoctor: $resp \n";
        sleep 2;
    }
    # If client message is empty exit
    else
    {
        print "Client has exited!\n";

        exit 1;
    }
}

Back to Article

Listing 4

#!/usr/bin/perl -w
use IO::Socket;
use Chatbot::Eliza;
$remote = IO::Socket::INET->new(
          Proto    => "tcp",
          PeerAddr => "localhost",
          PeerPort => "1111",
          )
         or die "cannot connect to tharapy port at localhost";

my $msg = "Are you my therapist?";
my $ans = " ";
my $eliza = Chatbot::Eliza->new;
while (1) {

$remote->send($msg);
$remote->recv($ans, 256);
$msg = $eliza->transform($ans);
}

Back to Article