{-# LANGUAGE FlexibleContexts #-} module Network.IRC.Handlers.MessageLogger (messageLoggerMsgHandlerMaker) where import qualified Data.Text.Format as TF import qualified Data.Text.Format.Params as TF import ClassyPrelude hiding ((), (<.>), FilePath, log) import Data.Time (diffDays) import System.Directory (createDirectoryIfMissing, getModificationTime, copyFile, removeFile) import System.FilePath (FilePath, (), (<.>)) import System.IO (openFile, IOMode(..), hSetBuffering, BufferMode(..)) import qualified Network.IRC.Configuration as CF import Network.IRC import Network.IRC.Util type LoggerState = Maybe (Handle, Day) messageLoggerMsgHandlerMaker :: MsgHandlerMaker messageLoggerMsgHandlerMaker = MsgHandlerMaker "messagelogger" go where go botConfig _ = do state <- io $ newIORef Nothing initMessageLogger botConfig state return $ newMsgHandler { onMessage = flip messageLogger state , onStop = exitMessageLogger state } getLogFilePath :: BotConfig -> IO FilePath getLogFilePath BotConfig { .. } = do let logFileDir = CF.require "messagelogger.logdir" config :: Text createDirectoryIfMissing True (unpack logFileDir) return $ unpack logFileDir unpack (botChannel ++ "-" ++ nickToText botNick) <.> "log" openLogFile :: FilePath -> IO Handle openLogFile logFilePath = do logFileHandle <- openFile logFilePath AppendMode hSetBuffering logFileHandle LineBuffering return logFileHandle initMessageLogger :: BotConfig -> IORef LoggerState -> IO () initMessageLogger botConfig state = do logFilePath <- getLogFilePath botConfig logFileHandle <- openLogFile logFilePath time <- getModificationTime logFilePath atomicWriteIORef state $ Just (logFileHandle, utctDay time) exitMessageLogger :: MonadMsgHandler m => IORef LoggerState -> m () exitMessageLogger state = io $ readIORef state >>= flip whenJust (hClose . fst) withLogFile :: MonadMsgHandler m => (Handle -> IO ()) -> IORef LoggerState -> m [Message] withLogFile action state = do botConfig <- ask io $ do Just (logFileHandle, prevDay) <- readIORef state curDay <- map utctDay getCurrentTime let diff = diffDays curDay prevDay logFileHandle' <- if diff >= 1 then do hClose logFileHandle logFilePath <- getLogFilePath botConfig mask_ $ do copyFile logFilePath (logFilePath <.> show prevDay) removeFile logFilePath openLogFile logFilePath else return logFileHandle action logFileHandle' atomicWriteIORef state $ Just (logFileHandle', curDay) return [] messageLogger :: MonadMsgHandler m => Message -> IORef LoggerState -> m [Message] messageLogger Message { .. } | Just (ChannelMsg user msg) <- fromMessage message = log "<{}> {}" [nick user, msg] | Just (ActionMsg user msg) <- fromMessage message = log "<{}> {} {}" [nick user, nick user, msg] | Just (KickMsg user kickedNick msg) <- fromMessage message = log "** {} KICKED {} :{}" [nick user, nickToText kickedNick, msg] | Just (JoinMsg user) <- fromMessage message = log "** {} JOINED" [nick user] | Just (PartMsg user msg) <- fromMessage message = log "** {} PARTED :{}" [nick user, msg] | Just (QuitMsg user msg) <- fromMessage message = log "** {} QUIT :{}" [nick user, msg] | Just (NickMsg user newNick) <- fromMessage message = log "** {} CHANGED NICK TO {}" [nick user, nickToText newNick] | Just (NamesMsg nicks) <- fromMessage message = log "** USERS {}" [unwords . map nickToText $ nicks] | otherwise = const $ return [] where nick = nickToText . userNick log format args = withLogFile $ \logFile -> TF.hprint logFile ("[{}] " ++ format ++ "\n") $ TF.buildParams (fmtTime msgTime : args) fmtTime = pack . formatTime defaultTimeLocale "%F %T"