There are a few programming projects that just have to be re-written in each and every programming language. You might think it’s a waste of time, but because they’re used primarily by programmers and geeks, and because using them usually involves heavily modifying them, everyone wants one that was written in the language (s)he’s most familiar with, and writing them can be a good learning experience. One such project is the IRC bot.
I wrote a bot in PowerShell awhile back that we called PowerBot … the script using XMPP (Jabber), and could connect to IRC through a gateway. Although it could do the thing a bot should be able to do (join channels, respond to messages, etc), and had bonus features like announcing new posts from PoshCode.org, and following channel members on Twitter … it was technically a Jabber bot, not an IRC bot (and thus, a little slow to respond on IRC).
In any case, I’ve created a very simple framework for people to write PowerShell IRC decided to rectify that now by publishing a new bot based on the SmartIrc4Net library. Of course, it’s not a “pure” PowerShell bot (since I’m using an IRC library), but this is .Net after all, and we don’t really believe in rewriting things that already work just fine. SmartIrc4net is a very nice bit of work, and it’s a single dll you just have to plop in a folder with the script, and it’s LGPL (2.1) so you can reuse it at will with very few restrictions.
The full script is available on PoshCode but I wanted to go through and explain a few pieces of it, so I’ll break it up in pieces here. Incidentally, if you run this command on that version of the script, you’ll see there are exactly 39 lines of code, including loading the irc library, and the initial Hello World command.
The first section of the script loads the assembly and contains the Start-PowerBot script. I’ve wrapped things onto multiple lines here to fit in my blog a bit better and for readability:
The most important thing to notice here is that the SmartIrc4Net library is event driven, so you just have to Add scriptblock event handlers to things, and it will call them when something happens. You can see I’ve handled the error condition directly in the scriptblock, but for the other handlers I’ve called out functions which I will specify later.
In this simple bot I’ve only handled errors, private messages (a QueryMessage is a message sent directly to the bot, instead of to the channel), and channel messages. The “channel” is where you all hang out and chat — you young’uns may know this as a “chat room” ... but on IRC we still like to pretend we’re on HAM radio, so we have channels and go by weird aliases like “Gaurhoth” and “Gnopeg” and “SmellyHippy” and consider it impolite to ask what people’s real names are (we’ll probably kick-ban you if you ask “A/S/L?”).
You do NOT need a password for the $irc.login call, but if you have registered your bot’s nick (which I highly recommend if your IRC network supports it), then you should pass it’s password to the function. Finally, the Resume-Powerbot call at the end is a call to our simplest function. This is essentially the “message loop” of our program:
Resume-PowerBot is the simplest possible loop: it will exit if you press any key while the PowerShell window has focus. You might want to beef up the loop, and add the ability to type while the script is running (see my demo script). Putting that together with this would let you implement a simple interactive PowerShell IRC client … which is scriptable to the Nth degree.
When I call ListenOnce($false) I pass the optional boolean parameter $false to specify that I don’t want ListenOnce to block — otherwise, ListenOnce() will block until it actually receives a message: it’s basically like the difference between writing if($Host.UI.RawUI.KeyAvailable){$Host.UI.RawUI.ReadKey()} and just writing $Host.UI.RawUI.ReadKey() …
Stop-PowerBot actually sends a quite message and then disconnects from IRC, and if you call it, you’ll need to call Start-PowerBot before you can actually use the $irc variable again…
Event handlers in PowerShell are really very frustrating. Instead of just passing the arguments to the event handler, thus allowing your event handler to match “any” event signature, PowerShell takes exactly two arguments to it’s event handlers: the first is the $this parameter which represents what is triggering the event, and the second is the $_ parameter which represents the EventArgs. In any case, although that is sometimes limiting when developer use non-standard events, it does represent the usual CLR-compliant event signature, and luckily, most of the events in SmartIrc4net match it.
The two handlers I’ve written in this demo handle private (Query) messages and public channel messages. They are virtually identical, except that they send the return message differently
Finally, the actual commands for this bot are stored as a hash. You “register” a command by adding a key to the hash, where the key is a single word that matches the hash (it has to be the first word in the message) and the value is a scriptblock that will be executed. Generally, you just need to return a string (or an object) that you want to reply with, but you can, obviously, take any action you want.
Unlike the event handlers, the powerbot commands receive parameters: the first is a convenience parameter: it is the rest of the message that came after the keyword. The second parameter is the full “Data” member from SmartIrc4Net, and includes all the text that was in the message. Although the first parameter is a little redundant, most commands only need to handle the first parameter, so I’m keeping it there.
Someone asked on the PowerShell Community Forum about a way to use google talk or jabber to send messages from PowerShell, and I got carried away …
The result is yet another CodePlex project: PoshXmpp. Basically I just took the event-based GPL library provided by AG Software (agsXMPP) and wrote a polling-based wrapper for it.
You can do pretty much anything instant-messaging related that you want to do with Jabber, from building bridges between different jabber transports, to notifying yourself of events on servers, etc. As an example, here’s the script for a Jabber bot that joins a chat (#PowerShell on irc.FreeNode.net by default) and then notifies us of new posts in an ATOM feed (by default, it monitors the PowerShell usenet newsgroup by using the Google groups ATOM feed).
Now, most of that script was spent parsing xml from the atom feed, so lets try another example. This script will join mirror a groupt chat to instant message. By default it joins the PowerShell IRC channel, and then instant messages you so that you can participate in the IRC chat via your instant messenger. You can register your Jabber account with the AIM, ICQ, MSN, or other transport on it’s Jabber server and then use this to mirror IRC to your MSN account or to AIM on your phone or whatever
. I’ve been using im.flosoft.biz which allows you to not only register new accounts on the web, but also register them with the transports through a web form.
There’s another example on the PowerShell Xmpp for Jabber project page, showing how you can use this to read a chat room out loud (sometimes I run that when I’m just monitoring a chat room while I do other work). You could also use the Xmpp library for notifications — set up your Windows 2008 server to instant message you when IIS fails, or when you receive an email, whatever. I’ve even been playing with Invoke-Expression … you can actually write a script to do powershell remoting over Jabber (yikes).