// IRC Bot
// Karl L. Barrus <karl at klbarrus dot com>
// January 9, 2005
//
// Copyright (c) 2005 Karl L. Barrus
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 675 Mass Ave, Cambridge, MA 02139, USA.
// simple IRC bot based on the PircBot Java framework
// see http://www.jibble.org/ for info about the PircBot Java API
//
// public commands supported:
// @help
// @8ball question
// @spin
// @addquote user quote
// @quotelist
// @quote [user]
// @seen user
//
// private commands supported:
// save
// print [user]
// delquotes password user [index]
import org.jibble.pircbot.*;
import java.util.*;
import java.io.*;
public class MyBot extends PircBot
{
private String channel_;
private String chan_password_;
private String priv_password_;
private ArrayList quotedUsers_;
private ArrayList seenUsers_;
private HashMap lastSeenDate_;
private HashMap lastSeenQuote_;
private boolean connected_;
private boolean joined_;
private boolean quotesChanged_;
private boolean seenChanged_;
private int BOT_HELP = 0;
private int BOT_8BALL = 1;
private int BOT_SPIN = 2;
private int BOT_ADDQUOTE = 3;
private int BOT_QUOTELIST = 4;
private int BOT_QUOTE = 5;
private int BOT_SEEN = 6;
private int BOT_PRIV_SAVE = 0;
private int BOT_PRIV_PRINT = 1;
private int BOT_PRIV_DELQUOTES = 2;
private int BOT_PRIV_SEENLIST = 3;
String botAttention_ = "@";
private int SEEN_LIST_USERS = 10;
String botPublicCommands_[] =
{
botAttention_ + "help",
botAttention_ + "8ball",
botAttention_ + "spin",
botAttention_ + "addquote",
botAttention_ + "quotelist",
botAttention_ + "quote",
botAttention_ + "seen"
};
String botPrivateCommands_[] =
{
"save",
"print",
"delquotes",
"seenlist"
};
String ircServers[] =
{
"irc.choopa.net",
"irc.easynews.com",
"irc.servercentral.net",
"irc.prison.net",
"irc.efnet.pl",
// "irc.rt.ru"
};
String eightBallReplys[] =
{
"Signs point to yes.",
"Yes.",
"Reply hazy, try again.",
"Without a doubt.",
"My sources say no.",
"As I see it, yes.",
"You may rely on it.",
"Concentrate and ask again.",
"Outlook not so good.",
"It is decidedly so.",
"Better not tell you now.",
"Very doubtful.",
"Yes - definitely.",
"It is certain.",
"Cannot predict now.",
"Most likely.",
"Ask again later.",
"My reply is no.",
"Outlook good.",
"Don't count on it."
};
public MyBot( String[] args )
{
// args[ 0 ] = name
// args[ 1 ] = channel password (to get ops)
// args[ 2 ] = private password (to delete quotes)
// args[ 3 ] = channel to join
setName( args[ 0 ] );
setLogin( args[ 0 ] );
chan_password_ = args[ 1 ];
priv_password_ = args[ 2 ];
channel_ = args[ 3 ];
quotedUsers_ = new ArrayList();
seenUsers_ = new ArrayList();
lastSeenDate_ = new HashMap();
lastSeenQuote_ = new HashMap();
connected_ = false;
joined_ = false;
quotesChanged_ = false;
seenChanged_ = false;
readQuoteList();
readSeenList();
saveThread st = new saveThread( this );
st.start();
}
public void onMessage( String channel, String sender, String login, String hostname, String message )
{
lastSeenDate_.put( sender.toLowerCase(), new Date() );
lastSeenQuote_.put( sender.toLowerCase(), message );
updateSeenList( sender );
seenChanged_ = true;
if ( message.startsWith( botAttention_ ) )
{
String args[] = parseLine( message );
if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_HELP ] ) )
doHelp( channel, sender, args );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_8BALL ] ) )
do8Ball( channel, sender, args );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_SPIN ] ) )
doSpin( channel, sender, args );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_ADDQUOTE ] ) )
doAddQuote( channel, sender, args );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_QUOTELIST ] ) )
doQuoteList( channel, sender, args );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_QUOTE ] ) )
doQuote( channel, sender, args );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_SEEN ] ) )
doSeen( channel, sender, args );
}
}
public void onPrivateMessage( String sender, String login, String hostname, String message )
{
String args[] = parseLine( message );
if ( args[ 0 ].startsWith( botPrivateCommands_[ BOT_PRIV_SAVE ] ) )
{
saveQuoteList();
saveSeenList();
}
else if ( args[ 0 ].startsWith( botPrivateCommands_[ BOT_PRIV_PRINT ] ) )
printQuoteList( sender, args );
else if ( args[ 0 ].startsWith( botPrivateCommands_[ BOT_PRIV_DELQUOTES ] ) )
doDelQuote( sender, args );
else if ( args[ 0 ].startsWith( botPrivateCommands_[ BOT_PRIV_SEENLIST ] ) )
printSeenList( sender );
else if ( args[ 0 ].startsWith( botPublicCommands_[ BOT_QUOTE ] ) )
doQuote( null, sender, args );
}
public void onJoin( String channel, String sender, String login, String hostname )
{
joined_ = true;
}
public void onMode( String channel, String sourceNick, String sourceLogin, String sourceHostName, String mode )
{
}
public void onKick( String channel, String kickerNick, String kickerLogin, String kickerHostname, String recipientNick, String reason )
{
System.out.println( "*** kicked out" );
joined_ = false;
saveQuoteList();
saveSeenList();
// try to rejoin 10 times
for ( int i = 0; i < 10; ++i )
{
try
{
Thread.sleep( 10 * 1000 );
}
catch ( InterruptedException ie )
{
System.out.println( "*** run interrupted exception" );
}
joinChannel( channel_ );
if ( joined_ )
break;
}
// join didn't work - now what?
}
public void onConnect()
{
System.out.println( "*** connected" );
connected_ = true;
joinChannel( channel_ );
}
public void onDisconnect()
{
System.out.println( "*** disconnected" );
connected_ = false;
saveQuoteList();
saveSeenList();
// try to reconnect 10 times
for ( int i = 0; i < 10; ++i )
{
try
{
Thread.sleep( 60 * 1000 );
}
catch ( InterruptedException ie )
{
System.out.println( "*** run interrupted exception" );
}
try
{
reconnect();
}
catch ( Exception e )
{
System.out.println( "*** reconnect exception" );
}
if ( connected_ )
break;
}
// reconnect didn't work - now what?
}
// @help
private void doHelp( String channel, String sender, String[] args )
{
StringBuffer reply = new StringBuffer();
reply.append( "I respond to: " );
for ( int i = 0; i < botPublicCommands_.length; ++i )
{
reply.append( botPublicCommands_[ i ] );
reply.append( " " );
}
sendMessage( channel, reply.toString() );
}
// @8ball question
private void do8Ball( String channel, String sender, String[] args )
{
String command = botPublicCommands_[ BOT_8BALL ];
StringBuffer reply = new StringBuffer();
if ( args[ 1 ] != null )
{
// the parsing splits the message into 5 arguments
// for the 8ball question, we want to recombine them back into the original question
StringBuffer question = new StringBuffer();
for ( int i = 1; i < 5 && args[ i ] != null; ++i )
{
question.append( args[ i ] );
if ( i != 4 )
question.append( " " );
}
int replyIndex = ( int ) ( Math.random() * eightBallReplys.length );
reply.append( sender );
reply.append( " asks " );
reply.append( question );
reply.append( " ... " );
reply.append( eightBallReplys[ replyIndex ] );
}
else
{
reply.append( "usage: " );
reply.append( command );
reply.append( " question" );
}
sendMessage( channel, reply.toString() );
}
// @spin
private void doSpin( String channel, String sender, String[] args )
{
StringBuffer reply = new StringBuffer();
int flirtIndex = ( int ) ( Math.random() * seenUsers_.size() );
String flirt = ( String ) seenUsers_.get( flirtIndex );
reply.append( sender );
reply.append( " spins the bottle... and it points to " );
reply.append( flirt );
sendMessage( channel, reply.toString() );
}
// @addquote user quote
private void doAddQuote( String channel, String sender, String[] args )
{
String command = botPublicCommands_[ BOT_ADDQUOTE ];
StringBuffer reply = new StringBuffer();
String userid = args[ 1 ];
if ( userid != null && args[ 2 ] != null )
{
StringBuffer quote = new StringBuffer();
for ( int i = 2; i < 5 && args[ i ] != null; ++i )
{
quote.append( args[ i ] );
if ( i != 4 )
quote.append( " " );
}
ircUser user = lookupUser( userid );
if ( user != null )
{
// existing quotedUser, just add the quote
user.addQuote( quote.toString() );
}
else
{
// new quotedUser, create user and add the quote
user = new ircUser( userid );
user.addQuote( quote.toString() );
quotedUsers_.add( user );
}
sendNotice( sender, "quote added" );
quotesChanged_ = true;
}
else
{
reply.append( "usage: " );
reply.append( command );
reply.append( " user quote" );
sendMessage( channel, reply.toString() );
}
}
// @quotelist
private void doQuoteList( String channel, String sender, String[] args )
{
StringBuffer reply = new StringBuffer();
if ( quotedUsers_.size() != 0 )
{
reply.append( "users with quotes:" );
ircUser user = null;
for ( int i = 0; i < quotedUsers_.size(); ++i )
{
user = ( ircUser ) quotedUsers_.get( i );
reply.append( " " );
reply.append( user.getNick() );
}
}
else
{
reply.append( "No quotes stored. Why not add one?" );
}
sendMessage( channel, reply.toString() );
}
// @quote [user]
private void doQuote( String channel, String sender, String[] args )
{
String command = botPublicCommands_[ BOT_QUOTE ];
StringBuffer reply = new StringBuffer();
if ( quotedUsers_.size() > 0 )
{
ircUser user = null;
String userid = args[ 1 ];
if ( userid != null )
{
// user specified, extract userid and lookup
user = lookupUser( userid );
}
else
{
// no user specified, pick random user
int userIndex = ( int ) ( Math.random() * quotedUsers_.size() );
user = ( ircUser ) quotedUsers_.get( userIndex );
}
if ( user != null )
{
// be sure the first character printed isn't an @
// otherwise the bot may interpret it as a command
// i.e. hostile user could do "@addquote @quote blah" and then "@quote @quote" will start a loop
reply.append( "<" );
reply.append( user.getNick() );
reply.append( ">: " );
reply.append( user.getAQuote() );
}
else
{
reply.append( "Sorry, I don't know any quotes from " );
reply.append( userid );
reply.append( ". Why not add one?" );
}
}
else
{
reply.append( "No quotes stored. Why not add one?" );
}
if ( channel != null )
sendMessage( channel, reply.toString() );
else
sendNotice( sender, reply.toString() );
}
// @seen user
private void doSeen( String channel, String sender, String[] args )
{
String command = botPublicCommands_[ BOT_SEEN ];
StringBuffer reply = new StringBuffer();
String userid = args[ 1 ];
if ( userid != null )
{
Date seenDate = ( Date ) lastSeenDate_.get( userid.toLowerCase() );
String seenQuote = ( String ) lastSeenQuote_.get( userid.toLowerCase() );
if ( seenQuote != null )
{
Date now = new Date();
long timeDiff = now.getTime() - seenDate.getTime();
String ago = getAgoString( timeDiff );
reply.append( userid.toString() );
reply.append( " last said '" );
reply.append( seenQuote );
reply.append( "'" );
reply.append( ago );
}
else
{
reply.append( "I haven't heard " );
reply.append( userid );
reply.append( " say anything." );
}
}
else
{
reply.append( "usage: " );
reply.append( command );
reply.append( " user" );
}
sendMessage( channel, reply.toString() );
}
// delquotes password user [index]
private void doDelQuote( String sender, String[] args )
{
String command = botPrivateCommands_[ BOT_PRIV_DELQUOTES ];
StringBuffer reply = new StringBuffer();
String password = args[ 1 ];
if ( password == null || !priv_password_.equals( password ) )
{
System.out.println( "*** wrong password" );
return;
}
String userid = args[ 2 ];
if ( userid != null )
{
ircUser user = lookupUser( userid );
if ( user != null )
{
String indexString = args[ 3 ];
if ( indexString != null )
{
// index supplied - delete that particular quote from userid
Integer intClass = new Integer( indexString );
int index = intClass.intValue();
user.removeQuote( index );
reply.append( "quote deleted" );
if ( user.getNumQuotes() == 0 )
{
// no more quotes, so delete user
quotedUsers_.remove( user );
}
}
else
{
// no index supplied - delete all quotes from userid
quotedUsers_.remove( user );
reply.append( "all quotes from user " );
reply.append( userid );
reply.append( " deleted" );
}
quotesChanged_ = true;
}
else
{
reply.append( "can't find user " );
reply.append( userid );
}
}
else
{
reply.append( "usage: " );
reply.append( command );
reply.append( " password user [index]" );
}
sendMessage( sender, reply.toString() );
}
private void updateSeenList( String userName )
{
// keep an updated list of the SEEN_LIST_USERS most recently active users
// delete all occurences of the user, then add
boolean present;
do
{
present = seenUsers_.remove( userName );
}
while ( present );
seenUsers_.add( userName );
// trim list to SEEN_LIST_USERS users by removing the oldest (lowest index) entry
if ( seenUsers_.size() > SEEN_LIST_USERS )
{
seenUsers_.remove( 0 );
}
}
private void printSeenList( String sender )
{
StringBuffer reply = new StringBuffer();
if ( seenUsers_.size() == 0 )
{
reply.append( "I haven't seen anybody yet." );
}
else
{
for ( int i = 0; i < seenUsers_.size(); ++i )
{
reply.append( ( String ) seenUsers_.get( i ) );
reply.append( " " );
}
}
sendMessage( sender, reply.toString() );
}
private ircUser lookupUser( String userid )
{
ircUser user = null;
for ( int i = 0; i < quotedUsers_.size(); ++i )
{
ircUser tempUser = ( ircUser ) quotedUsers_.get( i );
if ( userid.equalsIgnoreCase( tempUser.getNick() ) )
{
user = tempUser;
break;
}
}
return user;
}
private int getUserIndex( String userid )
{
int index = -1;
for ( int i = 0; i < quotedUsers_.size(); ++i )
{
ircUser user = ( ircUser ) quotedUsers_.get( i );
if ( userid.equalsIgnoreCase( user.getNick() ) )
{
index = i;
break;
}
}
return index;
}
private String getAgoString( long timeDiff )
{
int days, hours, minutes, seconds;
seconds = ( int ) timeDiff / 1000;
if ( seconds == 0 )
seconds = 1;
days = seconds / 86400;
seconds -= days * 86400;
hours = seconds / 3600;
seconds -= hours * 3600;
minutes = seconds / 60;
seconds -= minutes * 60;
StringBuffer ago = new StringBuffer();
ago.append( " about" );
if ( days > 0 )
ago.append( " " + getTimeString( days, "day" ) );
if ( hours > 0 )
ago.append( " " + getTimeString( hours, "hour" ) );
if ( minutes > 0 )
ago.append( " " + getTimeString( minutes, "minute" ) );
if ( seconds > 0 )
ago.append( " " + getTimeString( seconds, "second" ) );
ago.append( " ago." );
return ago.toString();
}
private String getTimeString( int counter, String text )
{
StringBuffer reply = new StringBuffer();
reply.append( counter );
reply.append( " " );
reply.append( text );
if ( counter != 1 )
reply.append( "s" );
return reply.toString();
}
public void doServerList()
{
startIdentServer();
while ( !connected_ )
{
try
{
int serverIndex = ( int ) ( Math.random() * ircServers.length );
System.out.println( "*** connecting to " + ircServers[ serverIndex ] );
connect( ircServers[ serverIndex ] );
}
catch( Exception e )
{
System.out.println( "*** connection exception" );
try
{
Thread.sleep( 60 * 1000 );
}
catch ( InterruptedException ie )
{
System.out.println( "*** run interrupted exception" );
}
}
}
}
public void printQuoteList( String sender, String[] args )
{
String command = botPrivateCommands_[ BOT_PRIV_PRINT ];
StringBuffer reply = new StringBuffer();
if ( quotedUsers_.size() > 0 )
{
String userid = args[ 1 ];
ircUser user = null;
if ( userid != null )
{
// userid supplied
user = lookupUser( userid );
if ( user != null )
{
printQuotesListUser( sender, user );
}
else
{
reply.append( "No quotes from user " );
reply.append( userid );
sendMessage( sender, reply.toString() );
}
}
else
{
// print all quotes from all users
for ( int i = 0; i < quotedUsers_.size(); ++i )
{
user = ( ircUser ) quotedUsers_.get( i );
printQuotesListUser( sender, user );
}
}
}
else
{
reply.append( "No quotes stored." );
sendMessage( sender, reply.toString() );
}
}
private void printQuotesListUser( String sender, ircUser user )
{
StringBuffer reply = new StringBuffer();
int numQuotes = user.getNumQuotes();
reply.append( "I have " );
reply.append( user.getNumQuotes() );
reply.append( " quotes from " );
reply.append( user.getNick() );
reply.append( ":" );
sendMessage( sender, reply.toString() );
for ( int j = 0; j < numQuotes; ++j )
{
reply.delete( 0, reply.length() );
reply.append( j );
reply.append( ": " );
reply.append( user.getQuote( j ) );
sendMessage( sender, reply.toString() );
}
}
public void saveQuoteList()
{
if ( quotesChanged_ )
{
try
{
String filename = "quotes-" + channel_;
FileOutputStream fileOut = new FileOutputStream( filename );
ObjectOutputStream out = new ObjectOutputStream( fileOut );
out.writeObject( quotedUsers_ );
out.flush();
fileOut.close();
System.out.println( "*** quotes saved" );
quotesChanged_ = false;
}
catch ( IOException ioe )
{
System.out.println( "*** saveQuoteList exception " + ioe.getMessage() );
}
}
}
public void readQuoteList()
{
try
{
String filename = "quotes-" + channel_;
FileInputStream fileIn = new FileInputStream( filename );
ObjectInputStream in = new ObjectInputStream( fileIn );
quotedUsers_ = ( ArrayList ) in.readObject();
fileIn.close();
System.out.println( "*** quotes read" );
}
catch ( IOException ioe )
{
System.out.println( "*** readQuoteList exception " + ioe.getMessage() );
}
catch ( ClassNotFoundException cnfe )
{
System.out.println( "*** readQuoteList exception " + cnfe.getMessage() );
}
}
public void saveSeenList()
{
if ( seenChanged_ )
{
try
{
String filename = "seen-" + channel_;
FileOutputStream fileOut = new FileOutputStream( filename );
ObjectOutputStream out = new ObjectOutputStream( fileOut );
out.writeObject( lastSeenDate_ );
out.writeObject( lastSeenQuote_ );
out.flush();
fileOut.close();
System.out.println( "*** lastseen saved" );
seenChanged_ = false;
}
catch ( IOException ioe )
{
System.out.println( "*** saveSeenList exception " + ioe.getMessage() );
}
}
}
public void readSeenList()
{
try
{
String filename = "seen-" + channel_;
FileInputStream fileIn = new FileInputStream( filename );
ObjectInputStream in = new ObjectInputStream( fileIn );
lastSeenDate_ = ( HashMap ) in.readObject();
lastSeenQuote_ = ( HashMap ) in.readObject();
fileIn.close();
System.out.println( "*** lastseen read" );
}
catch ( IOException ioe )
{
System.out.println( "*** readSeenList exception " + ioe.getMessage() );
}
catch ( ClassNotFoundException cnfe )
{
System.out.println( "*** readSeenList exception " + cnfe.getMessage() );
}
}
private String[] parseLine( String message )
{
// split into
// command arg1 arg2 arg3 arg4
// arg4 will contain the rest of the line
// some of the args might be null
int i, begin, end;
String args[] = new String[ 5 ];
for ( i = 0; i < 5; ++i )
args[ i ] = null;
for ( i = 0, begin = 0; i < 5; ++i )
{
end = message.indexOf( ' ', begin );
if ( end == -1 || i == 4 )
{
args[ i ] = message.substring( begin );
break;
}
else
args[ i ] = message.substring( begin, end );
end++;
begin = end;
}
return args;
}
}