diff --git a/Network/FastIRC.hs b/Network/FastIRC.hs
--- a/Network/FastIRC.hs
+++ b/Network/FastIRC.hs
@@ -10,6 +10,7 @@
 module Network.FastIRC
   ( module Network.FastIRC.Messages,
     module Network.FastIRC.ServerSet,
+    module Network.FastIRC.Types,
     module Network.FastIRC.Users,
     module Network.FastIRC.Utils
   )
@@ -17,5 +18,6 @@
 
 import Network.FastIRC.Messages
 import Network.FastIRC.ServerSet
+import Network.FastIRC.Types
 import Network.FastIRC.Users
 import Network.FastIRC.Utils
diff --git a/Network/FastIRC/Messages.hs b/Network/FastIRC/Messages.hs
--- a/Network/FastIRC/Messages.hs
+++ b/Network/FastIRC/Messages.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- |
 -- Module:     Network.FastIRC.Messages
 -- Copyright:  (c) 2010 Ertugrul Soeylemez
@@ -5,25 +7,34 @@
 -- Maintainer: Ertugrul Soeylemez
 -- Stability:  experimental
 --
--- Parser for IRC messages.
+-- Parser and printer for IRC messages.
 
 module Network.FastIRC.Messages
   ( -- * IRC messages
     Message(..),
     messageParser,
+    readMessage,
+    showMessage,
 
     -- * IRC commands
     Command(..),
-    commandParser
+    commandParser,
+    showCommand
   )
   where
 
 import qualified Data.ByteString.Char8 as B
+import qualified Data.Map as M
 import Control.Applicative
 import Data.Attoparsec.Char8 as P hiding (many)
+import Data.Char
+import Data.Map (Map)
+import Data.Maybe
 import Network.FastIRC.ServerSet
+import Network.FastIRC.Types
 import Network.FastIRC.Users
 import Network.FastIRC.Utils
+import Text.Printf
 
 
 -- | Data type for IRC messages.
@@ -39,11 +50,11 @@
 -- | Data type for IRC commands.
 
 data Command
-  -- | Arbitrary string command.
-  = StringCmd B.ByteString [B.ByteString]
+  = StringCmd CommandName [CommandArg]  -- ^ Arbitrary string command.
+  | NumericCmd Integer [CommandArg]     -- ^ Arbitrary numeric command.
 
-  -- | Arbitrary numeric command.
-  | NumericCmd Integer [B.ByteString]
+  -- | Join command with a list of channels as well as channel keys.
+  | JoinCmd (Map ChannelName (Maybe ChannelKey))
 
   deriving (Eq, Show)
 
@@ -52,27 +63,37 @@
 
 commandParser :: Parser Command
 commandParser =
-  try numCmd <|> stringCmd
+  try numCmd <|>
+  stringCmd
 
   where
-    lastArg :: Parser B.ByteString
-    lastArg =
-      char ':' *>
-      P.takeWhile isMessageChar
+    cmdArg :: Parser CommandArg
+    cmdArg = do
+      skipMany1 (char ' ')
+      try lastArg <|> takeWhile1 isIRCTokChar
 
-    cmdArgs :: Parser [B.ByteString]
-    cmdArgs =
-      many $ do
-        skipMany1 (char ' ')
-        try lastArg <|> takeWhile1 isIRCTokChar
+      where
+        lastArg :: Parser CommandArg
+        lastArg = char ':' *> P.takeWhile isMessageChar
 
-    stringCmd :: Parser Command
-    stringCmd = StringCmd <$> takeWhile1 isCommandChar <*> cmdArgs
+    joinCmd :: Parser Command
+    joinCmd = do
+      channels <- B.split ',' <$> cmdArg
+      keys <- option [] $ B.split ',' <$> cmdArg
+      many cmdArg
+      return . JoinCmd . M.fromList $ zip channels (map Just keys ++ repeat Nothing)
 
     numCmd :: Parser Command
-    numCmd = NumericCmd <$> decimal <*> cmdArgs
+    numCmd = NumericCmd <$> decimal <*> many cmdArg
 
+    stringCmd :: Parser Command
+    stringCmd = do
+      cmd <- B.map toUpper <$> takeWhile1 isCommandChar
+      case cmd of
+        "JOIN" -> joinCmd
+        _      -> StringCmd cmd <$> many cmdArg
 
+
 -- | Parser for IRC messages.
 
 messageParser :: ServerSet -> Parser Message
@@ -83,3 +104,61 @@
   where
     userSpec :: Parser UserSpec
     userSpec = char ':' *> userParser servers <* skipMany1 (char ' ')
