hask-irc/hask-irc-core/Network/IRC/Client.hs

155 lines
6.3 KiB
Haskell
Raw Normal View History

{-|
Module : Network.IRC.Client
Description : The IRC bot client used to create and run the bot.
Copyright : (c) Abhinav Sarkar, 2014
License : Apache-2.0
Maintainer : abhinav@abhinavsarkar.net
Stability : experimental
Portability : POSIX
-}
2014-05-22 01:08:36 +05:30
{-# LANGUAGE TemplateHaskell #-}
2014-05-04 04:28:08 +05:30
module Network.IRC.Client (runBot) where
2014-05-04 04:28:08 +05:30
2014-05-22 01:08:36 +05:30
import qualified System.Log.Logger as HSL
import ClassyPrelude
2014-06-01 23:14:19 +05:30
import Control.Concurrent.Lifted (fork, newChan, threadDelay, myThreadId, Chan)
import Control.Exception.Lifted (throwTo, AsyncException (UserInterrupt))
2014-05-21 11:20:53 +05:30
import Network (PortID (PortNumber), connectTo, withSocketsDo)
import System.IO (hSetBuffering, BufferMode(..))
2014-06-01 23:14:19 +05:30
import System.Log.Formatter (tfLogFormatter)
import System.Log.Handler (setFormatter)
import System.Log.Handler.Simple (streamHandler)
import System.Log.Logger (Priority (..), updateGlobalLogger, rootLoggerName,
setHandlers, setLevel)
2014-05-22 01:08:36 +05:30
import System.Log.Logger.TH (deriveLoggers)
2014-06-01 23:14:19 +05:30
import System.Posix.Signals (installHandler, sigINT, sigTERM, Handler (Catch))
2014-05-04 04:28:08 +05:30
import qualified Network.IRC.Handlers.Core as Core
import Network.IRC.Bot
import Network.IRC.Internal.Types
2014-06-08 07:12:33 +05:30
import Network.IRC.Types
import Network.IRC.Util
2014-05-21 00:06:37 +05:30
2014-05-22 01:08:36 +05:30
$(deriveLoggers "HSL" [HSL.DEBUG, HSL.ERROR])
2014-06-01 23:14:19 +05:30
coreMsgHandlerNames :: [MsgHandlerName]
coreMsgHandlerNames = ["pingpong", "help"]
connect :: BotConfig -> IO (Bot, MVar BotStatus, Channel Line, Channel Command, Channel Event)
connect botConfig@BotConfig { .. } = do
2014-05-22 01:08:36 +05:30
debugM "Connecting ..."
socket <- connectToWithRetry
hSetBuffering socket LineBuffering
2014-05-22 01:08:36 +05:30
debugM "Connected"
2014-05-22 20:59:02 +05:30
lineChan <- newChannel
commandChan <- newChannel
eventChan <- newChannel
mvBotStatus <- newMVar Connected
msgHandlers <- loadMsgHandlers (fst eventChan)
msgHandlerInfo' <- foldM (\m (hn, h) -> getHelp h botConfig >>= \hm -> return $ insertMap hn hm m)
mempty (mapToList msgHandlers)
let botConfig' = botConfig { msgHandlerInfo = msgHandlerInfo'}
return (Bot botConfig' socket msgHandlers, mvBotStatus, lineChan, commandChan, eventChan)
where
connectToWithRetry = connectTo (unpack server) (PortNumber (fromIntegral port))
`catch` (\(e :: SomeException) -> do
2014-05-22 01:08:36 +05:30
errorM ("Error while connecting: " ++ show e ++ ". Waiting.")
threadDelay (5 * oneSec)
connectToWithRetry)
2014-05-21 00:06:37 +05:30
newChannel = (,) <$> newChan <*> newEmptyMVar
mkMsgHandler :: Chan Event -> MsgHandlerName -> IO (Maybe MsgHandler)
2014-06-01 23:14:19 +05:30
mkMsgHandler eventChan name =
flip (`foldM` Nothing) msgHandlerMakers $ \finalHandler handler ->
case finalHandler of
Just _ -> return finalHandler
Nothing -> msgHandlerMaker handler botConfig eventChan name
2014-06-01 23:14:19 +05:30
2014-05-22 20:59:02 +05:30
loadMsgHandlers eventChan =
flip (`foldM` mempty) (mapKeys msgHandlerInfo) $ \hMap msgHandlerName -> do
debugM . unpack $ "Loading msg handler: " ++ msgHandlerName
2014-06-01 23:14:19 +05:30
mMsgHandler <- mkMsgHandler eventChan msgHandlerName
2014-05-22 20:59:02 +05:30
case mMsgHandler of
Nothing -> do
debugM . unpack $ "No msg handler found with name: " ++ msgHandlerName
return hMap
Just msgHandler -> return $ insertMap msgHandlerName msgHandler hMap
2014-05-21 00:38:01 +05:30
disconnect :: (Bot, MVar BotStatus, Channel Line, Channel Command, Channel Event) -> IO ()
2014-05-21 00:38:01 +05:30
disconnect (Bot { .. }, mvBotStatus, (_, readLatch), (commandChan, sendLatch), (eventChan, eventLatch)) = do
2014-05-22 01:08:36 +05:30
debugM "Disconnecting ..."
sendCommand commandChan $ toCommand QuitCmd
awaitLatch sendLatch
swapMVar mvBotStatus Disconnected
awaitLatch readLatch
2014-05-21 00:06:37 +05:30
sendEvent eventChan =<< toEvent QuitEvent
awaitLatch eventLatch
2014-05-21 00:38:01 +05:30
unloadMsgHandlers
handle (\(_ :: SomeException) -> return ()) $ hClose botSocket
2014-05-22 01:08:36 +05:30
debugM "Disconnected"
2014-05-21 00:38:01 +05:30
where
unloadMsgHandlers = forM_ (mapToList msgHandlers) $ \(msgHandlerName, msgHandler) -> do
2014-05-22 01:08:36 +05:30
debugM . unpack $ "Unloading msg handler: " ++ msgHandlerName
2014-05-21 00:38:01 +05:30
stopMsgHandler msgHandler botConfig
2014-05-04 04:28:08 +05:30
2014-06-01 23:14:19 +05:30
runBotIntenal :: BotConfig -> IO ()
runBotIntenal botConfig' = withSocketsDo $ do
status <- run
case status of
2014-06-01 23:14:19 +05:30
Disconnected -> debugM "Restarting .." >> runBotIntenal botConfig
Errored -> debugM "Restarting .." >> runBotIntenal botConfig
Interrupted -> return ()
NickNotAvailable -> return ()
_ -> error "Unsupported status"
where
2014-05-22 20:59:02 +05:30
botConfig = botConfig' {
msgHandlerInfo =
foldl' (\m name -> insertMap name mempty m) mempty
(hashNub $ mapKeys (msgHandlerInfo botConfig') ++ coreMsgHandlerNames),
msgHandlerMakers = ordNub $ Core.mkMsgHandler : msgHandlerMakers botConfig'
2014-05-22 20:59:02 +05:30
}
handleErrors :: SomeException -> IO BotStatus
2014-05-15 12:02:31 +05:30
handleErrors e = case fromException e of
2014-05-22 01:08:36 +05:30
Just UserInterrupt -> debugM "User interrupt" >> return Interrupted
_ -> debugM ("Exception! " ++ show e) >> return Errored
2014-06-01 23:14:19 +05:30
run = bracket (connect botConfig) disconnect $
2014-05-21 00:06:37 +05:30
\(bot, mvBotStatus, (lineChan, readLatch), (commandChan, sendLatch), eventChannel) ->
handle handleErrors $ do
2014-06-01 23:14:19 +05:30
debugM $ "Running with config:\n" ++ show botConfig
sendCommand commandChan $ toCommand NickCmd
sendCommand commandChan $ toCommand UserCmd
fork $ sendCommandLoop (commandChan, sendLatch) bot
fork $ readLineLoop mvBotStatus (lineChan, readLatch) bot oneSec
2014-05-21 00:06:37 +05:30
fork $ eventProcessLoop eventChannel lineChan commandChan bot
runIRC bot Connected (messageProcessLoop lineChan commandChan)
2014-06-01 23:14:19 +05:30
-- | Creates and runs an IRC bot for given the config. This IO action runs forever.
runBot :: BotConfig -- ^ The bot config used to create the bot.
-> IO ()
2014-06-01 23:14:19 +05:30
runBot botConfig = do
-- setup signal handling
mainThreadId <- myThreadId
installHandler sigINT (Catch $ throwTo mainThreadId UserInterrupt) Nothing
installHandler sigTERM (Catch $ throwTo mainThreadId UserInterrupt) Nothing
-- setup logging
hSetBuffering stdout LineBuffering
hSetBuffering stderr LineBuffering
stderrHandler <- streamHandler stderr DEBUG >>= \lh -> return $
setFormatter lh $ tfLogFormatter "%F %T" "[$utcTime] $loggername $prio $msg"
updateGlobalLogger rootLoggerName (setHandlers [stderrHandler] . setLevel DEBUG)
-- run
runBotIntenal botConfig