Wednesday, January 30, 2008

Jerkbot gets a Groovy makeover

Tonight I decided to rewrite my example bot for jerklib. I chose to do it in Groovy because i've had the urge to use it. The fact that it integrates well with java makes it a perfect candidate to write a bot (or even a client) using Jerklib! As I was writing it I felt like a kid in a candy store, oogling at the coolness that is Groovy. To run this code download the jerklib jar. Then simply type:
groovy -cp jerklib.jar:. filename.groovy
Or you're on windows then it's:
groovy -cp jerklib.jar;. filename.groovy

Of course you'll replace filename.groovy w/ the file you saved it under.

A Note: I primarily used this class to test my additions to the jerklib -- so you can call this a scratchpad of sorts (at least it was at the time.)


import jerklib.Profile
import jerklib.ProfileImpl
import jerklib.events.listeners.IRCEventListener
import jerklib.events.IRCEvent
import jerklib.events.JoinCompleteEvent
import jerklib.ConnectionManager
import jerklib.events.MessageEvent
import jerklib.events.MessageEvent
import jerklib.events.NickChangeEvent
import jerklib.events.AwayEvent



/**
* Created: Jan 29, 2008 11:42:23 PM
* @author Robert O'Connor
*/
class GroovyJerkbot implements IRCEventListener {
def manager

GroovyJerkbot(String nick, String username, String hostname, int port) {
def profile = new ProfileImpl(username, nick, nick + new Random().nextInt(42),
nick + new Random().nextInt(512))
manager = new ConnectionManager(profile)
manager.requestConnection(hostname, port).addIRCEventListener(this)

}


static void main(args) {
def bot = new GroovyJerkbot("jerkbot", "jerkbot", "irc.freenode.org", 6667)
}

@Override
void receiveEvent(IRCEvent e) {
if (e.getType() == IRCEvent.Type.CONNECT_COMPLETE) {
e.getSession().join("#jerklib")
e.getSession().who("mohadib")
e.getSession().setAway("FOO!")
}
else if (e.getType() == IRCEvent.Type.JOIN_COMPLETE) {
def JoinCompleteEvent event = (JoinCompleteEvent) e
event.getChannel().say("Hai2u")
} else if (e.getType() == IRCEvent.Type.CHANNEL_MESSAGE) {
MessageEvent event = (MessageEvent) e
// what does this is take the channel msg and match it to the pattern ~say foo
def matcher = event.getMessage() =~ /^~say\s+(.*)$/
if (matcher.matches())
{
e.getSession().channelSay(event.getChannel().getName(), matcher.group(1))
}else if(event.getMessage() ==~ /^~part.*$/) {
e.getSession().partChannel(event.getChannel(),"I was asked to leave")
} else {
def whoMatcher = event.getMessage() =~ /^~who\s+(.*)$/
if(whoMatcher.matches()) {
e.getSession().who(whoMatcher.group(1))
}
def awayMatcher = event.getMessage() =~ /^~away\s+(.*)$/
if(awayMatcher.matches()) {
e.getSession().setAway(m.group(1))
}else {
e.getSession().unsetAway()
}
}

} else if (e.getType() == IRCEvent.Type.PRIVATE_MESSAGE) {
MessageEvent event = (MessageEvent)e
if(event.getMessage() ==~ /^~quit.*$/) {
e.getSession().close("I was asked to leave.")
}
} else if (e.getType() == IRCEvent.Type.NICK_CHANGE) {
NickChangeEvent event = (NickChangeEvent) e
println event.getOldNick()
println event.getNewNick()
println event.getUserName()
println event.getHostName()
println e.getRawEventData()
} else {
if (e.getType() == IRCEvent.Type.AWAY_EVENT) {
AwayEvent event = (AwayEvent)e
println "Nick: "+event.getNick()
println "Event Type: "+event.getEventType()
println "Us?: "+event.isYou()
println "Away?: "+event.isAway()

}
}

}

}



As you can see, the import for Random is missing (no need with groovy, it's imported implicitly, whereas in java, only java.lang is imported implicitly! Also, semi-colons are OPTIONAL!! Groovy has native regex support! =~ means to create the matcher for this pattern. The ==~ operator checks if the text matches the pattern given after the ==~ operator.

Nobody would argue with me if I said the above example was verbose. Jason Davis wrote this the following gem:

 
import jerklib.*;
import jerklib.tasks.*;
import jerklib.events.listeners.*;
import jerklib.events.IRCEvent.Type;

def stratMap =[:]
stratMap[ Type.CONNECT_COMPLETE ] = {x-> x.session.joinChannel "#jerklib"}
stratMap[ Type.JOIN_COMPLETE ] = {x-> x.channel.say "Hello World"}
stratMap[Type.CHANNEL_MESSAGE] = {x-> println x.rawEventData}

conMan = new ConnectionManager(new ProfileImpl("foo","gscripbot","gscripbot1","teh"));
session = conMan.requestConnection('irc.freenode.net');
session.addIRCEventListener({x-> close = stratMap[x.type]; if(close != null)close(x)} as IRCEventListener);


Jason used the Strategy Pattern for the event handling. It cleans up your code greatly and eliminates the need for a bunch of if statements. It also can cut down on the verbosity of your code, like it has in this case.