+
+
+-- | Run the 'messageParser' parser.
+
+readMessage :: ServerSet -> MsgString -> Maybe Message
+readMessage = parseComplete . messageParser
+
+
+-- | Turn a 'Command' into a 'B.ByteString'.  Please note that a command
+-- does not contain an origin specification.  You should use
+-- 'showMessage' to format a an IRC message to be sent to the server.
+
+showCommand :: Command -> MsgString
+showCommand cmd =
+  case cmd of
+    StringCmd cmdStr args  -> B.append cmdStr (showArgs args)
+    NumericCmd cmdNum args ->
+      B.append (B.pack . printf "%03i" $ cmdNum)
+               (showArgs args)
+
+    JoinCmd channels ->
+      case formatJoins channels of
+        (chanList, "")      -> B.append "JOIN" (showArgs [chanList])
+        (chanList, keyList) -> B.append "JOIN" (showArgs [chanList, keyList])
+
+  where
+    formatJoins :: Map ChannelName (Maybe ChannelKey) ->
+                   (CommandArg, CommandArg)
+    formatJoins channels = (chanList, keyList)
+      where
+        (withKey, withoutKey) = M.partition isJust channels
+        chanWithKeyAssocs = M.assocs withKey
+        chanList = B.intercalate "," $ map fst chanWithKeyAssocs ++
+                                       M.keys withoutKey
+        keyList  = B.intercalate "," $ map (fromJust . snd) chanWithKeyAssocs
+
+    showArgs :: [CommandArg] -> MsgString
+    showArgs [] = B.empty
+    showArgs [arg]
+      | B.null arg        = " :"
+      | B.head arg == ':' = B.append " :" arg
+      | B.elem ' ' arg    = B.append " :" arg
+      | otherwise         = B.cons ' ' arg
+    showArgs (arg:args) =
+      B.append (B.cons ' ' arg) (showArgs args)
+
+
+-- | Turn a 'Message' into a 'B.ByteString'.  It will already contain
+-- \"\\r\\n\" and can be sent as is to the IRC server.
+
+showMessage :: Message -> MsgString
+showMessage (Message origin cmd) =
+  case origin of
+    Nothing -> B.append (showCommand cmd) "\r\n"
+    Just o  ->
+      B.concat [ ':' `B.cons` showUserSpec o,
+                 ' ' `B.cons` showCommand cmd,
+                 "\r\n" ]
diff --git a/Network/FastIRC/ServerSet.hs b/Network/FastIRC/ServerSet.hs
--- a/Network/FastIRC/ServerSet.hs
+++ b/Network/FastIRC/ServerSet.hs
@@ -19,20 +19,21 @@
     isServer,
 
     -- * Conversion
-    serverList,
-    serversFromList
+    serversFromList,
+    serversToList
   )
   where
 
 import qualified Data.ByteString.Char8 as B
 import qualified Data.Set as S
 import Data.Char
+import Network.FastIRC.Types
 
 
 -- | A set of servers.  This data type uses 'S.Set' internally, but
 -- the strings are handled case-insensitively.
 
-newtype ServerSet = ServerSet (S.Set B.ByteString)
+newtype ServerSet = ServerSet (S.Set ServerName)
 
 
 -- | Empty set of servers.
@@ -43,29 +44,29 @@
 
 -- | Add a server to a 'ServerSet'.
 
-addServer :: B.ByteString -> ServerSet -> ServerSet
+addServer :: ServerName -> ServerSet -> ServerSet
 addServer s (ServerSet ss) = ServerSet $ S.insert (B.map toLower s) ss
 
 
 -- | Remove a server from a 'ServerSet'.
 
-delServer :: B.ByteString -> ServerSet -> ServerSet
+delServer :: ServerName -> ServerSet -> ServerSet
 delServer s (ServerSet ss) = ServerSet $ S.delete (B.map toLower s) ss
 
 
 -- | Check whether specified server is in the set.
 
-isServer :: B.ByteString -> ServerSet -> Bool
+isServer :: ServerName -> ServerSet -> Bool
 isServer s (ServerSet ss) = S.member (B.map toLower s) ss
 
 
--- | Convert to list.
+-- | Build from list.
 
-serverList :: ServerSet -> [B.ByteString]
-serverList (ServerSet ss) = S.toList ss
+serversFromList :: [ServerName] -> ServerSet
+serversFromList = ServerSet . S.fromList
 
 
--- | Build from list.
+-- | Convert to list.
 
