-- Copyright (c) 2004 Thomas Jaeger
-- Copyright (c) 2005 Simon Winwood
-- Copyright (c) 2005 Don Stewart
-- Copyright (c) 2005 David House <dmouse@gmail.com>
--
-- | Logging an IRC channel..
--
module Lambdabot.Plugin.IRC.Log (logPlugin) where

import Lambdabot.Compat.FreenodeNick
import Lambdabot.IRC
import Lambdabot.Monad
import qualified Lambdabot.Message as Msg
import Lambdabot.Nick
import Lambdabot.Plugin
import Lambdabot.Util

import Control.Monad
import qualified Data.Map as M
import Data.Time
import System.Directory (createDirectoryIfMissing)
import System.FilePath
import System.IO

-- ------------------------------------------------------------------------

type Channel = Nick

type DateStamp = (Int, Int, Integer)
data ChanState = CS { ChanState -> Handle
chanHandle  :: Handle,
                      ChanState -> DateStamp
chanDate    :: DateStamp }
               deriving (Int -> ChanState -> ShowS
[ChanState] -> ShowS
ChanState -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ChanState] -> ShowS
$cshowList :: [ChanState] -> ShowS
show :: ChanState -> String
$cshow :: ChanState -> String
showsPrec :: Int -> ChanState -> ShowS
$cshowsPrec :: Int -> ChanState -> ShowS
Show, ChanState -> ChanState -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ChanState -> ChanState -> Bool
$c/= :: ChanState -> ChanState -> Bool
== :: ChanState -> ChanState -> Bool
$c== :: ChanState -> ChanState -> Bool
Eq)
type LogState = M.Map Channel ChanState
type Log = ModuleT LogState LB

data Event =
    Said Nick UTCTime String
    | Joined Nick String UTCTime
    | Parted Nick String UTCTime -- covers quitting as well
    | Kicked Nick Nick String UTCTime String
    | Renick Nick String UTCTime Nick
    | Mode Nick String UTCTime String
    deriving (Event -> Event -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Event -> Event -> Bool
$c/= :: Event -> Event -> Bool
== :: Event -> Event -> Bool
$c== :: Event -> Event -> Bool
Eq)

instance Show Event where
    show :: Event -> String
show (Said Channel
nick UTCTime
ct String
what)      = UTCTime -> String
timeStamp UTCTime
ct forall a. [a] -> [a] -> [a]
++ String
" <" forall a. [a] -> [a] -> [a]
++ Channel -> String
nName Channel
nick forall a. [a] -> [a] -> [a]
++ String
"> " forall a. [a] -> [a] -> [a]
++ String
what
    show (Joined Channel
nick String
usr UTCTime
ct)     = UTCTime -> String
timeStamp UTCTime
ct forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
nick)
                                    forall a. [a] -> [a] -> [a]
++ String
" (" forall a. [a] -> [a] -> [a]
++ String
usr forall a. [a] -> [a] -> [a]
++ String
") joined."
    show (Parted Channel
nick String
usr UTCTime
ct)     = UTCTime -> String
timeStamp UTCTime
ct forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
nick)
                                    forall a. [a] -> [a] -> [a]
++ String
" (" forall a. [a] -> [a] -> [a]
++ String
usr forall a. [a] -> [a] -> [a]
++ String
") left."
    show (Kicked Channel
nick Channel
op String
usrop UTCTime
ct String
reason) = UTCTime -> String
timeStamp UTCTime
ct forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
nick)
                                            forall a. [a] -> [a] -> [a]
++ String
" was kicked by " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
op)
                                            forall a. [a] -> [a] -> [a]
++ String
" (" forall a. [a] -> [a] -> [a]
++ String
usrop forall a. [a] -> [a] -> [a]
++ String
"): " forall a. [a] -> [a] -> [a]
++ String
reason forall a. [a] -> [a] -> [a]
++ String
"."
    show (Renick Channel
nick String
usr UTCTime
ct Channel
new) = UTCTime -> String
timeStamp UTCTime
ct forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
nick)
                                    forall a. [a] -> [a] -> [a]
++ String
" (" forall a. [a] -> [a] -> [a]
++ String
usr forall a. [a] -> [a] -> [a]
++ String
") is now " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
new) forall a. [a] -> [a] -> [a]
++ String
"."
    show (Mode Channel
nick String
usr UTCTime
ct String
mode)  = UTCTime -> String
timeStamp UTCTime
ct forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show (Channel -> FreenodeNick
FreenodeNick Channel
nick)
                                    forall a. [a] -> [a] -> [a]
