Difference between revisions of "Roll your own IRC bot"
DonStewart (talk | contribs) (Start on irc bot tutorial) |
DonStewart (talk | contribs) (another chapter) |
||
Line 33: | Line 33: | ||
read and print any data we receive. |
read and print any data we receive. |
||
− | Put this code in the module <hask>1.hs</hask>, and we can then run it |
+ | Put this code in the module <hask>1.hs</hask>, and we can then run it. |
+ | Use whichever system you like: |
||
Using runhaskell: |
Using runhaskell: |
||
<code> |
<code> |
||
$ runhaskell 1.hs |
$ runhaskell 1.hs |
||
+ | "NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :*** |
||
+ | Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ... |
||
+ | </code> |
||
+ | |||
+ | Or we can just compile it to an executable with GHC: |
||
+ | <code> |
||
+ | $ ghc --make 1.hs -o tutbot |
||
+ | Chasing modules from: 1.hs |
||
+ | Compiling Main ( 1.hs, 1.o ) |
||
+ | Linking ... |
||
+ | $ ./tutbot |
||
"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :*** |
"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :*** |
||
Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ... |
Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ... |
||
Line 57: | Line 69: | ||
</code> |
</code> |
||
+ | Great! We're on the network. |
||
− | Or we can just compile it to an executable with GHC: |
||
+ | |||
+ | == Talking IRC == |
||
+ | |||
+ | Now we're listening to the server, we better start sending some |
||
+ | information back. Three details are important, the nick, the user name, |
||
+ | and a channel to join. So let's send those. |
||
+ | |||
+ | <haskell> |
||
+ | import Network |
||
+ | import System.IO |
||
+ | import Text.Printf |
||
+ | |||
+ | server = "irc.freenode.org" |
||
+ | port = 6667 |
||
+ | chan = "#tutbot-testing" |
||
+ | nick = "tutbot" |
||
+ | |||
+ | main = do |
||
+ | h <- connectTo server (PortNumber (fromIntegral port)) |
||
+ | hSetBuffering h NoBuffering |
||
+ | write h "NICK" nick |
||
+ | write h "USER" (nick++" 0 * :tutorial bot") |
||
+ | write h "JOIN" chan |
||
+ | listen h |
||
+ | |||
+ | write :: Handle -> String -> String -> IO () |
||
+ | write h s t = do |
||
+ | hPrintf h "%s %s\r\n" s t |
||
+ | printf "> %s %s\n" s t |
||
+ | |||
+ | listen h = forever $ do |
||
+ | s <- hGetLine h |
||
+ | putStrLn s |
||
+ | where |
||
+ | forever a = a >> forever a |
||
+ | </haskell> |
||
+ | |||
+ | Now, we've done quite a few things here. Firstly, we import |
||
+ | <hask>Text.Printf</hask>, which will be useful. We also set up a channel |
||
+ | name and bot nickname. The <hask>main</hask> function has been extended |
||
+ | to send messages back to the IRC server, using a <hask>write</hask> |
||
+ | function. Let's look at that a bit more closely: |
||
+ | |||
+ | <haskell> |
||
+ | write :: Handle -> String -> String -> IO () |
||
+ | write h s t = do |
||
+ | hPrintf h "%s %s\r\n" s t |
||
+ | printf "> %s %s\n" s t |
||
+ | </haskell> |
||
+ | |||
+ | We've given <hask>write</hask> an explicit type to help document it, and |
||
+ | we'll use explicit types signatures from now on, as they're just good |
||
+ | practice (though of course not required, as Haskell uses type inference |
||
+ | to work out the types anyway). |
||
+ | |||
+ | The <hask>write</hask> function takes 3 arguments: a handle (our |
||
+ | socket), and then two strings representing an IRC protocol action, and |
||
+ | any arguments it takes. <hask>write</hask> then uses <hask>hPrintf<hask> |
||
+ | to build an IRC message, and write it over the wire to the server. For |
||
+ | debugging purposes we also print to standard output the message we send. |
||
+ | |||
+ | Our second function, <hask>listen</hask>, is as follows: |
||
+ | |||
+ | <haskell> |
||
+ | listen :: Handle -> IO () |
||
+ | listen h = forever $ do |
||
+ | s <- hGetLine h |
||
+ | putStrLn s |
||
+ | where |
||
+ | forever a = a >> forever a |
||
+ | </haskell> |
||
+ | |||
+ | This function takes a Handle argument, and sits in an infinite loop |
||
+ | reading lines of text from the network, and printing them. We take |
||
+ | advantage of two powerful features, lazy evaluation and higher order |
||
+ | functions, to roll our own loop control structure, <hask>forever</hask>, |
||
+ | as a normal function! <hask>forever<hask> takes a chunk of code as an |
||
+ | argument, evaluates it, and recurses -- an infinite loop function. It |
||
+ | is very common to roll our own control structures in Haskell this way, |
||
+ | using higher order functions. No need to add new syntax to the language, |
||
+ | when you can just write a normal function to implement whatever control |
||
+ | flow you wish. |
||
+ | |||
+ | Let's run this thing: |
||
<code> |
<code> |
||
− | $ |
+ | $ runhaskell 2.hs |
+ | > NICK tutbot |
||
− | Chasing modules from: 1.hs |
||
+ | > USER tutbot 0 * :tutorial bot |
||
− | Compiling Main ( 1.hs, 1.o ) |
||
+ | > JOIN #tutbot-testing |
||
− | Linking ... |
||
+ | NOTICE AUTH :*** Looking up your hostname... |
||
− | $ ./tutbot |
||
− | + | NOTICE AUTH :*** Found your hostname, welcome back |
|
− | + | NOTICE AUTH :*** Checking ident |
|
+ | NOTICE AUTH :*** No identd (auth) response |
||
+ | :orwell.freenode.net 001 tutbot :Welcome to the freenode IRC Network tutbot |
||
+ | :orwell.freenode.net 002 tutbot :Your host is orwell.freenode.net |
||
+ | ... |
||
+ | :tutbot!n=tutbot@aa.bb.cc.dd JOIN :#tutbot-testing |
||
+ | :orwell.freenode.net MODE #tutbot-testing +ns |
||
+ | :orwell.freenode.net 353 tutbot @ #tutbot-testing :@tutbot |
||
+ | :orwell.freenode.net 366 tutbot #tutbot-testing :End of /NAMES list. |
||
</code> |
</code> |
||
+ | And we're in business! From an irc client, we can watch the bot connect: |
||
− | Great! We're on the network. |
||
+ | <code> |
||
+ | 15:02 -- tutbot [n=tutbot@aa.bb.cc.dd] has joined #tutbot-testing |
||
+ | 15:02 dons> hello |
||
+ | </code> |
||
+ | And the bot logs to standard output: |
||
− | == Talking IRC == |
||
+ | <code> |
||
+ | :dons!i=dons@my.net PRIVMSG #tutbot-testing :hello |
||
+ | </code> |
||
+ | |||
+ | We can now implement some commands. |
||
+ | == A simple interpreter == |
||
− | Todo |
||
− | [[Category:Tutorials]] |
+ | [[Category:Tutorials]]</hask> |
Revision as of 04:10, 4 October 2006
Roll your own IRC bot in Haskell, with monads!
This tutorial is designed as a practical guide to writing real world code in Haskell, and hopes to intuitively motivate and introduce some of the advanced features of Haskell to the novice programmer. Our goal is to write a concise, robust and elegant IRC bot in Haskell.
Getting started
You'll need a reasonably recent version of GHC or Hugs. Our first step is to get on the network. So let's start by importing the Network package, and the standard IO library and defining a server to connect to.
import Network
import System.IO
server = "irc.freenode.org"
port = 6667
main = do
h <- connectTo server (PortNumber (fromIntegral port))
hSetBuffering h NoBuffering
t <- hGetContents h
print t
The key here is the main
function. This is the entry point
to a Haskell program. We first connect to the server, then set the
buffering on the socket off. Once we've got a socket, we can then just
read and print any data we receive.
Put this code in the module 1.hs
, and we can then run it.
Use whichever system you like:
Using runhaskell:
$ runhaskell 1.hs
"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :***
Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ...
Or we can just compile it to an executable with GHC:
$ ghc --make 1.hs -o tutbot
Chasing modules from: 1.hs
Compiling Main ( 1.hs, 1.o )
Linking ...
$ ./tutbot
"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :***
Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ...
Or using GHCi:
$ ghci 1.hs
*Main> main
"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :***
Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ...
Or in Hugs:
$ runhugs 1.hs
"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :***
Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ...
Great! We're on the network.
Talking IRC
Now we're listening to the server, we better start sending some information back. Three details are important, the nick, the user name, and a channel to join. So let's send those.
import Network
import System.IO
import Text.Printf
server = "irc.freenode.org"
port = 6667
chan = "#tutbot-testing"
nick = "tutbot"
main = do
h <- connectTo server (PortNumber (fromIntegral port))
hSetBuffering h NoBuffering
write h "NICK" nick
write h "USER" (nick++" 0 * :tutorial bot")
write h "JOIN" chan
listen h
write :: Handle -> String -> String -> IO ()
write h s t = do
hPrintf h "%s %s\r\n" s t
printf "> %s %s\n" s t
listen h = forever $ do
s <- hGetLine h
putStrLn s
where
forever a = a >> forever a
Now, we've done quite a few things here. Firstly, we import
Text.Printf
, which will be useful. We also set up a channel
name and bot nickname. The main
function has been extended
to send messages back to the IRC server, using a write
function. Let's look at that a bit more closely:
write :: Handle -> String -> String -> IO ()
write h s t = do
hPrintf h "%s %s\r\n" s t
printf "> %s %s\n" s t
We've given write
an explicit type to help document it, and
we'll use explicit types signatures from now on, as they're just good
practice (though of course not required, as Haskell uses type inference
to work out the types anyway).
The write
function takes 3 arguments: a handle (our
socket), and then two strings representing an IRC protocol action, and
any arguments it takes. write
then uses hPrintf<hask> to build an IRC message, and write it over the wire to the server. For debugging purposes we also print to standard output the message we send. Our second function, <hask>listen
, is as follows:
listen :: Handle -> IO ()
listen h = forever $ do
s <- hGetLine h
putStrLn s
where
forever a = a >> forever a
This function takes a Handle argument, and sits in an infinite loop
reading lines of text from the network, and printing them. We take
advantage of two powerful features, lazy evaluation and higher order
functions, to roll our own loop control structure, forever
,
as a normal function! forever<hask> takes a chunk of code as an argument, evaluates it, and recurses -- an infinite loop function. It is very common to roll our own control structures in Haskell this way, using higher order functions. No need to add new syntax to the language, when you can just write a normal function to implement whatever control flow you wish. Let's run this thing: <code> $ runhaskell 2.hs > NICK tutbot > USER tutbot 0 * :tutorial bot > JOIN #tutbot-testing NOTICE AUTH :*** Looking up your hostname... NOTICE AUTH :*** Found your hostname, welcome back NOTICE AUTH :*** Checking ident NOTICE AUTH :*** No identd (auth) response :orwell.freenode.net 001 tutbot :Welcome to the freenode IRC Network tutbot :orwell.freenode.net 002 tutbot :Your host is orwell.freenode.net ... :tutbot!n=tutbot@aa.bb.cc.dd JOIN :#tutbot-testing :orwell.freenode.net MODE #tutbot-testing +ns :orwell.freenode.net 353 tutbot @ #tutbot-testing :@tutbot :orwell.freenode.net 366 tutbot #tutbot-testing :End of /NAMES list. </code> And we're in business! From an irc client, we can watch the bot connect: <code> 15:02 -- tutbot [n=tutbot@aa.bb.cc.dd] has joined #tutbot-testing 15:02 dons> hello </code> And the bot logs to standard output: <code> :dons!i=dons@my.net PRIVMSG #tutbot-testing :hello </code> We can now implement some commands. == A simple interpreter == [[Category:Tutorials]]