-serversFromList :: [B.ByteString] -> ServerSet
-serversFromList = ServerSet . S.fromList
+serversToList :: ServerSet -> [ServerName]
+serversToList (ServerSet ss) = S.toList ss
diff --git a/Network/FastIRC/Types.hs b/Network/FastIRC/Types.hs
new file mode 100644
--- /dev/null
+++ b/Network/FastIRC/Types.hs
@@ -0,0 +1,35 @@
+-- |
+-- Module:     Network.FastIRC.Types
+-- Copyright:  (c) 2010 Ertugrul Soeylemez
+-- License:    BSD3
+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
+-- Stability:  experimental
+--
+-- A number of convenient type aliases.
+
+module Network.FastIRC.Types
+  ( -- * Types
+    ChannelKey,
+    ChannelName,
+    CommandArg,
+    CommandName,
+    HostName,
+    MsgString,
+    NickName,
+    ServerName,
+    UserName
+  )
+  where
+
+import qualified Data.ByteString.Char8 as B
+
+
+type ChannelKey  = B.ByteString
+type ChannelName = B.ByteString
+type CommandArg  = B.ByteString
+type CommandName = B.ByteString
+type HostName    = B.ByteString
+type MsgString   = B.ByteString
+type NickName    = B.ByteString
+type ServerName  = B.ByteString
+type UserName    = B.ByteString
diff --git a/Network/FastIRC/Users.hs b/Network/FastIRC/Users.hs
--- a/Network/FastIRC/Users.hs
+++ b/Network/FastIRC/Users.hs
@@ -9,6 +9,7 @@
 
 module Network.FastIRC.Users
   ( UserSpec(..),
+    showUserSpec,
     userParser )
   where
 
@@ -17,6 +18,7 @@
 import Control.Monad
 import Data.Attoparsec.Char8 as P
 import Network.FastIRC.ServerSet
+import Network.FastIRC.Types
 import Network.FastIRC.Utils
 
 
@@ -24,12 +26,21 @@
 
 data UserSpec
   -- | Nickname.
-  = Nick B.ByteString
+  = Nick NickName
   -- | Nickname, username and hostname.
-  | User B.ByteString B.ByteString B.ByteString
+  | User NickName UserName HostName
   -- | IRC server.
-  | Server B.ByteString
+  | Server ServerName
   deriving (Eq, Read, Show)
+
+
+-- | Turn a 'UserSpec' into a 'B.ByteString' in a format suitable to be
+-- sent to the IRC server.
+
+showUserSpec :: UserSpec -> MsgString
+showUserSpec (Nick n) = n
+showUserSpec (User n u h) = B.concat [ n, B.cons '!' u, B.cons '@' h ]
+showUserSpec (Server sh) = sh
 
 
 -- | A 'Parser' for IRC users and servers.
