Rudimentary Clojure IRC Client in 30 lines

The other day I wanted to experiment a little on using TCP in Clojure - thank goodness for the beautiful channel model of concurrency (CSP) - because with the aleph library it took about 10 minutes to setup.

The process of which is to establish a TCP connection with the IRC server (in this situation freenode) using the wait-for-result and tcp-client functions that return a Lamina channel returning messages that have been deliminated on "\r\n" over the TCP stream (magic!). In this basic instance all I have done is wait for the TCP connection to be established, run an asynchronous go-loop to print every message that comes through, send the command to join the #clojure-test channel (so we don't bother our #clojure brethren) and send a message. If you run this as it is - it will print every line that comes over TCP.
(ns clj-irc-cli.core
  (:require [clojure.core.async :as a]))

(use 'lamina.core 'aleph.tcp 'gloss.core)

(def ch
  (wait-for-result
   (tcp-client {:host "irc.freenode.net",
                :port 6667,
                :frame (string :utf-8 :delimiters ["\r\n"])})))

(a/go-loop []
  (when-let [msg (wait-for-message ch)]
    (println msg)
    (recur)))

(enqueue ch "NICK clojure-user")
(enqueue ch "USER clojure-user 8 *  : Johnny Bloggy")
(enqueue ch "JOIN #clojure-test")
(enqueue ch "PRIVMSG #clojure-test :Hi there..")
The output is not ideal, unless you can parse a stream of 100 character a second!

So in order to be useful in the most basic way, we need it to: Maintain connection by relaying PONG messages, Only parse out important messages (in our case, messages people send over the channel) and be able to send custom messages of our own!

And guess what - I implemented it!
(ns clj-irc-cli.core
  (:require [clojure.core.async :refer [go-loop]]
            [aleph.tcp :refer [tcp-client]]
            [lamina.core :refer [enqueue wait-for-message wait-for-result]]
            [gloss.core :refer [string]]))

(let [ch (wait-for-result
          (tcp-client {:host "irc.freenode.net",
                       :port 6667,
                       :frame (string :utf-8 :delimiters ["\r\n"])}))]
  
  (enqueue ch "NICK clojure-user")
  (enqueue ch "USER clojure-user 8 *  : Jonny Blogger")
  (enqueue ch "JOIN #clojure")
  
  (go-loop []
    (when-let [msg (wait-for-message ch)]
      (when (.contains msg "PING")
        (enqueue ch "PONG"))
      (when (.contains msg "PRIVMSG #clojure")
        (println (str
                  (second (re-matches #":([a-zA-Z]+).*" msg))
                  " says: "
                  (second (re-matches #".*PRIVMSG #clojure :(.*)" msg)))))
      (recur)))
  
  (loop []
    (let [input (read-line)]
      (enqueue ch (str "PRIVMSG #clojure :" input))
      (recur))))
Pardon my enthusiasm, but this is exciting stuff for me.

So what is going on here is just as before, but we are now maintaining the connection, parsing out messages from the IRC channel and waiting for user input to send to the IRC server. Now - we have a 'sort-of fully working, but very very verrry bad IRC client. :)



The link to the full github repo is: https://github.com/chris-free/clj-irc-cli

For more information on the IRC protocol (now you know how basic it is) please refer to: http://oreilly.com/pub/h/1963

Another Clojure implementation: http://nakkaya.com/2010/02/10/a-simple-clojure-irc-client/

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.