/src/IRC.hs

http://github.com/Eelis/geordi · Haskell · 88 lines · 74 code · 11 blank · 3 comment · 6 complexity · 1d7c3b4c74b47c3667d6c8a86ea17bb3 MD5 · raw file

  1. {-# LANGUAGE UnicodeSyntax, PatternGuards #-}
  2. module IRC (Message(..), Command(..), encode, decode, UserName, ServerName, Prefix(..), send) where
  3. import qualified Network.IRC as Base
  4. import qualified Data.ByteString as ByteString
  5. import qualified Data.ByteString.Char8
  6. import qualified Data.ByteString.UTF8
  7. import qualified Codec.Binary.UTF8.String as UTF8
  8. import System.IO (hFlush, Handle)
  9. import Data.List (intersperse)
  10. import Control.Arrow (first)
  11. import Prelude hiding ((.))
  12. import Prelude.Unicode
  13. import Util ((.))
  14. data Prefix = Server ServerName | NickName String (Maybe UserName) (Maybe ServerName)
  15. deriving (Show, Read, Eq)
  16. data Message = Message { msg_prefix :: Maybe Prefix, msg_command :: Command }
  17. deriving (Show, Read, Eq)
  18. data Command
  19. = PrivMsg { privMsg_target, privMsg_text :: String }
  20. | Notice { notice_target, notice_text :: String }
  21. | Quit
  22. | Welcome
  23. | Invite { invite_nick, invite_channel :: String }
  24. | Nick String
  25. | Pass String
  26. | Ping { ping_servers :: [ByteString.ByteString] }
  27. | Pong { pong_servers :: [ByteString.ByteString] }
  28. | NickNameInUse
  29. | User { user :: String, mode :: Integer, realname :: String }
  30. | Join { join_chans :: [String], join_keychans :: [(String, String)] }
  31. | OtherCommand
  32. deriving (Show, Read, Eq)
  33. -- Clearly woefully incomplete, but all geordi needs.
  34. fromBase :: Base.Message Message
  35. fromBase (Base.Message prefix cmd params) =
  36. Message (fromPrefix . prefix) $ case (Data.ByteString.Char8.unpack cmd, params) of
  37. ("PING", x) Ping x
  38. ("PRIVMSG", [x, y]) PrivMsg (utf8dec x) (utf8dec y)
  39. ("QUIT", _) Quit
  40. ("INVITE", [x, y]) Invite (utf8dec x) (utf8dec y)
  41. ("001", _) Welcome
  42. ("433", _) NickNameInUse
  43. _ OtherCommand
  44. where
  45. utf8dec = Data.ByteString.UTF8.toString
  46. fromPrefix :: Base.Prefix -> Prefix
  47. fromPrefix (Base.Server n) = Server n
  48. fromPrefix (Base.NickName n x y) = NickName (utf8dec n) x y
  49. toBase :: Message Base.Message
  50. toBase (Message prefix command) =
  51. uncurry (Base.Message $ toPrefix . prefix) $ first Data.ByteString.Char8.pack $ case command of
  52. Pong x ("PONG", x)
  53. Pass x ("PASS", [utf8enc x])
  54. Nick x ("NICK", [utf8enc x])
  55. User u m n ("USER", utf8enc . [u, show m, "*", n])
  56. PrivMsg x y ("PRIVMSG", [utf8enc x, utf8enc y])
  57. Notice x y ("NOTICE", [utf8enc x, utf8enc y])
  58. Join c kc ("JOIN", map (utf8enc . concat . intersperse ",") $
  59. if null kc then [c] else [fst . kc ++ c, snd . kc])
  60. _ error "sorry, not implemented"
  61. where
  62. utf8enc = Data.ByteString.UTF8.fromString
  63. toPrefix :: Prefix -> Base.Prefix
  64. toPrefix (Server n) = Base.Server n
  65. toPrefix (NickName n x y) = Base.NickName (utf8enc n) x y
  66. encode :: Message String
  67. encode = Data.ByteString.UTF8.toString . Base.encode . toBase
  68. decode :: ByteString.ByteString Maybe Message
  69. decode = (fromBase .) . Base.decode
  70. type UserName = Base.UserName
  71. type ServerName = Base.ServerName
  72. send :: Handle Message IO ()
  73. send h m = Data.ByteString.Char8.hPutStrLn h (ByteString.pack $ take 510 $ enc 450 $ takeWhile (not . (`elem` "\r\n")) $ encode m) >> hFlush h
  74. where
  75. enc n (c:s) | c' ← UTF8.encode [c], n' n - length c', n' 0 length c' == 1 = c' ++ enc n' s
  76. enc _ _ = []
  77. -- Without enc, the last UTF-8 encoded character might get cut in half if its encoding consists of multiple bytes. We want to avoid this because it causes some IRC clients (like irssi) to conclude that the encoding must be something other than UTF-8. As a further complication, while we can be sure that the server will receive messages up to 512 bytes long, it may drop anything after 450 bytes or so as it prepends our prefix when relaying messages to other clients. Hence, we can only reliably encode UTF-8 characters until a message is 450 bytes long. We don't need to immediately truncate after 450 bytes, though; after that limit, we don't truncate until an actual multi-byte character is encountered.