diff --git a/Network/FastIRC/Utils.hs b/Network/FastIRC/Utils.hs
--- a/Network/FastIRC/Utils.hs
+++ b/Network/FastIRC/Utils.hs
@@ -9,6 +9,8 @@
 
 module Network.FastIRC.Utils
   ( -- * Character predicates for IRC
+    isChannelChar,
+    isChanPwdChar,
     isCommandChar,
     isHostChar,
     isIRCEOLChar,
@@ -17,6 +19,7 @@
     isNickChar,
     isServerChar,
     isUserChar,
+    isUserSpecChar,
 
     -- * Other helper functions
     parseComplete
@@ -27,16 +30,29 @@
 import Data.Attoparsec.Char8
 
 
+-- | Character predicate for channel names.
+
+isChannelChar :: Char -> Bool
+isChannelChar c = isIRCTokChar c && c /= ','
+
+
+-- | Character predicate for channel passwords.
+
+isChanPwdChar :: Char -> Bool
+isChanPwdChar = isChannelChar
+
+
 -- | Character predicate for IRC commands.
 
 isCommandChar :: Char -> Bool
 isCommandChar = inClass "A-Za-z0-9_"
 
 
--- | Character predicate for IRC tokens.
+-- | Character predicate for IRC user hostnames.  In the string @x!y\@z@
+-- the substring @z@ is the user's hostname.
 
-isIRCTokChar :: Char -> Bool
-isIRCTokChar c = c /= ' ' && c /= '\r' && c /= '\n'
+isHostChar :: Char -> Bool
+isHostChar = isUserSpecChar
 
 
 -- | Character predicate for IRC end of line characters.
@@ -45,16 +61,16 @@
 isIRCEOLChar c = c == '\n' || c == '\r'
 
 
--- | Character predicate for IRC messages.
+-- | Character predicate for IRC tokens.
 
-isMessageChar :: Char -> Bool
-isMessageChar c = c /= '\n' && c /= '\r'
+isIRCTokChar :: Char -> Bool
+isIRCTokChar c = c /= ' ' && c /= '\r' && c /= '\n'
 
 
--- | Character predicate for nicknames, usernames and hostnames.
+-- | Character predicate for IRC messages.
 
-isUserSpecChar :: Char -> Bool
-isUserSpecChar c = c > '!' && c /= '@'
+isMessageChar :: Char -> Bool
+isMessageChar c = c /= '\n' && c /= '\r'
 
 
 -- | Character predicate for IRC nicknames.  This function considers
@@ -65,6 +81,12 @@
 isNickChar = isUserSpecChar
 
 
+-- | Character predicate for IRC servers.
+
+isServerChar :: Char -> Bool
+isServerChar c = inClass "a-zA-Z0-9.:-" c || c >= '\x80'
+
+
 -- | Character predicate for IRC usernames.  In the string @x!y\@z@ the
 -- substring @y@ is the user's username.
 
@@ -72,17 +94,10 @@
 isUserChar = isUserSpecChar
 
 
--- | Character predicate for IRC user hostnames.  In the string @x!y\@z@
--- the substring @z@ is the user's hostname.
-
-isHostChar :: Char -> Bool
-isHostChar = isUserSpecChar
-
-
--- | Character predicate for IRC servers.
+-- | Character predicate for nicknames, usernames and hostnames.
 
-isServerChar :: Char -> Bool
-isServerChar c = inClass "a-zA-Z0-9.:-" c || c >= '\x80'
+isUserSpecChar :: Char -> Bool
+isUserSpecChar c = c > '!' && c /= '@'
 
 
 -- | Run a parser completely.
diff --git a/Test.hs b/Test.hs
deleted file mode 100644
--- a/Test.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Main where
-
-import Network.FastIRC
-import Network.FastIRC.Session
-
-
-main :: IO ()
-main = return ()
diff --git a/fastirc.cabal b/fastirc.cabal
--- a/fastirc.cabal
+++ b/fastirc.cabal
@@ -1,21 +1,24 @@
 Name:          fastirc
-Version:       0.1.1
+Version:       0.1.2
 Category:      Network
 Synopsis:      Fast Internet Relay Chat (IRC) library
-Maintainer:    Ertugrul Söylemez
-Author:        Ertugrul Söylemez
+Package-URL:   http://code.haskell.org/fastirc/
+Maintainer:    Ertugrul Söylemez <es@ertes.de>
+Author:        Ertugrul Söylemez <es@ertes.de>
 Copyright:     (c) 2010 Ertugrul Söylemez
 License:       BSD3
 License-file:  LICENSE
 Build-type:    Simple
 Stability:     experimental
-Cabal-version: >= 1.2
-Description:   Fast Internet Relay Chat (IRC) library.
-
+Cabal-version: >= 1.6
+Description:
+  Fast Internet Relay Chat (IRC) library.  This library implements a
+  attoparsec-based fast parser for IRC messages as well as a (currently
+  yet nonexistent) network manager for servers and clients.
 
-Flag debug
-  Description: Build the test program
-  Default:     False
+Source-repository head
+  Type:     darcs
+  Location: http://code.haskell.org/fastirc/
 
 
 Library
@@ -23,25 +26,25 @@
     attoparsec >= 0.8,
     base >= 4 && < 5,
     bytestring >= 0.9.1.4,
+    bytestring-show >= 0.3.3,
     containers >= 0.2.0.1,
     monadLib >= 3.6.1
-  GHC-Options: -O2
+  GHC-Options: -W
+  Extensions:
+    OverloadedStrings
   Exposed-modules:
-    Network.FastIRC,
-    Network.FastIRC.Messages,
-    Network.FastIRC.ServerSet,
-    Network.FastIRC.Session,
+    Network.FastIRC
+    Network.FastIRC.Messages
+    Network.FastIRC.ServerSet
+    Network.FastIRC.Session
+    Network.FastIRC.Types
     Network.FastIRC.Users
     Network.FastIRC.Utils
 
 
-Executable test
-  Build-depends:  base >= 4 && < 5
-  Main-is:        Test.hs
-  GHC-Options:    -O2
-  if flag(debug)
-    Buildable: True
-  else
-    Buildable: False
-  Other-modules:
-    Network.FastIRC
+-- Executable test
+--   Build-depends:  base >= 4 && < 5
+--   Main-is:        Test.hs
+--   GHC-Options:    -W
+--   Other-modules:
+--     Network.FastIRC
