irc-fun-bot-0.5.0.0: src/Network/IRC/Fun/Bot/Internal/MsgCount.hs
{- This file is part of irc-fun-bot.
-
- Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
-
- ♡ Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Network.IRC.Fun.Bot.Internal.MsgCount
( startCountingAll
, startCountingChan
, startCountingChans
, stopCountingAll
, stopCountingChan
, stopCountingChans
, getCountLogs
, getCountLog
, chanIsCounted
, everParted
, everJoined
, countMsgs
, msgsSinceParted
, recordMsg
, recordJoin
, recordPart
, recordQuit
, countEvent
)
where
import Control.Monad (liftM, when)
import Control.Monad.Trans.RWS (modify, gets)
import Data.Foldable (any, foldl)
import Data.Sequence (Seq, ViewR (..), (|>))
import Network.IRC.Fun.Bot.Internal.Nicks (presence)
import Network.IRC.Fun.Bot.Internal.State
import Network.IRC.Fun.Bot.Internal.Types
import Prelude hiding (any, foldl)
import qualified Data.HashMap.Lazy as M
import qualified Data.Sequence as Q
import qualified Data.Sequence.Util as QU
import qualified Network.IRC.Fun.Client.Events as C (Event (..))
set :: Bool -> ChanState -> ChanState
set b cs = cs { csCounting = b }
enable :: ChanState -> ChanState
enable = set True
disable :: ChanState -> ChanState
disable = set False
-- | Start counting in all the channels the bot has joined which aren't
-- being counted.
startCountingAll :: Session e s ()
startCountingAll = modifyChans $ M.map enable
-- | Start counting in the given channel, if not counted already.
startCountingChan :: String -> Session e s ()
startCountingChan chan = modifyChans $ M.adjust enable chan
-- | Start counting in the channels not counted, among the ones given.
startCountingChans :: [String] -> Session e s ()
startCountingChans chans = do
chanmapAll <- getChans
let given = M.fromList (zip chans (repeat ()))
chanmapG = chanmapAll `M.intersection` given
chanmapD = M.filter (not . csCounting) chanmapG
chanmapE = M.map enable chanmapD
chanmapAllE = chanmapE `M.union` chanmapAll
putChans chanmapAllE
-- | Stop counting in all counted channels.
stopCountingAll :: Session e s ()
stopCountingAll =
modify $ \ bstate -> bstate
{ bsMsgCountLog = M.empty
, bsChannels = M.map disable $ bsChannels bstate
}
-- | Stop counting in the given channel, if counted.
stopCountingChan :: String -> Session e s ()
stopCountingChan chan =
modify $ \ bstate -> bstate
{ bsMsgCountLog = M.delete chan $ bsMsgCountLog bstate
, bsChannels = M.adjust disable chan $ bsChannels bstate
}
-- | Stop counting in the counted channels among the ones given.
stopCountingChans :: [String] -> Session e s ()
stopCountingChans chans =
modify $ \ bstate -> bstate
{ bsMsgCountLog =
bsMsgCountLog bstate
`M.difference`
M.fromList (zip chans (repeat ()))
, bsChannels =
let chanmap = bsChannels bstate
chanmapE = M.filter csCounting chanmap
chanmapD = M.map disable chanmapE
in chanmapD `M.union` chanmap
}
-- | Get a mapping between channel names and sequences of their message
-- counting data.
getCountLogs :: Session e s (M.HashMap String (Seq MsgCountEntry))
getCountLogs = gets bsMsgCountLog
-- | Get the count log for a specific channel. If there is none, an empty log
-- is returned.
getCountLog :: String -> Session e s (Seq MsgCountEntry)
getCountLog chan = liftM (M.lookupDefault Q.empty chan) getCountLogs
-- | Check whether a given channel is being counted.
chanIsCounted :: String -> Session e s Bool
chanIsCounted chan = liftM (maybe False csCounting . M.lookup chan) getChans
-- | Check whether a given user ever parted the channel, as far as the count
-- log remembers.
everParted :: String -> String -> Session e s Bool
nick `everParted` chan =
let p (MsgCountPart n) = n == nick
p _ = False
in liftM (any p) $ getCountLog chan
-- | Check whether a given user ever joined the channel, as far as the count
-- log remembers.
everJoined :: String -> String -> Session e s Bool
nick `everJoined` chan =
let p (MsgCountJoin n) = n == nick
p _ = False
in liftM (any p) $ getCountLog chan
-- | Determine how many messages a log sequence has recorded.
countMsgs :: Seq MsgCountEntry -> Int
countMsgs =
let f n (MsgCountMsgs m) = n + m
f n (MsgCountJoin _) = n
f n (MsgCountPart _) = n
in foldl f 0
-- | Find out how many messages the channel had since the given user parted it.
-- On success, return 'Right' it. Otherwise, i.e. if no parting of the user is
-- recorded, return 'Left' the total recorded messages.
msgsSinceParted :: String -> String -> Session e s (Either Int Int)
msgsSinceParted nick chan = do
cl <- getCountLog chan
let p (MsgCountPart n) = n == nick
p _ = False
(after, until) = Q.breakr p cl
count = countMsgs after
return $ if Q.null until
then Left count
else Right count
modifyLog :: String
-> (Seq MsgCountEntry -> Seq MsgCountEntry)
-> Session e s ()
modifyLog chan f = do
t <- chanIsCounted chan
when t $ do
n <- askConfigS cfgMaxMsgCount
let g logs =
let prev = M.lookupDefault Q.empty chan logs
new = f prev
fixed = if Q.length new > n then QU.tail new else new
in M.insert chan fixed logs
modify $ \ bstate -> bstate
{ bsMsgCountLog = g $ bsMsgCountLog bstate
}
-- | Count a message in the channel's count log.
recordMsg :: String -> Session e s ()
recordMsg chan =
let f q =
case Q.viewr q of
EmptyR -> Q.singleton $ MsgCountMsgs 1
l :> MsgCountMsgs n -> l |> MsgCountMsgs (n + 1)
_ :> _ -> q |> MsgCountMsgs 1
in modifyLog chan f
-- | Count a join in the channel's count log.
recordJoin :: String -> String -> Session e s ()
recordJoin nick chan =
let f = (|> (MsgCountJoin nick))
in modifyLog chan f
-- | Count a part (i.e. leave one channel) in the channel's count log.
recordPart :: String -> String -> Session e s ()
recordPart nick chan =
let f = (|> (MsgCountPart nick))
in modifyLog chan f
-- | Count a quit (i.e. leave all channels) in the channel's count log.
recordQuit :: String -> Session e s ()
recordQuit nick = do
chans <- presence nick
let chansPresent = M.fromList $ zip chans (repeat ())
chansCounted <- liftM (M.filter csCounting) getChans
let chansAct = chansPresent `M.intersection` chansCounted
logs <- getCountLogs
let logsAct = logs `M.intersection` chansAct
n <- askConfigS cfgMaxMsgCount
let f q =
let new = q |> MsgCountPart nick
in if Q.length new > n then QU.tail new else new
logsNew = M.map f logsAct
modify $ \ bstate -> bstate
{ bsMsgCountLog = logsNew `M.union` bsMsgCountLog bstate
}
-- | If en event needs to be considered in message counting, handle it.
countEvent :: C.Event -> Maybe (Msg a)
countEvent (C.Join chan nick) = Just $ MsgCountLogJoin nick chan
countEvent (C.Part chan nick _) = Just $ MsgCountLogPart nick chan
countEvent (C.Quit nick _) = Just $ MsgCountLogQuit nick
countEvent (C.ChannelMessage chan _ _ _) = Just $ MsgCountLogMsg chan
countEvent (C.ChannelAction chan _ _) = Just $ MsgCountLogMsg chan
countEvent _ = Nothing