hask-irc/hask-irc-handlers/Network/IRC/Handlers/NickTracker.hs

208 lines
9.5 KiB
Haskell
Raw Normal View History

2014-05-23 02:45:45 +05:30
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Network.IRC.Handlers.NickTracker (mkMsgHandler) where
2014-05-25 14:51:33 +05:30
import qualified Data.Configurator as CF
import qualified Data.IxSet as IS
import qualified Data.UUID as U
import qualified Data.UUID.V4 as U
2014-05-23 02:45:45 +05:30
2014-06-01 23:14:19 +05:30
import ClassyPrelude hiding (swap)
import Control.Monad.Reader (ask)
import Control.Monad.State (get, put)
import Data.Acid (AcidState, Query, Update, makeAcidic, query, update,
openLocalState, createArchive)
import Data.Acid.Local (createCheckpointAndClose)
import Data.Convertible (convert)
import Data.IxSet (getOne, (@=))
import Data.Time (addUTCTime, NominalDiffTime)
2014-05-23 02:45:45 +05:30
import Network.IRC.Handlers.NickTracker.Types
2014-06-01 02:11:20 +05:30
import Network.IRC.Types
2014-05-23 02:45:45 +05:30
import Network.IRC.Util
2014-06-01 02:11:20 +05:30
-- database
getByNickQ :: Nick -> Query NickTracking (Maybe NickTrack)
getByNickQ nick = do
2014-05-23 02:45:45 +05:30
NickTracking { .. } <- ask
return . getOne $ nickTracking @= nick
getByCanonicalNickQ :: CanonicalNick -> Query NickTracking [NickTrack]
getByCanonicalNickQ canonicalNick = do
2014-05-23 02:45:45 +05:30
NickTracking { .. } <- ask
return . IS.toList $ nickTracking @= canonicalNick
saveNickTrackQ :: NickTrack -> Update NickTracking ()
saveNickTrackQ nt = do
2014-05-23 02:45:45 +05:30
NickTracking { .. } <- get
put . NickTracking $ IS.updateIx (nick nt) nt nickTracking
$(makeAcidic ''NickTracking ['getByNickQ, 'getByCanonicalNickQ, 'saveNickTrackQ])
2014-06-01 02:11:20 +05:30
getByNick :: AcidState NickTracking -> Nick -> IO (Maybe NickTrack)
getByNick acid = query acid . GetByNickQ
saveNickTrack :: AcidState NickTracking -> NickTrack -> IO ()
saveNickTrack acid = update acid . SaveNickTrackQ
2014-05-23 02:45:45 +05:30
2014-06-01 02:11:20 +05:30
-- handler
2014-05-25 14:51:33 +05:30
data NickTrackingState = NickTrackingState { acid :: AcidState NickTracking
, refreshInterval :: NominalDiffTime
, onlineNicks :: HashSet Nick
, lastRefreshOn :: UTCTime }
2014-06-01 06:48:24 +05:30
nickTrackerMsg :: MonadMsgHandler m => IORef NickTrackingState -> Message -> m [Command]
nickTrackerMsg state message@Message { .. } = case msgDetails of
ChannelMsg { .. } -> updateNickTrack state user msg msgTime >> handleCommands
2014-06-01 06:48:24 +05:30
ActionMsg { .. } -> updateNickTrack state user msg msgTime >> return []
JoinMsg { .. } -> updateNickTrack state user "" msgTime >> add user >> return []
PartMsg { .. } -> updateNickTrack state user msg msgTime >> remove user >> return []
QuitMsg { .. } -> updateNickTrack state user msg msgTime >> remove user >> return []
2014-05-25 14:51:33 +05:30
NickMsg { .. } ->
2014-06-01 06:48:24 +05:30
handleNickChange state user newNick msgTime >> swap (user, User newNick "") >> return []
2014-05-25 14:51:33 +05:30
NamesMsg { .. } -> do
2014-05-25 15:52:15 +05:30
forM_ nicks $ \n -> updateNickTrack state (User n "") "" msgTime
2014-06-01 06:48:24 +05:30
refresh nicks >> updateRefreshTime >> return []
2014-05-25 14:51:33 +05:30
IdleMsg { .. } -> do
NickTrackingState { .. } <- readIORef state
if addUTCTime refreshInterval lastRefreshOn < msgTime
2014-06-01 06:48:24 +05:30
then updateRefreshTime >> return [NamesCmd]
else return []
_ -> return []
2014-05-23 02:45:45 +05:30
where
2014-05-25 14:51:33 +05:30
updateRefreshTime = atomicModIORef state $ \ s -> s { lastRefreshOn = msgTime }
modifyOnlineNicks f = atomicModIORef state $ \s -> s { onlineNicks = f . onlineNicks $ s }
2014-06-01 02:11:20 +05:30
add = modifyOnlineNicks . flip ((. userNick) . flip insertSet)
remove = modifyOnlineNicks . flip ((. userNick) . flip deleteSet)
swap users = modifyOnlineNicks $
2014-06-01 02:11:20 +05:30
let (oNick, nNick) = both userNick users
2014-05-25 14:51:33 +05:30
in deleteSet oNick . insertSet nNick
2014-06-01 02:11:20 +05:30
refresh = modifyOnlineNicks . const . setFromList
2014-05-25 14:51:33 +05:30
2014-06-01 02:11:20 +05:30
commands = [ ("!nicks", handleNickCommand)
, ("!seen", handleSeenCommand)
, ("!forgetnicks", handleForgetNicksCommand)]
2014-05-23 04:56:26 +05:30
handleCommands = case find ((`isPrefixOf` msg msgDetails) . fst) commands of
2014-06-01 06:48:24 +05:30
Nothing -> return []
Just (_, handler) -> handler state message
2014-05-24 23:49:52 +05:30
2014-05-25 14:51:33 +05:30
updateNickTrack :: MonadMsgHandler m => IORef NickTrackingState -> User -> Text -> UTCTime -> m ()
2014-05-24 23:49:52 +05:30
updateNickTrack state user message msgTime = io $ do
2014-05-25 14:51:33 +05:30
NickTrackingState { .. } <- readIORef state
2014-05-24 23:49:52 +05:30
let nck = userNick user
mnt <- getByNick acid nck
2014-05-24 23:49:52 +05:30
(message', lastMessageOn', cn) <- case (message, mnt) of
("", Just (NickTrack { .. })) -> return (lastMessage, lastMessageOn, canonicalNick)
(_, Just (NickTrack { .. })) -> return (message, msgTime, canonicalNick)
2014-05-25 01:09:31 +05:30
_ -> newCanonicalNick >>= \cn -> return (message, msgTime, cn)
2014-05-24 23:49:52 +05:30
2014-06-01 02:11:20 +05:30
saveNickTrack acid $ NickTrack nck cn (LastSeenOn msgTime) lastMessageOn' message'
2014-05-24 23:49:52 +05:30
2014-06-01 02:11:20 +05:30
handleNickChange :: MonadMsgHandler m => IORef NickTrackingState -> User -> Nick -> UTCTime -> m ()
2014-05-24 23:49:52 +05:30
handleNickChange state user newNick msgTime = io $ do
2014-05-25 14:51:33 +05:30
NickTrackingState { .. } <- readIORef state
2014-05-24 23:49:52 +05:30
let prevNick = userNick user
mpnt <- getByNick acid prevNick
mnt <- getByNick acid newNick
2014-05-24 23:49:52 +05:30
mInfo <- case (mpnt, mnt) of
(Nothing, _) -> newCanonicalNick >>= \cn -> return $ Just ("", cn, msgTime)
(Just pnt, Nothing) ->
return $ Just (lastMessage pnt, canonicalNick pnt, lastMessageOn pnt)
(Just pnt, Just nt) | canonicalNick pnt == canonicalNick nt -> do
let nt' = maximumByEx (comparing lastMessageOn) [pnt, nt]
return $ Just (lastMessage nt', canonicalNick nt', lastMessageOn nt')
_ -> return Nothing
whenJust mInfo $ \(message, cn, lastMessageOn') ->
2014-06-01 02:11:20 +05:30
saveNickTrack acid $ NickTrack newNick cn (LastSeenOn msgTime) lastMessageOn' message
2014-05-24 23:49:52 +05:30
newCanonicalNick :: IO CanonicalNick
newCanonicalNick = map (CanonicalNick . pack . U.toString) U.nextRandom
2014-05-23 04:38:52 +05:30
2014-05-23 04:56:26 +05:30
withNickTracks :: MonadMsgHandler m
=> (Text -> [NickTrack] -> HashSet Nick -> IO Text) -> IORef NickTrackingState -> Message
2014-06-01 06:48:24 +05:30
-> m [Command]
withNickTracks f state message = io $ do
2014-05-25 14:51:33 +05:30
NickTrackingState { .. } <- readIORef state
let nick = clean . unwords . drop 1 . words . msg . msgDetails $ message
if nick == ""
2014-06-01 06:48:24 +05:30
then return []
else do
2014-06-01 02:11:20 +05:30
mcn <- liftM (map canonicalNick) . getByNick acid . Nick $ nick
2014-06-01 06:48:24 +05:30
map (singleton . ChannelMsgReply) $ case mcn of
Nothing -> return $ "Unknown nick: " ++ nick
Just cn -> io $ query acid (GetByCanonicalNickQ cn) >>= \nts -> f nick nts onlineNicks
2014-06-01 06:48:24 +05:30
handleNickCommand :: MonadMsgHandler m => IORef NickTrackingState -> Message -> m [Command]
2014-05-25 14:51:33 +05:30
handleNickCommand = withNickTracks $ \nck nickTracks _ -> do
2014-05-23 04:56:26 +05:30
let nicks = map ((\(Nick n) -> n) . nick) nickTracks
2014-05-23 04:38:52 +05:30
if length nicks == 1
then return $ nck ++ " has only one nick"
else return $ nck ++ "'s other nicks are: " ++ intercalate ", " (filter (/= nck) nicks)
2014-06-01 06:48:24 +05:30
handleSeenCommand :: MonadMsgHandler m => IORef NickTrackingState -> Message -> m [Command]
2014-05-25 14:51:33 +05:30
handleSeenCommand = withNickTracks $ \nck nickTracks onlineNicks -> do
2014-05-23 04:38:52 +05:30
let NickTrack { lastSeenOn = LastSeenOn lastSeenOn'
2014-05-23 04:56:26 +05:30
, nick = Nick lastSeenAs } = maximumByEx (comparing lastSeenOn) nickTracks
2014-05-23 04:38:52 +05:30
let NickTrack { lastMessageOn = lastMessageOn'
2014-05-23 04:56:26 +05:30
, lastMessage = lastMessage'
, nick = Nick lastMessageAs } = maximumByEx (comparing lastMessageOn) nickTracks
now <- io getCurrentTime
2014-05-25 15:52:15 +05:30
return $
(if any (`member` onlineNicks) . map nick $ nickTracks
then nck ++ " is online now"
else nck ++ " was last seen " ++ relativeTime lastSeenOn' now) ++
2014-05-25 14:51:33 +05:30
(if nck /= lastSeenAs then " as " ++ lastSeenAs else "") ++
2014-05-23 12:21:38 +05:30
(if clean lastMessage' == "" then "" else
" and " ++ relativeTime lastMessageOn' now ++ " " ++ nck ++
2014-05-25 14:51:33 +05:30
(if nck /= lastMessageAs then " as " ++ lastMessageAs else "") ++
2014-05-23 12:21:38 +05:30
" said: " ++ lastMessage')
2014-06-01 06:48:24 +05:30
handleForgetNicksCommand :: MonadMsgHandler m => IORef NickTrackingState -> Message -> m [Command]
handleForgetNicksCommand state Message { msgDetails = ~ChannelMsg { .. }, .. } = do
NickTrackingState { .. } <- readIORef state
let nick = userNick user
io $ do
Just nt <- getByNick acid nick
cn <- newCanonicalNick
saveNickTrack acid $ nt { canonicalNick = cn }
2014-06-01 06:48:24 +05:30
return [ChannelMsgReply $ "Forgot all alternate nicks of " ++ nickToText nick]
nickTrackerEvent :: MonadMsgHandler m => IORef NickTrackingState -> SomeEvent -> m EventResponse
nickTrackerEvent state event = case fromEvent event of
Just (NickTrackRequest nick reply, _) -> io $ do
NickTrackingState { .. } <- readIORef state
getByNick acid nick >>= putMVar reply >> return RespNothing
_ -> return RespNothing
2014-05-23 04:38:52 +05:30
2014-05-25 14:51:33 +05:30
stopNickTracker :: MonadMsgHandler m => IORef NickTrackingState -> m ()
2014-05-23 12:21:38 +05:30
stopNickTracker state = io $ do
2014-05-25 14:51:33 +05:30
NickTrackingState { .. } <- readIORef state
2014-05-23 02:45:45 +05:30
createArchive acid
createCheckpointAndClose acid
2014-06-01 23:14:19 +05:30
mkMsgHandler :: MsgHandlerMaker
mkMsgHandler = MsgHandlerMaker "nicktracker" go
2014-05-23 02:45:45 +05:30
where
2014-05-23 04:38:52 +05:30
helpMsgs = mapFromList [
2014-06-01 06:48:24 +05:30
("!nicks", "Shows alternate nicks of the user. !nicks <nick>"),
2014-06-01 02:11:20 +05:30
("!seen", "Lets you know when a user was last seen online and last spoke in the channel. !seen <user nick>"),
("!forgetnicks", "Forgets all your alternate nicks. !forgetnicks") ]
go BotConfig { .. } _ "nicktracker" = do
state <- io $ do
now <- getCurrentTime
refreshInterval <- map convert (CF.lookupDefault 60 config "nicktracker.refresh_interval" :: IO Int)
acid <- openLocalState emptyNickTracking
newIORef (NickTrackingState acid refreshInterval mempty now)
return . Just $ newMsgHandler { onMessage = nickTrackerMsg state
, onEvent = nickTrackerEvent state
, onStop = stopNickTracker state
, onHelp = return helpMsgs }
go _ _ _ = return Nothing