Added tracking of online nicks
This commit is contained in:
parent
7c5ee230e4
commit
aaab36d743
@ -1,5 +1,3 @@
|
|||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
module Network.IRC.Handlers.NickTracker (mkMsgHandler) where
|
module Network.IRC.Handlers.NickTracker (mkMsgHandler) where
|
||||||
|
|
||||||
import qualified Data.IxSet as IS
|
import qualified Data.Configurator as CF
|
||||||
import qualified Data.UUID as U
|
import qualified Data.IxSet as IS
|
||||||
import qualified Data.UUID.V4 as U
|
import qualified Data.UUID as U
|
||||||
|
import qualified Data.UUID.V4 as U
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude hiding (swap)
|
||||||
import Control.Concurrent.Lifted (Chan)
|
import Control.Concurrent.Lifted (Chan)
|
||||||
import Control.Monad.Reader (ask)
|
import Control.Monad.Reader (ask)
|
||||||
import Control.Monad.State (get, put)
|
import Control.Monad.State (get, put)
|
||||||
import Data.Acid (AcidState, Query, Update, makeAcidic, query, update,
|
import Data.Acid (AcidState, Query, Update, makeAcidic, query, update,
|
||||||
openLocalState, createArchive)
|
openLocalState, createArchive)
|
||||||
import Data.Acid.Local (createCheckpointAndClose)
|
import Data.Acid.Local (createCheckpointAndClose)
|
||||||
|
import Data.Convertible (convert)
|
||||||
import Data.IxSet (getOne, (@=))
|
import Data.IxSet (getOne, (@=))
|
||||||
|
import Data.Time (addUTCTime, NominalDiffTime)
|
||||||
|
|
||||||
import Network.IRC.Handlers.NickTracker.Types
|
import Network.IRC.Handlers.NickTracker.Types
|
||||||
import Network.IRC.Types hiding (Nick)
|
import Network.IRC.Types hiding (Nick)
|
||||||
@ -39,18 +40,42 @@ saveNickTrack nt = do
|
|||||||
|
|
||||||
$(makeAcidic ''NickTracking ['getByNick, 'getByCanonicalNick, 'saveNickTrack])
|
$(makeAcidic ''NickTracking ['getByNick, 'getByCanonicalNick, 'saveNickTrack])
|
||||||
|
|
||||||
nickTrackerMsg :: MonadMsgHandler m => IORef (AcidState NickTracking) -> Message -> m (Maybe Command)
|
data NickTrackingState = NickTrackingState { acid :: AcidState NickTracking
|
||||||
|
, refreshInterval :: NominalDiffTime
|
||||||
|
, onlineNicks :: HashSet Nick
|
||||||
|
, lastRefreshOn :: UTCTime }
|
||||||
|
|
||||||
|
modifyOnlineNicks :: (HashSet Nick -> HashSet Nick) -> NickTrackingState -> NickTrackingState
|
||||||
|
modifyOnlineNicks f state = state { onlineNicks = f . onlineNicks $ state }
|
||||||
|
|
||||||
|
nickTrackerMsg :: MonadMsgHandler m => IORef NickTrackingState -> Message -> m (Maybe Command)
|
||||||
nickTrackerMsg state Message { .. } = case msgDetails of
|
nickTrackerMsg state Message { .. } = case msgDetails of
|
||||||
ChannelMsg { .. } -> updateNickTrack state user msg msgTime >> handleCommands msg
|
ChannelMsg { .. } -> updateNickTrack state user msg msgTime >> handleCommands msg
|
||||||
ActionMsg { .. } -> updateNickTrack state user msg msgTime >> return Nothing
|
ActionMsg { .. } -> updateNickTrack state user msg msgTime >> return Nothing
|
||||||
JoinMsg { .. } -> updateNickTrack state user "" msgTime >> return Nothing
|
JoinMsg { .. } -> updateNickTrack state user "" msgTime >> add user >> return Nothing
|
||||||
PartMsg { .. } -> updateNickTrack state user msg msgTime >> return Nothing
|
PartMsg { .. } -> updateNickTrack state user msg msgTime >> remove user >> return Nothing
|
||||||
QuitMsg { .. } -> updateNickTrack state user msg msgTime >> return Nothing
|
QuitMsg { .. } -> updateNickTrack state user msg msgTime >> remove user >> return Nothing
|
||||||
NickMsg { .. } -> handleNickChange state user newNick msgTime >> return Nothing
|
NickMsg { .. } ->
|
||||||
NamesMsg { .. } ->
|
handleNickChange state user newNick msgTime >> swap (user, User newNick "") >> return Nothing
|
||||||
mapM_ (\n -> updateNickTrack state (User n "") "" msgTime) nicks >> return Nothing
|
NamesMsg { .. } -> do
|
||||||
|
mapM_ (\n -> updateNickTrack state (User n "") "" msgTime) nicks
|
||||||
|
refresh nicks >> updateRefreshTime >> return Nothing
|
||||||
|
IdleMsg { .. } -> do
|
||||||
|
NickTrackingState { .. } <- readIORef state
|
||||||
|
if addUTCTime refreshInterval lastRefreshOn < msgTime
|
||||||
|
then updateRefreshTime >> return (Just NamesCmd)
|
||||||
|
else return Nothing
|
||||||
_ -> return Nothing
|
_ -> return Nothing
|
||||||
where
|
where
|
||||||
|
updateRefreshTime = atomicModIORef state $ \ s -> s { lastRefreshOn = msgTime }
|
||||||
|
|
||||||
|
add = atomicModIORef state . modifyOnlineNicks . flip ((. (Nick . userNick)) . flip insertSet)
|
||||||
|
remove = atomicModIORef state . modifyOnlineNicks . flip ((. (Nick . userNick)) . flip deleteSet)
|
||||||
|
swap users = atomicModIORef state . modifyOnlineNicks $
|
||||||
|
let (oNick, nNick) = both (Nick . userNick) users
|
||||||
|
in deleteSet oNick . insertSet nNick
|
||||||
|
refresh = atomicModIORef state . modifyOnlineNicks . const . setFromList . map Nick
|
||||||
|
|
||||||
commands = [ ("!nick", handleNickCommand)
|
commands = [ ("!nick", handleNickCommand)
|
||||||
, ("!seen", handleSeenCommand) ]
|
, ("!seen", handleSeenCommand) ]
|
||||||
|
|
||||||
@ -58,9 +83,9 @@ nickTrackerMsg state Message { .. } = case msgDetails of
|
|||||||
Nothing -> return Nothing
|
Nothing -> return Nothing
|
||||||
Just (_, handler) -> handler state msg
|
Just (_, handler) -> handler state msg
|
||||||
|
|
||||||
updateNickTrack :: MonadMsgHandler m => IORef (AcidState NickTracking) -> User -> Text -> UTCTime -> m ()
|
updateNickTrack :: MonadMsgHandler m => IORef NickTrackingState -> User -> Text -> UTCTime -> m ()
|
||||||
updateNickTrack state user message msgTime = io $ do
|
updateNickTrack state user message msgTime = io $ do
|
||||||
acid <- readIORef state
|
NickTrackingState { .. } <- readIORef state
|
||||||
let nck = userNick user
|
let nck = userNick user
|
||||||
mnt <- query acid . GetByNick $ Nick nck
|
mnt <- query acid . GetByNick $ Nick nck
|
||||||
(message', lastMessageOn', cn) <- case (message, mnt) of
|
(message', lastMessageOn', cn) <- case (message, mnt) of
|
||||||
@ -71,9 +96,9 @@ updateNickTrack state user message msgTime = io $ do
|
|||||||
update acid . SaveNickTrack $
|
update acid . SaveNickTrack $
|
||||||
NickTrack (Nick nck) cn (LastSeenOn msgTime) lastMessageOn' message'
|
NickTrack (Nick nck) cn (LastSeenOn msgTime) lastMessageOn' message'
|
||||||
|
|
||||||
handleNickChange :: MonadMsgHandler m => IORef (AcidState NickTracking) -> User -> Text -> UTCTime -> m ()
|
handleNickChange :: MonadMsgHandler m => IORef NickTrackingState -> User -> Text -> UTCTime -> m ()
|
||||||
handleNickChange state user newNick msgTime = io $ do
|
handleNickChange state user newNick msgTime = io $ do
|
||||||
acid <- readIORef state
|
NickTrackingState { .. } <- readIORef state
|
||||||
let prevNick = userNick user
|
let prevNick = userNick user
|
||||||
mpnt <- query acid . GetByNick $ Nick prevNick
|
mpnt <- query acid . GetByNick $ Nick prevNick
|
||||||
mnt <- query acid . GetByNick $ Nick newNick
|
mnt <- query acid . GetByNick $ Nick newNick
|
||||||
@ -94,49 +119,56 @@ newCanonicalNick :: IO CanonicalNick
|
|||||||
newCanonicalNick = map (CanonicalNick . pack . U.toString) U.nextRandom
|
newCanonicalNick = map (CanonicalNick . pack . U.toString) U.nextRandom
|
||||||
|
|
||||||
withNickTracks :: MonadMsgHandler m
|
withNickTracks :: MonadMsgHandler m
|
||||||
=> (Text -> [NickTrack] -> IO Text) -> IORef (AcidState NickTracking) -> Text
|
=> (Text -> [NickTrack] -> HashSet Nick -> IO Text) -> IORef NickTrackingState -> Text
|
||||||
-> m (Maybe Command)
|
-> m (Maybe Command)
|
||||||
withNickTracks f state msg = io $ do
|
withNickTracks f state msg = io $ do
|
||||||
acid <- readIORef state
|
NickTrackingState { .. } <- readIORef state
|
||||||
let nick = clean . unwords . drop 1 . words $ msg
|
let nick = clean . unwords . drop 1 . words $ msg
|
||||||
mcn <- liftM (map canonicalNick) . query acid . GetByNick $ Nick nick
|
mcn <- liftM (map canonicalNick) . query acid . GetByNick $ Nick nick
|
||||||
map (Just . ChannelMsgReply) $ case mcn of
|
map (Just . ChannelMsgReply) $ case mcn of
|
||||||
Nothing -> return $ "Unknown nick: " ++ nick
|
Nothing -> return $ "Unknown nick: " ++ nick
|
||||||
Just cn -> io $ query acid (GetByCanonicalNick cn) >>= f nick
|
Just cn -> io $ query acid (GetByCanonicalNick cn) >>= \nts -> f nick nts onlineNicks
|
||||||
|
|
||||||
handleNickCommand :: MonadMsgHandler m => IORef(AcidState NickTracking) -> Text -> m (Maybe Command)
|
handleNickCommand :: MonadMsgHandler m => IORef NickTrackingState -> Text -> m (Maybe Command)
|
||||||
handleNickCommand = withNickTracks $ \nck nickTracks -> do
|
handleNickCommand = withNickTracks $ \nck nickTracks _ -> do
|
||||||
let nicks = map ((\(Nick n) -> n) . nick) nickTracks
|
let nicks = map ((\(Nick n) -> n) . nick) nickTracks
|
||||||
if length nicks == 1
|
if length nicks == 1
|
||||||
then return $ nck ++ " has only one nick"
|
then return $ nck ++ " has only one nick"
|
||||||
else return $ nck ++ "'s other nicks are: " ++ intercalate ", " (filter (/= nck) nicks)
|
else return $ nck ++ "'s other nicks are: " ++ intercalate ", " (filter (/= nck) nicks)
|
||||||
|
|
||||||
handleSeenCommand :: MonadMsgHandler m => IORef(AcidState NickTracking) -> Text -> m (Maybe Command)
|
handleSeenCommand :: MonadMsgHandler m => IORef NickTrackingState -> Text -> m (Maybe Command)
|
||||||
handleSeenCommand = withNickTracks $ \nick nickTracks -> do
|
handleSeenCommand = withNickTracks $ \nck nickTracks onlineNicks -> do
|
||||||
let NickTrack { lastSeenOn = LastSeenOn lastSeenOn'
|
let NickTrack { lastSeenOn = LastSeenOn lastSeenOn'
|
||||||
, nick = Nick lastSeenAs } = maximumByEx (comparing lastSeenOn) nickTracks
|
, nick = Nick lastSeenAs } = maximumByEx (comparing lastSeenOn) nickTracks
|
||||||
let NickTrack { lastMessageOn = lastMessageOn'
|
let NickTrack { lastMessageOn = lastMessageOn'
|
||||||
, lastMessage = lastMessage'
|
, lastMessage = lastMessage'
|
||||||
, nick = Nick lastMessageAs } = maximumByEx (comparing lastMessageOn) nickTracks
|
, nick = Nick lastMessageAs } = maximumByEx (comparing lastMessageOn) nickTracks
|
||||||
|
|
||||||
return $ nick ++ " was last seen on " ++ fmtTime lastSeenOn' ++
|
return $ (if any (`member` onlineNicks) . map nick $ nickTracks
|
||||||
(if nick /= lastSeenAs then " as " ++ lastSeenAs else "") ++
|
then nck ++ " is online now"
|
||||||
|
else nck ++ " was last seen on " ++ fmtTime lastSeenOn') ++
|
||||||
|
(if nck /= lastSeenAs then " as " ++ lastSeenAs else "") ++
|
||||||
(if clean lastMessage' == "" then "" else
|
(if clean lastMessage' == "" then "" else
|
||||||
" and at " ++ fmtTime lastMessageOn' ++ " " ++ nick ++
|
" and at " ++ fmtTime lastMessageOn' ++ " " ++ nck ++
|
||||||
(if nick /= lastMessageAs then " as " ++ lastMessageAs else "") ++
|
(if nck /= lastMessageAs then " as " ++ lastMessageAs else "") ++
|
||||||
" said: " ++ lastMessage')
|
" said: " ++ lastMessage')
|
||||||
where
|
where
|
||||||
fmtTime = pack . formatTime defaultTimeLocale "%F %T"
|
fmtTime = pack . formatTime defaultTimeLocale "%F %T"
|
||||||
|
|
||||||
stopNickTracker :: MonadMsgHandler m => IORef (AcidState NickTracking) -> m ()
|
stopNickTracker :: MonadMsgHandler m => IORef NickTrackingState -> m ()
|
||||||
stopNickTracker state = io $ do
|
stopNickTracker state = io $ do
|
||||||
acid <- readIORef state
|
NickTrackingState { .. } <- readIORef state
|
||||||
createArchive acid
|
createArchive acid
|
||||||
createCheckpointAndClose acid
|
createCheckpointAndClose acid
|
||||||
|
|
||||||
mkMsgHandler :: BotConfig -> Chan SomeEvent -> MsgHandlerName -> IO (Maybe MsgHandler)
|
mkMsgHandler :: BotConfig -> Chan SomeEvent -> MsgHandlerName -> IO (Maybe MsgHandler)
|
||||||
mkMsgHandler BotConfig { .. } _ "nicktracker" = do
|
mkMsgHandler BotConfig { .. } _ "nicktracker" = do
|
||||||
state <- io $ openLocalState emptyNickTracking >>= newIORef
|
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
|
return . Just $ newMsgHandler { onMessage = nickTrackerMsg state
|
||||||
, onStop = stopNickTracker state
|
, onStop = stopNickTracker state
|
||||||
, onHelp = return helpMsgs }
|
, onHelp = return helpMsgs }
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
module Network.IRC.Handlers.NickTracker.Types where
|
module Network.IRC.Handlers.NickTracker.Types where
|
||||||
@ -8,7 +7,7 @@ import Data.Data (Data)
|
|||||||
import Data.IxSet (IxSet, Indexable (..), ixSet, ixFun)
|
import Data.IxSet (IxSet, Indexable (..), ixSet, ixFun)
|
||||||
import Data.SafeCopy (base, deriveSafeCopy)
|
import Data.SafeCopy (base, deriveSafeCopy)
|
||||||
|
|
||||||
newtype Nick = Nick Text deriving (Eq, Ord, Show, Data, Typeable)
|
newtype Nick = Nick Text deriving (Eq, Ord, Show, Data, Typeable, Hashable)
|
||||||
newtype CanonicalNick = CanonicalNick Text deriving (Eq, Ord, Show, Data, Typeable)
|
newtype CanonicalNick = CanonicalNick Text deriving (Eq, Ord, Show, Data, Typeable)
|
||||||
newtype LastSeenOn = LastSeenOn UTCTime deriving (Eq, Ord, Show, Data, Typeable)
|
newtype LastSeenOn = LastSeenOn UTCTime deriving (Eq, Ord, Show, Data, Typeable)
|
||||||
|
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
module Network.IRC.Handlers.SongSearch (mkMsgHandler) where
|
module Network.IRC.Handlers.SongSearch (mkMsgHandler) where
|
||||||
|
@ -9,8 +9,7 @@ import Data.Text (strip)
|
|||||||
|
|
||||||
import Network.IRC.Types
|
import Network.IRC.Types
|
||||||
|
|
||||||
data MessageParseType = Names
|
data MessageParseType = Names
|
||||||
| Whois
|
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
data MessagePart = MessagePart { msgParserType :: MessageParseType
|
data MessagePart = MessagePart { msgParserType :: MessageParseType
|
||||||
@ -28,38 +27,38 @@ type MessageParser = BotConfig -> UTCTime -> Text -> [MessagePart] -> MessagePar
|
|||||||
|
|
||||||
parseLine :: BotConfig -> UTCTime -> Text -> [MessagePart] -> (Maybe Message, [MessagePart])
|
parseLine :: BotConfig -> UTCTime -> Text -> [MessagePart] -> (Maybe Message, [MessagePart])
|
||||||
parseLine botConfig time line msgParts =
|
parseLine botConfig time line msgParts =
|
||||||
case lineParser botConfig time line msgParts of
|
fromMaybe (Nothing, msgParts) . flip (`foldl'` Nothing) parsers $ \parseResult parser ->
|
||||||
Done message@(Message { msgDetails = OtherMsg { .. }, .. }) _ ->
|
case parseResult of
|
||||||
fromMaybe (Just message, msgParts) . flip (`foldl'` Nothing) parsers $ \parseResult parser ->
|
Just _ -> parseResult
|
||||||
case parseResult of
|
Nothing -> case parser botConfig time line msgParts of
|
||||||
Just _ -> parseResult
|
Reject -> Nothing
|
||||||
Nothing -> case parser botConfig time line msgParts of
|
Partial msgParts' -> Just (Nothing, msgParts')
|
||||||
Reject -> Nothing
|
Done message' msgParts' -> Just (Just message', msgParts')
|
||||||
Partial msgParts' -> Just (Nothing, msgParts')
|
|
||||||
Done message' msgParts' -> Just (Just message', msgParts')
|
|
||||||
Done message _ -> (Just message, msgParts)
|
|
||||||
_ -> error "This should never happen"
|
|
||||||
where
|
where
|
||||||
parsers = [namesParser]
|
parsers = [pingParser, namesParser, lineParser]
|
||||||
|
|
||||||
|
pingParser :: MessageParser
|
||||||
|
pingParser _ time line msgParts
|
||||||
|
| "PING :" `isPrefixOf` line = Done (Message time line . PingMsg . drop 6 $ line) msgParts
|
||||||
|
| otherwise = Reject
|
||||||
|
|
||||||
lineParser :: MessageParser
|
lineParser :: MessageParser
|
||||||
lineParser BotConfig { .. } time line msgParts
|
lineParser BotConfig { .. } time line msgParts = flip Done msgParts . Message time line $
|
||||||
| "PING :" `isPrefixOf` line = flip Done msgParts $ Message time line $ PingMsg (drop 6 line)
|
case command of
|
||||||
| otherwise = flip Done msgParts $ case command of
|
"PONG" -> PongMsg message
|
||||||
"PONG" -> Message time line $ PongMsg message
|
"JOIN" -> JoinMsg user
|
||||||
"JOIN" -> Message time line $ JoinMsg user
|
"QUIT" -> QuitMsg user quitMessage
|
||||||
"QUIT" -> Message time line $ QuitMsg user quitMessage
|
"PART" -> PartMsg user message
|
||||||
"PART" -> Message time line $ PartMsg user message
|
"KICK" -> KickMsg user kicked kickReason
|
||||||
"KICK" -> Message time line $ KickMsg user kicked kickReason
|
"MODE" -> if source == botNick
|
||||||
"MODE" -> if source == botNick
|
then ModeMsg Self target message []
|
||||||
then Message time line $ ModeMsg Self target message []
|
else ModeMsg user target mode modeArgs
|
||||||
else Message time line $ ModeMsg user target mode modeArgs
|
"NICK" -> NickMsg user (drop 1 target)
|
||||||
"NICK" -> Message time line $ NickMsg user (drop 1 target)
|
"433" -> NickInUseMsg
|
||||||
"433" -> Message time line NickInUseMsg
|
"PRIVMSG" | target /= channel -> PrivMsg user message
|
||||||
"PRIVMSG" | target /= channel -> Message time line $ PrivMsg user message
|
| isActionMsg -> ActionMsg user (initDef . drop 8 $ message)
|
||||||
| isActionMsg -> Message time line $ ActionMsg user (initDef . drop 8 $ message)
|
| otherwise -> ChannelMsg user message
|
||||||
| otherwise -> Message time line $ ChannelMsg user message
|
_ -> OtherMsg source command target message
|
||||||
_ -> Message time line $ OtherMsg source command target message
|
|
||||||
where
|
where
|
||||||
splits = words line
|
splits = words line
|
||||||
command = splits !! 1
|
command = splits !! 1
|
||||||
@ -92,7 +91,6 @@ namesParser BotConfig { .. } time line msgParts = case command of
|
|||||||
stripNickPrefix = pack . dropWhile (`elem` ("~&@%+" :: String)) . unpack
|
stripNickPrefix = pack . dropWhile (`elem` ("~&@%+" :: String)) . unpack
|
||||||
namesNicks line' = map stripNickPrefix . words . drop 1 . unwords . drop 5 . words $ line'
|
namesNicks line' = map stripNickPrefix . words . drop 1 . unwords . drop 5 . words $ line'
|
||||||
|
|
||||||
|
|
||||||
lineFromCommand :: BotConfig -> Command -> Maybe Text
|
lineFromCommand :: BotConfig -> Command -> Maybe Text
|
||||||
lineFromCommand BotConfig { .. } command = case command of
|
lineFromCommand BotConfig { .. } command = case command of
|
||||||
PongCmd { .. } -> Just $ "PONG :" ++ rmsg
|
PongCmd { .. } -> Just $ "PONG :" ++ rmsg
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
|
||||||
{-# LANGUAGE ExistentialQuantification #-}
|
{-# LANGUAGE ExistentialQuantification #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
|
||||||
@ -31,6 +29,7 @@ module Network.IRC.Types
|
|||||||
where
|
where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
import Control.Monad.Base (MonadBase)
|
||||||
import Control.Monad.Reader (ReaderT, MonadReader, runReaderT)
|
import Control.Monad.Reader (ReaderT, MonadReader, runReaderT)
|
||||||
import Control.Monad.State (StateT, MonadState, execStateT)
|
import Control.Monad.State (StateT, MonadState, execStateT)
|
||||||
import Data.Configurator.Types (Config)
|
import Data.Configurator.Types (Config)
|
||||||
@ -153,9 +152,10 @@ newtype MsgHandlerT a = MsgHandlerT { _runMsgHandler :: ReaderT BotConfig IO a }
|
|||||||
, Applicative
|
, Applicative
|
||||||
, Monad
|
, Monad
|
||||||
, MonadIO
|
, MonadIO
|
||||||
|
, MonadBase IO
|
||||||
, MonadReader BotConfig )
|
, MonadReader BotConfig )
|
||||||
|
|
||||||
class (MonadIO m, Applicative m, MonadReader BotConfig m) => MonadMsgHandler m where
|
class (MonadIO m, Applicative m, MonadReader BotConfig m, MonadBase IO m) => MonadMsgHandler m where
|
||||||
msgHandler :: MsgHandlerT a -> m a
|
msgHandler :: MsgHandlerT a -> m a
|
||||||
|
|
||||||
instance MonadMsgHandler MsgHandlerT where
|
instance MonadMsgHandler MsgHandlerT where
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Network.IRC.Util where
|
module Network.IRC.Util where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
import Control.Arrow (Arrow)
|
||||||
import Control.Concurrent.Lifted (Chan)
|
import Control.Concurrent.Lifted (Chan)
|
||||||
|
import Control.Monad.Base (MonadBase)
|
||||||
import Data.Text (strip)
|
import Data.Text (strip)
|
||||||
|
|
||||||
oneSec :: Int
|
oneSec :: Int
|
||||||
@ -31,3 +35,10 @@ clean = toLower . strip
|
|||||||
|
|
||||||
io :: MonadIO m => IO a -> m a
|
io :: MonadIO m => IO a -> m a
|
||||||
io = liftIO
|
io = liftIO
|
||||||
|
|
||||||
|
both :: Arrow cat => cat b d -> cat (b, b) (d, d)
|
||||||
|
both f = first f . second f
|
||||||
|
|
||||||
|
atomicModIORef :: MonadBase IO f => IORef t -> (t -> t) -> f ()
|
||||||
|
atomicModIORef ref f = void . atomicModifyIORef' ref $ \v -> (f v, v)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ server = "irc.freenode.net"
|
|||||||
port = 6667
|
port = 6667
|
||||||
channel = "#testtesttest"
|
channel = "#testtesttest"
|
||||||
nick = "haskman"
|
nick = "haskman"
|
||||||
|
timeout = 130
|
||||||
msghandlers = ["greeter", "welcomer", "songsearch", "auth", "nicktracker"]
|
msghandlers = ["greeter", "welcomer", "songsearch", "auth", "nicktracker"]
|
||||||
|
|
||||||
songsearch {
|
songsearch {
|
||||||
@ -11,3 +12,7 @@ songsearch {
|
|||||||
messagelogger {
|
messagelogger {
|
||||||
logdir = "./logs/"
|
logdir = "./logs/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nicktracker {
|
||||||
|
refresh_interval = 60
|
||||||
|
}
|
||||||
|
102
hask-irc.cabal
102
hask-irc.cabal
@ -50,31 +50,34 @@ cabal-version: >=1.10
|
|||||||
|
|
||||||
library
|
library
|
||||||
default-extensions: NoImplicitPrelude, OverloadedStrings, RecordWildCards, ScopedTypeVariables,
|
default-extensions: NoImplicitPrelude, OverloadedStrings, RecordWildCards, ScopedTypeVariables,
|
||||||
BangPatterns, TupleSections, NamedFieldPuns
|
BangPatterns, TupleSections, NamedFieldPuns, GeneralizedNewtypeDeriving,
|
||||||
|
DeriveDataTypeable
|
||||||
|
|
||||||
build-depends: base >=4.5 && <4.8,
|
build-depends: base >=4.5 && <4.8,
|
||||||
text >=0.11 && <0.12,
|
text >=0.11 && <0.12,
|
||||||
mtl >=2.1 && <2.2,
|
mtl >=2.1 && <2.2,
|
||||||
network >=2.3 && <2.5,
|
network >=2.3 && <2.5,
|
||||||
configurator >=0.2 && <0.3,
|
configurator >=0.2 && <0.3,
|
||||||
time >=1.4 && <1.5,
|
time >=1.4 && <1.5,
|
||||||
curl-aeson >=0.0.3 && <0.1,
|
curl-aeson >=0.0.3 && <0.1,
|
||||||
aeson >=0.6.0.0 && <0.7,
|
aeson >=0.6.0.0 && <0.7,
|
||||||
HTTP >=4000 && <5000,
|
HTTP >=4000 && <5000,
|
||||||
transformers >=0.3 && <0.4,
|
transformers >=0.3 && <0.4,
|
||||||
classy-prelude >=0.9 && <1.0,
|
classy-prelude >=0.9 && <1.0,
|
||||||
text-format >=0.3 && <0.4,
|
text-format >=0.3 && <0.4,
|
||||||
filepath >=1.3 && <1.4,
|
filepath >=1.3 && <1.4,
|
||||||
directory >=1.2 && <1.3,
|
directory >=1.2 && <1.3,
|
||||||
lifted-base >=0.2 && <0.3,
|
lifted-base >=0.2 && <0.3,
|
||||||
unix >=2.7 && <2.8,
|
unix >=2.7 && <2.8,
|
||||||
convertible >=1.1 && <1.2,
|
convertible >=1.1 && <1.2,
|
||||||
hslogger >=1.2 && <1.3,
|
hslogger >=1.2 && <1.3,
|
||||||
hslogger-template >=2.0 && <2.1,
|
hslogger-template >=2.0 && <2.1,
|
||||||
ixset >=1.0 && <1.1,
|
ixset >=1.0 && <1.1,
|
||||||
acid-state >=0.12 && <0.13,
|
acid-state >=0.12 && <0.13,
|
||||||
safecopy >=0.8 && <0.9,
|
safecopy >=0.8 && <0.9,
|
||||||
uuid >=1.3 && <1.4
|
uuid >=1.3 && <1.4,
|
||||||
|
transformers-base >=0.4 && <0.5,
|
||||||
|
unordered-containers >=0.2 && <0.3
|
||||||
|
|
||||||
exposed-modules: Network.IRC.Types, Network.IRC.Protocol,
|
exposed-modules: Network.IRC.Types, Network.IRC.Protocol,
|
||||||
Network.IRC.Handlers, Network.IRC.Client
|
Network.IRC.Handlers, Network.IRC.Client
|
||||||
@ -93,32 +96,35 @@ executable hask-irc
|
|||||||
|
|
||||||
-- LANGUAGE extensions used by modules in this package.
|
-- LANGUAGE extensions used by modules in this package.
|
||||||
default-extensions: NoImplicitPrelude, OverloadedStrings, RecordWildCards, ScopedTypeVariables,
|
default-extensions: NoImplicitPrelude, OverloadedStrings, RecordWildCards, ScopedTypeVariables,
|
||||||
BangPatterns, TupleSections, NamedFieldPuns
|
BangPatterns, TupleSections, NamedFieldPuns, GeneralizedNewtypeDeriving,
|
||||||
|
DeriveDataTypeable
|
||||||
|
|
||||||
-- Other library packages from which modules are imported.
|
-- Other library packages from which modules are imported.
|
||||||
build-depends: base >=4.5 && <4.8,
|
build-depends: base >=4.5 && <4.8,
|
||||||
text >=0.11 && <0.12,
|
text >=0.11 && <0.12,
|
||||||
mtl >=2.1 && <2.2,
|
mtl >=2.1 && <2.2,
|
||||||
network >=2.3 && <2.5,
|
network >=2.3 && <2.5,
|
||||||
configurator >=0.2 && <0.3,
|
configurator >=0.2 && <0.3,
|
||||||
time >=1.4 && <1.5,
|
time >=1.4 && <1.5,
|
||||||
curl-aeson >=0.0.3 && <0.1,
|
curl-aeson >=0.0.3 && <0.1,
|
||||||
aeson >=0.6.0.0 && <0.7,
|
aeson >=0.6.0.0 && <0.7,
|
||||||
HTTP >=4000 && <5000,
|
HTTP >=4000 && <5000,
|
||||||
transformers >=0.3 && <0.4,
|
transformers >=0.3 && <0.4,
|
||||||
classy-prelude >=0.9 && <1.0,
|
classy-prelude >=0.9 && <1.0,
|
||||||
text-format >=0.3 && <0.4,
|
text-format >=0.3 && <0.4,
|
||||||
filepath >=1.3 && <1.4,
|
filepath >=1.3 && <1.4,
|
||||||
directory >=1.2 && <1.3,
|
directory >=1.2 && <1.3,
|
||||||
lifted-base >=0.2 && <0.3,
|
lifted-base >=0.2 && <0.3,
|
||||||
unix >=2.7 && <2.8,
|
unix >=2.7 && <2.8,
|
||||||
convertible >=1.1 && <1.2,
|
convertible >=1.1 && <1.2,
|
||||||
hslogger >=1.2 && <1.3,
|
hslogger >=1.2 && <1.3,
|
||||||
hslogger-template >=2.0 && <2.1,
|
hslogger-template >=2.0 && <2.1,
|
||||||
ixset >=1.0 && <1.1,
|
ixset >=1.0 && <1.1,
|
||||||
acid-state >=0.12 && <0.13,
|
acid-state >=0.12 && <0.13,
|
||||||
safecopy >=0.8 && <0.9,
|
safecopy >=0.8 && <0.9,
|
||||||
uuid >=1.3 && <1.4
|
uuid >=1.3 && <1.4,
|
||||||
|
transformers-base >=0.4 && <0.5,
|
||||||
|
unordered-containers >=0.2 && <0.3
|
||||||
|
|
||||||
-- Directories containing source files.
|
-- Directories containing source files.
|
||||||
-- hs-source-dirs:
|
-- hs-source-dirs:
|
||||||
|
Loading…
Reference in New Issue
Block a user