++ String
" (" forall a. [a] -> [a] -> [a]
++ String
usr forall a. [a] -> [a] -> [a]
++ String
") changed mode to " forall a. [a] -> [a] -> [a]
++ String
mode forall a. [a] -> [a] -> [a]
++ String
"."

-- * Dispatchers and Module instance declaration
--
logPlugin :: Module (M.Map Channel ChanState)
logPlugin :: Module LogState
logPlugin = forall st. Module st
newModule
    { moduleDefState :: LB LogState
moduleDefState  = forall (m :: * -> *) a. Monad m => a -> m a
return forall k a. Map k a
M.empty
    , moduleExit :: ModuleT LogState LB ()
moduleExit      = ModuleT LogState LB ()
cleanLogState
    , moduleInit :: ModuleT LogState LB ()
moduleInit      = do
        let doLog :: (t -> a -> b) -> t -> Handle -> a -> ModuleT LogState LB ()
doLog t -> a -> b
f t
m Handle
hdl = Handle -> String -> ModuleT LogState LB ()
logString Handle
hdl forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show forall b c a. (b -> c) -> (a -> b) -> a -> c
. t -> a -> b
f t
m
            connect :: String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
signal IrcMessage -> UTCTime -> b
cb = forall st. String -> Callback st -> ModuleT st LB ()
registerCallback String
signal forall a b. (a -> b) -> a -> b
$ \IrcMessage
msg -> do
                UTCTime
now <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
io IO UTCTime
getCurrentTime
                -- map over the channels this message was directed to, adding to each
                -- of their log files.
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall a.
(Handle -> UTCTime -> Log a) -> UTCTime -> Channel -> Log a
withValidLog (forall {b} {t} {a}.
Show b =>
(t -> a -> b) -> t -> Handle -> a -> ModuleT LogState LB ()
doLog IrcMessage -> UTCTime -> b
cb IrcMessage
msg) UTCTime
now) (forall a. Message a => a -> [Channel]
Msg.channels IrcMessage
msg)

        forall {b}.
Show b =>
String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
"PRIVMSG" IrcMessage -> UTCTime -> Event
msgCB
        forall {b}.
Show b =>
String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
"JOIN"    IrcMessage -> UTCTime -> Event
joinCB
        forall {b}.
Show b =>
String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
"PART"    IrcMessage -> UTCTime -> Event
partCB
        forall {b}.
Show b =>
String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
"KICK"    IrcMessage -> UTCTime -> Event
kickCB
        forall {b}.
Show b =>
String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
"NICK"    IrcMessage -> UTCTime -> Event
nickCB
        forall {b}.
Show b =>
String -> (IrcMessage -> UTCTime -> b) -> ModuleT LogState LB ()
connect String
"MODE"    IrcMessage -> UTCTime -> Event
modeCB
    }

-- * Logging helpers
--

-- | Show a number, padded to the left with zeroes up to the specified width
showWidth :: Int    -- ^ Width to fill to
          -> Int    -- ^ Number to show
          -> String -- ^ Padded string
showWidth :: Int -> Int -> String
showWidth Int
width Int
n = String
zeroes forall a. [a] -> [a] -> [a]
++ String
num
    where num :: String
num    = forall a. Show a => a -> String
show Int
n
          zeroes :: String
zeroes = forall a. Int -> a -> [a]
replicate (Int
width forall a. Num a => a -> a -> a
- forall (t :: * -> *) a. Foldable t => t a -> Int
length String
num) Char
'0'

timeStamp :: UTCTime -> String
timeStamp :: UTCTime -> String
timeStamp (UTCTime Day
_ DiffTime
ct) =
    (Int -> Int -> String
showWidth Int
2 (Int
hours forall a. Integral a => a -> a -> a
`mod` Int
24)) forall a. [a] -> [a] -> [a]
++ String
":" forall a. [a] -> [a] -> [a]
++
    (Int -> Int -> String
showWidth Int
2 (Int
mins  forall a. Integral a => a -> a -> a
`mod` Int
60)) forall a. [a] -> [a] -> [a]
++ String
":" forall a. [a] -> [a] -> [a]
++
    (Int -> Int -> String
showWidth Int
2 (Int
secs  forall a. Integral a => a -> a -> a
`mod` Int
60))
    where
        secs :: Int
