lambdabot-4.2.1: Plugin/IRC.hs
{-# LANGUAGE TemplateHaskell, MultiParamTypeClasses #-}
-- | The plugin-level IRC interface.
module Plugin.IRC (theModule) where
import Control.Concurrent( forkIO, newQSem, waitQSem, threadDelay, signalQSem,
newEmptyMVar, putMVar, takeMVar, MVar )
import Control.Exception
import IRCBase
import LMain( received )
import Message
import Network( connectTo, PortID(..) )
import Plugin
import System.IO (hGetLine, hPutStr, hPutStrLn, hSetBuffering, BufferMode(NoBuffering), stderr, hClose)
import qualified Data.ByteString.Char8 as P
$(plugin "IRC")
instance Module IRCModule () where
modulePrivs _ = ["irc-connect"]
moduleHelp _ _ = "irc-connect tag host portnum nickname userinfo. connect to an irc server"
process_ _ "irc-connect" rest =
case (split " " rest) of
tag:hostn:portn:nickn:uix -> do pn <- (PortNumber . fromInteger) `fmap` readM portn
online tag hostn pn nickn (concatWith " " uix)
return []
_ -> return ["Not enough parameters!"]
----------------------------------------------------------------------
-- Encoding and decoding of messages
-- | 'encodeMessage' takes a message and converts it to a function.
-- giving this function a string will attach the string to the message
-- and output a string containing IRC protocol commands ready for writing
-- on the outgoing stream socket.
encodeMessage :: IrcMessage -> String -> String
encodeMessage msg
= encodePrefix (msgPrefix msg) . encodeCommand (msgCommand msg)
. encodeParams (msgParams msg)
where
encodePrefix [] = id
encodePrefix prefix = showChar ':' . showString prefix . showChar ' '
encodeCommand cmd = showString cmd
encodeParams [] = id
encodeParams (p:ps) = showChar ' ' . showString p . encodeParams ps
-- | 'decodeMessage' Takes an input line from the IRC protocol stream
-- and decodes it into a message. FIXME: this has too many parameters.
decodeMessage :: String -> String -> String -> IrcMessage
decodeMessage svr lbn line =
let (prefix, rest1) = decodePrefix (,) line
(cmd, rest2) = decodeCmd (,) rest1
params = decodeParams rest2
in IrcMessage { msgServer = svr, msgLBName = lbn, msgPrefix = prefix,
msgCommand = cmd, msgParams = params }
where
decodePrefix k (':':cs) = decodePrefix' k cs
where decodePrefix' j "" = j "" ""
decodePrefix' j (' ':ds) = j "" ds
decodePrefix' j (c:ds) = decodePrefix' (j . (c:)) ds
decodePrefix k cs = k "" cs
decodeCmd k [] = k "" ""
decodeCmd k (' ':cs) = k "" cs
decodeCmd k (c:cs) = decodeCmd (k . (c:)) cs
decodeParams :: String -> [String]
decodeParams xs = decodeParams' [] [] xs
where
decodeParams' param params []
| null param = reverse params
| otherwise = reverse (reverse param : params)
decodeParams' param params (' ' : cs)
| null param = decodeParams' [] params cs
| otherwise = decodeParams' [] (reverse param : params) cs
decodeParams' param params rest@(c@':' : cs)
| null param = reverse (rest : params)
| otherwise = decodeParams' (c:param) params cs
decodeParams' param params (c:cs) = decodeParams' (c:param) params cs
ircSignOn :: String -> Nick -> String -> LB ()
ircSignOn svr nickn ircname = do
send $ user (nTag nickn) (nName nickn) svr ircname
send $ setNick nickn
------------------------------------------------------------------------
--
-- Lambdabot is mostly synchronous. We have a main loop, which reads
-- messages and forks threads to execute commands (which write responces).
-- OR
-- We have a main loop which reads offline commands, and synchronously
-- interprets them.
online :: String -> String -> PortID -> String -> String -> ModuleT () LB ()
online tag hostn portnum nickn ui = do
sock <- io $ connectTo hostn portnum
io $ hSetBuffering sock NoBuffering
-- Implements flood control: RFC 2813, section 5.8
sem1 <- io $ newQSem 0
sem2 <- io $ newQSem 4 -- one extra token stays in the MVar
sendmv <- io $ newEmptyMVar
io $ forkIO $ sequence_ $ repeat $ do
waitQSem sem1
threadDelay 2000000
signalQSem sem2
io $ forkIO $ sequence_ $ repeat $ do
waitQSem sem2
putMVar sendmv ()
signalQSem sem1
catchError (addServer tag $ io . sendMsg tag sock sendmv)
(\err -> io (hClose sock) >> throwError err)
lift $ ircSignOn hostn (Nick tag nickn) ui
lift $ liftLB forkIO $ catchError (readerLoop tag nickn sock)
(\e -> do io $ hPutStrLn stderr $ "irc[" ++ tag ++ "] error: " ++ show e
remServer tag)
return ()
readerLoop :: String -> String -> Handle -> LB ()
readerLoop tag nickn sock = do
line <- io $ hGetLine sock
let line' = filter (\c -> c /= '\n' && c /= '\r') line
if "PING " `isPrefixOf` line'
then io $ hPutStr sock ("PONG " ++ drop 5 line' ++ "\r\n")
else do forkLB $ received (decodeMessage tag nickn line')
return ()
readerLoop tag nickn sock
sendMsg :: String -> Handle -> MVar () -> IrcMessage -> IO ()
sendMsg tag sock mv msg =
catchJust ioErrors (do takeMVar mv
P.hPut sock $ P.pack $ encodeMessage msg "\r\n")
(\err -> do hPutStrLn stderr $ "irc[" ++ tag ++ "] error: " ++ show err
hClose sock)