IRC chat bot and monitor
Last weekend, I asked my wife if she could think of anything inappropriate or unusual to do with PowerShell. She came up with some good ideas and one of them was to write a chat/IM client.
A full client takes time, so with a nod toward PowerShell’s administrative uses, I wrote a script that forms the basis of an IRC chat bot.
This script can be used to pipe messages to a chat channel and/or join a set of channels, returning all messages posted there.
I’ve sure most of you out there use Microsoft MSN Windows Live Messenger, or even Yahoo! or AOL, so this may just serve as an example of synchronous TCP/IP messaging in PowerShell.
You can download the script here: chat-irc.ps1.
You may need to unblock it according the the instructions in help about_signing
.
Summary of use
This script operates in two modes, sender, or monitor (or both).
Regardless, you need to set up connection info in a hash, eg:
$coninfo = @{ server="chat.freenode.net" port=6667 nick="mynick" user="myuser" pwd="my password if required" realname="This is my real name" hostname"This is my host name, but is generally ignored" }
💡 I’ve been using chat.freenode.net and my own local hybrid irc server for testing.
To send a message to the #test channel and quit:
chat-irc -coninfo $coninfo -sendto "#test" -message "Hello, world"
To send output from the pipeline, eg a file:
gc file.txt | chat-irc -coninfo $coninfo
To monitor a channel for messages:
chat-irc -coninfo $coninfo -monitor "#test"
This will stay connected until you ctrl+C, are killed by the server, etc, or if chat-irc
is sent the message “stopstopstop”.
The output is formatted strings containing from/to/message, however these strings are annotated with noteproperties, so you can do:
chat-irc -coninfo $coninfo -monitor "#test" select *
The properties include sender info (user
,host
,nick
,full
), date
, to
and message
.
These two modes can be used together, and the monitor parameter can take
more than one channel, so for example:
chat-irc -coninfo $coninfo -monitor "#foo","#bar" -sendto "#test" -message "hello"
will send “hello” to #test then monitor #foo,#bar and #test for messages.
By default, only messages sent to the monitored channel(s) are output, but you can include private messages with the switch -incprivate
, the message of the day with -incmotd
, notices with -incnotice
and other with -incother
.
You can see debug info with -debug and verbose output (all messages)
with -verbose. These latter switches just set the $DebugPreference and $VerbosePreference variables to “Continue” in the script’s scope See Debug and Verbose Colouring for more information on that.
Examples
Here we get chat-irc to monitor the channel #test2 whilst I (millinad) chat to it in a normal IRC chat client.
PS> chat-irc -coninfo $coninfo -monitor "#test2" | select date,nick,message|ft -wrap date nick message ---- ---- ------- 31/01/2007 14:21:20 millinad hello soapybot 31/01/2007 14:21:30 millinad nice to see you here 31/01/2007 14:21:38 millinad time for you to go now 31/01/2007 14:21:40 millinad stopstopstop
and in the chat client:
* soapybot (n=soapybot@*******) has joined #test2hello soapybot nice to see you here time for you to go now stopstopstop * soapybot (n=soapybot@*******) has left #test2
Now lets do the same thing again, but with debug switched on and private messages enabled. We’ll turn off the pipeline formatting.
PS> chat-irc -coninfo $coninfo -monitor "#test2" -incprivate -debug DEBUG: Using connection info: DEBUG: server: chat.freenode.net DEBUG: port: 6667 DEBUG: user: soapybot DEBUG: nick: soapybot DEBUG: pwd: ************ DEBUG: realname: powershell bot using soapyfrog inout-irc.ps1 DEBUG: hostname: localhost freenode-connect : soapybot : ☺VERSION☺ DEBUG: I *may* have joined channel #test2 millinad : #test2 : hello again millinad : #test2 : i see you received a ctcp message above. pity you don't understa nd them. millinad : #test2 : time to go again millinad : soapybot : stopstopstop
In the last line, I sent soapybot a direct message to stopstopstop rather than to the channel. Without -incprivate
this message would not have been output.
How it works
The script connects to the chat server using System.Net.Sockets.TcpClient
. Messages are sent using a System.IO.StreamWriter
with ASCII encoding. .NET doesn’t seem to mind of non ASCII chars are translated in this way.
Reading messages is done differently. We can’t use a System.IO.StreamReader
, because ReadLine
blocks if there isn’t a full line available.
We can’t use asynchronous reading because it’s quite hard to use scriptblocks as delegates (although that might change in future versions of PowerShell), and threading is a bit of an unknown quantity in PowerShell.
Instead I use the Net.Sockets.NetworkStream
with its DataAvailable
and ReadByte
. This means I can check if data is available, and if not, idle or send messages, then check again. Reading byte by byte makes the code easier to read, and it’s not too big a performance hit, we’re bound by the network and the speed of people typing 🙂
Lines are processed in the process-line
function where the line is parsed and checked for known numeric responses and commands.
The default handling of a message is in the function _onprivmsg
where the message is just formatted and decorated and place into the output pipeline.
💡 If you wanted to have this bot do something else, you would do it here.
A good chat citizen
This script tries to be a good citizen by throttling it’s message sending with 1 second delays between messages. This is set with the -throttledelay
parameter (milliseconds). The amount of time the script idles when there is no data from the server is 2 seconds, set with the -idledelay
parameter.
Even with these values, it’s still possibly to be killed by a server for flooding. You may want to ask server admin to reduce the flood spec for your bot, or increase the throttledelay.
For example, at chat.freenode.net, piping the output from a directory listing would still cause a flood.
Enjoy 🙂