secs  = forall a b. (RealFrac a, Integral b) => a -> b
round DiffTime
ct :: Int
        mins :: Int
mins  = Int
secs forall a. Integral a => a -> a -> a
`div` Int
60
        hours :: Int
hours = Int
mins forall a. Integral a => a -> a -> a
`div` Int
60

-- | Show a DateStamp.
dateToString :: DateStamp -> String
dateToString :: DateStamp -> String
dateToString (Int
d, Int
m, Integer
y) = (Int -> Int -> String
showWidth Int
2 forall a b. (a -> b) -> a -> b
$ forall a. Num a => Integer -> a
fromInteger Integer
y) forall a. [a] -> [a] -> [a]
++ String
"-" forall a. [a] -> [a] -> [a]
++
                         (Int -> Int -> String
showWidth Int
2 forall a b. (a -> b) -> a -> b
$ forall a. Enum a => a -> Int
fromEnum Int
m forall a. Num a => a -> a -> a
+ Int
1) forall a. [a] -> [a] -> [a]
++ String
"-" forall a. [a] -> [a] -> [a]
++
                         (Int -> Int -> String
showWidth Int
2 Int
d)

-- | UTCTime -> DateStamp conversion
dateStamp :: UTCTime -> DateStamp
dateStamp :: UTCTime -> DateStamp
dateStamp (UTCTime Day
day DiffTime
_) = (Int
d, Int
m, Integer
y)
    where (Integer
y,Int
m,Int
d) = Day -> (Integer, Int, Int)
toGregorian Day
day

-- * State manipulation functions
--

-- | Cleans up after the module (closes files)
cleanLogState :: Log ()
cleanLogState :: ModuleT LogState LB ()
cleanLogState =
    forall (m :: * -> *) a.
MonadLBState m =>
(LBState m -> (LBState m -> m ()) -> m a) -> m a
withMS forall a b. (a -> b) -> a -> b
$ \LBState (ModuleT LogState LB)
state LBState (ModuleT LogState LB) -> ModuleT LogState LB ()
writer -> do
      forall (m :: * -> *) a. MonadIO m => IO a -> m a
io forall a b. (a -> b) -> a -> b
$ forall a b k. (a -> b -> b) -> b -> Map k a -> b
M.foldr (\ChanState
cs IO ()
iom -> IO ()
iom forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Handle -> IO ()
hClose (ChanState -> Handle
chanHandle ChanState
cs)) (forall (m :: * -> *) a. Monad m => a -> m a
return ()) LBState (ModuleT LogState LB)
state
      LBState (ModuleT LogState LB) -> ModuleT LogState LB ()
writer forall k a. Map k a
M.empty

-- | Fetch a channel from the internal map. Uses LB's fail if not found.
getChannel :: Channel -> Log ChanState
getChannel :: Channel -> Log ChanState
getChannel Channel
c = (forall (m :: * -> *). MonadLBState m => m (LBState m)
readMS forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {m :: * -> *} {k} {a}.
(MonadFail m, Ord k) =>
k -> Map k a -> m a
mLookup forall a b. (a -> b) -> a -> b
$ Channel
c
    where mLookup :: k -> Map k a -> m a
mLookup k
k = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"getChannel: not found") forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup k
k

getDate :: Channel -> Log DateStamp
getDate :: Channel -> Log DateStamp
getDate Channel
c = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ChanState -> DateStamp
chanDate forall b c a. (b -> c) -> (a -> b) -> a -> c
. Channel -> Log ChanState
getChannel forall a b. (a -> b) -> a -> b
$ Channel
c

