{- This file is part of irc-fun-bot. - - Written in 2015, 2016 by fr33domlover . - - ♡ 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 - . -} module Network.IRC.Fun.Bot.Internal.State ( askConfig , askConfigS , askBehavior , askBehaviorS , askEnv , askEnvS , askConnection , askTimeGetter , askMinuteGetter , getMinutes , getState , getStateS , putState , modifyState , getHistory , stateToInfo , getChanInfo , getChans , putChans , modifyChans , addCurrChan , removeCurrChan , clearCurrChans , channelSelected , botMemberOf , defRespEnabled , setDefResp ) where import Control.Monad (liftM) import Control.Monad.IO.Class (liftIO) import Data.HashMap.Lazy (HashMap) import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust) import Data.Sequence (Seq) import Data.Text (Text) import Data.Time.Clock (UTCTime) import Network.IRC.Fun.Bot.Internal.Monad import Network.IRC.Fun.Bot.Internal.Types import Network.IRC.Fun.Client.IO (Connection) import Network.IRC.Fun.Types (Channel) import qualified Data.HashMap.Lazy as M import qualified Data.HashSet as S -- | Fetch the bot configuration. askConfig :: Session e s Config askConfig = asks beConfig -- | Retrieve a function of the bot configuration. askConfigS :: (Config -> a) -> Session e s a askConfigS f = asks $ f . beConfig -- | Fetch the bot behavior definition. askBehavior :: Session e s (Behavior e s) askBehavior = asks beBehavior -- | Retrieve a function of the bot behavior definition. askBehaviorS :: (Behavior e s -> a) -> Session e s a askBehaviorS f = asks $ f . beBehavior -- | Fetch the bot environment, i.e. read-only state. askEnv :: Session e s e askEnv = asks beCustom -- | Retrieve a function of the bot environment. askEnvS :: (e -> a) -> Session e s a askEnvS f = asks $ f . beCustom -- | Fetch the bot session socket handle. askConnection :: Session e s Connection askConnection = asks beConn -- | Fetch the time getter. The actual time data is cached and updated at most -- once per second depending on need. You can safely use it at any frequency -- withou overloading IO and time formatting. -- -- The second item is a formatted time string in the form -- @2015-09-01 18:10:00@, and is always expressed in UTC. askTimeGetter :: Session e s (IO (UTCTime, Text)) askTimeGetter = asks beGetTime -- | Fetch the minute getter. The actual data is cached and updated at most -- once per minute depending on need. You can safely use it at any frequency -- withou overloading IO and time formatting. askMinuteGetter :: Session e s (IO Int64) askMinuteGetter = asks beGetMinute -- | Get the number of minutes since the epoch using an auto updating counter. getMinutes :: Session e s Int64 getMinutes = do getMin <- askMinuteGetter liftIO getMin -- | Fetch the current value of the state within the session. getState :: Session e s s getState = gets bsPublic -- | Get a specific component of the state, using a projection function -- supplied. getStateS :: (s -> a) -> Session e s a getStateS f = gets $ f . bsPublic -- | Set the state within the session. putState :: s -> Session e s () putState st = modify $ \ old -> old { bsPublic = st } -- | Update the state to the result of applying a function to the current -- state. modifyState :: (s -> s) -> Session e s () modifyState f = modify $ \ old@(BotState { bsPublic = st }) -> old { bsPublic = f st } -- | Get a mapping between channel names and sequences of their last messages. getHistory :: Session e s (HashMap Channel (Seq HistoryLine)) getHistory = gets bsHistory -- Create channel info from its state stateToInfo :: ChanState -> ChanInfo stateToInfo (ChanState track count mlogger hls dr) = ChanInfo track count (isJust mlogger) hls dr -- | Get channel state information, in the form of a mapping from channel names -- to their data. getChanInfo :: Session e s (HashMap Channel ChanInfo) getChanInfo = fmap (M.map stateToInfo) getChans -- Get the channel state map. getChans :: Session e s (HashMap Channel ChanState) getChans = gets bsChannels -- Set a new value for the channel state map. putChans :: HashMap Channel ChanState -> Session e s () putChans chans = modify $ \ s -> s { bsChannels = chans } -- Update the channel state map value with the result of applying a function. modifyChans :: (HashMap Channel ChanState -> HashMap Channel ChanState) -> Session e s () modifyChans f = modify $ \ s -> s { bsChannels = f $ bsChannels s } -- Add a channel to the list of currently really-joined channels. addCurrChan :: Channel -> Session e s () addCurrChan chan = let f = S.insert chan in modify $ \ s -> s { bsCurrChans = f $ bsCurrChans s } -- Remove a channel from the list of currently really-joined channels. removeCurrChan :: Channel -> Session e s () removeCurrChan chan = let f = S.delete chan in modify $ \ s -> s { bsCurrChans = f $ bsCurrChans s } -- Remove all channels from the list of currently really-joined channels. clearCurrChans :: Session e s () clearCurrChans = modify $ \ s -> s { bsCurrChans = S.empty } -- | Check whether a channel is listed in persistent state to be joined. channelSelected :: Channel -> Session e s Bool channelSelected chan = liftM (chan `S.member`) $ gets bsSelChans -- | Check whether, as far as the bot knows, if it currently a member of the -- given channel. Currently kicks, bans, etc. are fully tracked, therefore this -- information isn't 100% accurate, but if you aren't planning to ban your bot -- you can probably rely on it. botMemberOf :: Channel -> Session e s Bool botMemberOf chan = liftM (chan `S.member`) $ gets bsCurrChans -- | Check whether a given channel has nonexistent command response enabled. defRespEnabled :: Channel -> Session e s Bool defRespEnabled chan = do chans <- getChans return $ fromMaybe False $ fmap csDefResponse $ M.lookup chan chans -- | Enable or disable nonexistent command response for a channel. Return -- whether the new state differs from the previous one. If no such channel -- exists in the bot state, nothing changes. setDefResp :: Channel -> Bool -> Session e s Bool setDefResp chan yes = do chans <- getChans case M.lookup chan chans of Just cstate -> if csDefResponse cstate /= yes then do let cstate' = cstate { csDefResponse = yes } chans' = M.insert chan cstate' chans putChans chans' return True else return False Nothing -> return False