getHandle :: Channel -> Log Handle
getHandle :: Channel -> Log Handle
getHandle Channel
c = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ChanState -> Handle
chanHandle forall b c a. (b -> c) -> (a -> b) -> a -> c
. Channel -> Log ChanState
getChannel forall a b. (a -> b) -> a -> b
$ Channel
c
    -- add points. otherwise:
    -- Unbound implicit parameters (?ref::GHC.IOBase.MVar LogState, ?name::String)
    --  arising from instantiating a type signature at
    -- Plugin/Log.hs:187:30-39
    -- Probable cause: `getChannel' is applied to too few arguments

-- | Put a DateStamp and a Handle. Used by 'openChannelFile' and
--  'reopenChannelMaybe'.
putHdlAndDS :: Channel -> Handle -> DateStamp -> Log ()
putHdlAndDS :: Channel -> Handle -> DateStamp -> ModuleT LogState LB ()
putHdlAndDS Channel
c Handle
hdl DateStamp
ds =
        forall (m :: * -> *).
MonadLBState m =>
(LBState m -> LBState m) -> m ()
modifyMS (forall k a. Ord k => (a -> a) -> k -> Map k a -> Map k a
M.adjust (\ChanState
cs -> ChanState
cs {chanHandle :: Handle
chanHandle = Handle
hdl, chanDate :: DateStamp
chanDate = DateStamp
ds}) Channel
c)


-- * Logging IO
--

-- | Open a file to write the log to.
openChannelFile :: Channel -> UTCTime -> Log Handle
openChannelFile :: Channel -> UTCTime -> Log Handle
openChannelFile Channel
chan UTCTime
ct = do
    String
logDir <- forall (m :: * -> *) a. MonadLB m => LB a -> m a
lb forall a b. (a -> b) -> a -> b
$ String -> LB String
findLBFileForWriting String
"Log"
    let dir :: String
dir  = String
logDir String -> ShowS
</> Channel -> String
nTag Channel
chan String -> ShowS
</> Channel -> String
nName Channel
chan
        file :: String
file = String
dir String -> ShowS
</> (DateStamp -> String
dateToString DateStamp
date) String -> ShowS
<.> String
"txt"
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
io forall a b. (a -> b) -> a -> b
$ Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
dir forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> String -> IOMode -> IO Handle
openFile String
file IOMode
AppendMode
    where date :: DateStamp
date = UTCTime -> DateStamp
dateStamp UTCTime
ct

-- | Close and re-open a log file, and update the state.
reopenChannelMaybe :: Channel -> UTCTime -> Log ()
reopenChannelMaybe :: Channel -> UTCTime -> ModuleT LogState LB ()
reopenChannelMaybe Channel
chan UTCTime
ct = do
  DateStamp
date <- Channel -> Log DateStamp
getDate Channel
chan
  forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (DateStamp
date forall a. Eq a => a -> a -> Bool
/= UTCTime -> DateStamp
dateStamp UTCTime
ct) forall a b. (a -> b) -> a -> b
$ do
    Handle
hdl <- Channel -> Log Handle
getHandle Channel
chan
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
io forall a b. (a -> b) -> a -> b
$ Handle -> IO ()
hClose Handle
hdl
    Handle
hdl' <- Channel -> UTCTime -> Log Handle
openChannelFile Channel
chan UTCTime
ct
    Channel -> Handle -> DateStamp -> ModuleT LogState LB ()
putHdlAndDS Channel
chan Handle
hdl' (UTCTime -> DateStamp
dateStamp UTCTime
ct)

-- | Initialise the channel state (if it not already inited)
initChannelMaybe :: Nick -> UTCTime -> Log ()
initChannelMaybe :: Channel -> UTCTime -> ModuleT LogState LB ()
initChannelMaybe Channel
chan UTCTime
ct = do
  Bool
chanp <- forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM (forall k a. Ord k => k -> Map k a -> Bool
M.member Channel
chan) forall (m :: * -> *). MonadLBState m => m (LBState m)
readMS
  forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
chanp forall a b. (a -> b) -> a -> b
$ do
    Handle
hdl <- Channel -> UTCTime -> Log Handle
openChannelFile Channel
chan UTCTime
ct
    forall (m :: * -> *).
MonadLBState m =>
(LBState m -> LBState m) -> m ()
modifyMS (forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert Channel
chan forall a b. (a -> b) -> a -> b
$ Handle -> DateStamp -> ChanState
CS Handle
hdl (UTCTime -> DateStamp
dateStamp UTCTime
ct))

-- | Ensure that the log is correctly initialised etc.
withValidLog :: (Handle -> UTCTime -> Log a) -> UTCTime -> Channel -> Log a
withValidLog :: forall a.
(Handle -> UTCTime -> Log a) -> UTCTime -> Channel -> Log a
withValidLog Handle -> UTCTime -> Log a
f UTCTime
ct Channel
chan = do
  Channel -> UTCTime -> ModuleT LogState LB ()
initChannelMaybe Channel
chan UTCTime
ct
  Channel -> UTCTime -> ModuleT LogState LB ()
reopenChannelMaybe Channel
chan UTCTime
ct
  Handle
hdl <- Channel -> Log Handle
getHandle Channel
chan
  a
rv <- Handle -> UTCTime -> Log a
f Handle
hdl UTCTime
ct
  forall (m :: * -> *) a. Monad m => a -> m a
return a
rv

-- | Log a string. Main logging workhorse.
logString :: Handle -> String -> Log ()
logString :: Handle -> String -> ModuleT LogState LB ()
logString Handle
hdl String
str = forall (m :: * -> *) a. MonadIO m => IO a -> m a
io forall a b. (a -> b) -> a -> b
$ Handle -> String -> IO ()
hPutStrLn Handle
hdl String
str forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Handle -> IO ()
hFlush Handle
hdl
  -- We flush on each operation to ensure logs are up to date.

-- * The event loggers themselves
--

-- | When somebody joins.
joinCB :: IrcMessage -> UTCTime -> Event
joinCB :: IrcMessage -> UTCTime -> Event
joinCB IrcMessage
msg UTCTime
ct = Channel -> String -> UTCTime -> Event
Joined (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg) (forall a. Message a => a -> String
Msg.fullName IrcMessage
msg) UTCTime
ct

-- | When somebody quits.
partCB :: IrcMessage -> UTCTime -> Event
partCB :: IrcMessage -> UTCTime -> Event
partCB IrcMessage
msg UTCTime
ct = Channel -> String -> UTCTime -> Event
Parted (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg) (forall a. Message a => a -> String
Msg.fullName IrcMessage
msg) UTCTime
ct

-- | When somebody is kicked.
kickCB :: IrcMessage -> UTCTime -> Event
kickCB :: IrcMessage -> UTCTime -> Event
kickCB IrcMessage
msg UTCTime
ct = Channel -> Channel -> String -> UTCTime -> String -> Event
Kicked (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg) { nName :: String
nName = forall a. [a] -> a
head forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
tail forall a b. (a -> b) -> a -> b
$ IrcMessage -> [String]
ircMsgParams IrcMessage
msg }
                       (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg)
                       (forall a. Message a => a -> String
Msg.fullName IrcMessage
msg)
                       UTCTime
ct
                       (forall a. [a] -> [a]
tail forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
tail forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
tail forall a b. (a -> b) -> a -> b
$ IrcMessage -> [String]
ircMsgParams IrcMessage
msg)

-- | When somebody changes his\/her name.
-- TODO:  We should only do this for channels that the user is currently on.
nickCB :: IrcMessage -> UTCTime -> Event
nickCB :: IrcMessage -> UTCTime -> Event
nickCB IrcMessage
msg UTCTime
ct = Channel -> String -> UTCTime -> Channel -> Event
Renick (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg) (forall a. Message a => a -> String
Msg.fullName IrcMessage
msg) UTCTime
ct
                       (String -> String -> Channel
parseNick (forall a. Message a => a -> String
Msg.server IrcMessage
msg) forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ forall a. [a] -> a
head forall a b. (a -> b) -> a -> b
$ IrcMessage -> [String]
ircMsgParams IrcMessage
msg)

-- | When somebody changes channel mode.
modeCB :: IrcMessage -> UTCTime -> Event
modeCB :: IrcMessage -> UTCTime -> Event
modeCB IrcMessage
msg UTCTime
ct = Channel -> String -> UTCTime -> String -> Event
Mode (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg) (forall a. Message a => a -> String
Msg.fullName IrcMessage
msg) UTCTime
ct
                     ([String] -> String
unwords forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
tail forall a b. (a -> b) -> a -> b
$ IrcMessage -> [String]
ircMsgParams IrcMessage
msg)

-- | When somebody speaks.
msgCB :: IrcMessage -> UTCTime -> Event
msgCB :: IrcMessage -> UTCTime -> Event
msgCB IrcMessage
msg UTCTime
ct = Channel -> UTCTime -> String -> Event
Said (forall a. Message a => a -> Channel
Msg.nick IrcMessage
msg) UTCTime
ct
                    (forall a. [a] -> [a]
tail forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
tail forall a b. (a -> b) -> a -> b
$ IrcMessage -> [String]
ircMsgParams IrcMessage
msg)
                      -- each lines is :foo