Track bot's current SessionView in the Bot monad
This commit is contained in:
parent
2fc6bf98a3
commit
fbf1402e24
3 changed files with 68 additions and 13 deletions
|
|
@ -80,6 +80,7 @@ module EuphApi.Bot (
|
||||||
, getConnection
|
, getConnection
|
||||||
, getConnectionInfo
|
, getConnectionInfo
|
||||||
, getConnectTime
|
, getConnectTime
|
||||||
|
, getOwnView
|
||||||
-- * Bot commands
|
-- * Bot commands
|
||||||
, stop
|
, stop
|
||||||
, send
|
, send
|
||||||
|
|
@ -89,9 +90,12 @@ module EuphApi.Bot (
|
||||||
, messageLog
|
, messageLog
|
||||||
, messageLogAfter
|
, messageLogAfter
|
||||||
, who
|
, who
|
||||||
|
-- * Exceptions
|
||||||
|
, BotException
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
|
import Control.Exception
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
|
|
||||||
|
|
@ -139,6 +143,7 @@ data BotState b c = BotState
|
||||||
, bConnection :: E.Connection
|
, bConnection :: E.Connection
|
||||||
, bConnectionInfo :: c -- connection specific, user-defined info type
|
, bConnectionInfo :: c -- connection specific, user-defined info type
|
||||||
, bConnectTime :: UTCTime
|
, bConnectTime :: UTCTime
|
||||||
|
, bOwnView :: TVar (Maybe E.SessionView)
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | The monad that bots are written in.
|
-- | The monad that bots are written in.
|
||||||
|
|
@ -214,6 +219,17 @@ getConnection = asks bConnection
|
||||||
getConnectionInfo :: Bot b c c
|
getConnectionInfo :: Bot b c c
|
||||||
getConnectionInfo = asks bConnectionInfo
|
getConnectionInfo = asks bConnectionInfo
|
||||||
|
|
||||||
|
-- | Returns the 'E.SessionView' of the current connection.
|
||||||
|
--
|
||||||
|
-- Might throw a 'NoOwnViewYet' exception.
|
||||||
|
getOwnView :: Bot b c E.SessionView
|
||||||
|
getOwnView = do
|
||||||
|
var <- asks bOwnView
|
||||||
|
mView <- liftIO $ atomically $ readTVar var
|
||||||
|
case mView of
|
||||||
|
Just view -> return view
|
||||||
|
Nothing -> liftIO $ throwIO NoOwnViewYet
|
||||||
|
|
||||||
-- | A default reconnect policy that waits an exponentially increasing amount
|
-- | A default reconnect policy that waits an exponentially increasing amount
|
||||||
-- of seconds between retries.
|
-- of seconds between retries.
|
||||||
defaultReconnectPolicy :: Integer -> Maybe Int
|
defaultReconnectPolicy :: Integer -> Maybe Int
|
||||||
|
|
@ -231,10 +247,11 @@ runBot BotConfig{..} = do
|
||||||
bPassword <- atomically $ newTVar $ T.pack <$> botPassword
|
bPassword <- atomically $ newTVar $ T.pack <$> botPassword
|
||||||
bNick <- atomically $ newTVar $ T.pack botNick
|
bNick <- atomically $ newTVar $ T.pack botNick
|
||||||
bStopping <- atomically $ newTVar False
|
bStopping <- atomically $ newTVar False
|
||||||
|
bOwnView <- atomically $ newTVar Nothing
|
||||||
noticeM $ "Connecting to &" ++ botRoom ++ " on " ++ show botAddress ++ "."
|
noticeM $ "Connecting to &" ++ botRoom ++ " on " ++ show botAddress ++ "."
|
||||||
|
bConnectionInfo <- botNewConnectionInfo
|
||||||
bStartTime <- getCurrentTime
|
bStartTime <- getCurrentTime
|
||||||
bConnection <- E.startEuphConnection botAddress botRoom
|
bConnection <- E.startEuphConnection botAddress botRoom
|
||||||
bConnectionInfo <- botNewConnectionInfo
|
|
||||||
let bHandler = botHandler
|
let bHandler = botHandler
|
||||||
bBotInfo = botInfo
|
bBotInfo = botInfo
|
||||||
bNewConnectionInfo = botNewConnectionInfo
|
bNewConnectionInfo = botNewConnectionInfo
|
||||||
|
|
@ -250,7 +267,7 @@ reconnect retries = do
|
||||||
if stopping
|
if stopping
|
||||||
then return ()
|
then return ()
|
||||||
else
|
else
|
||||||
case (bReconnectPolicy state $ retries) of
|
case bReconnectPolicy state retries of
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
Just delay -> do
|
Just delay -> do
|
||||||
liftIO $ infoM $ "Attempting reconnect in " ++ show (delay `div` 1000000)
|
liftIO $ infoM $ "Attempting reconnect in " ++ show (delay `div` 1000000)
|
||||||
|
|
@ -258,11 +275,16 @@ reconnect retries = do
|
||||||
liftIO $ threadDelay delay
|
liftIO $ threadDelay delay
|
||||||
address <- liftIO $ atomically $ readTVar $ bAddress state
|
address <- liftIO $ atomically $ readTVar $ bAddress state
|
||||||
room <- liftIO $ atomically $ readTVar $ bRoom state
|
room <- liftIO $ atomically $ readTVar $ bRoom state
|
||||||
now <- liftIO $ getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
con <- liftIO $ E.startEuphConnection address room
|
ownView <- liftIO $ atomically $ newTVar Nothing
|
||||||
liftIO $ infoM $ "Reconnecting to &" ++ room ++ " on " ++ show address ++ "."
|
liftIO $ infoM $ "Reconnecting to &" ++ room ++ " on " ++ show address ++ "."
|
||||||
conInfo <- liftIO $ bNewConnectionInfo state
|
conInfo <- liftIO $ bNewConnectionInfo state
|
||||||
let newState = state{bConnection=con, bConnectionInfo=conInfo, bConnectTime=now}
|
con <- liftIO $ E.startEuphConnection address room
|
||||||
|
let newState = state{ bConnection=con
|
||||||
|
, bConnectionInfo=conInfo
|
||||||
|
, bConnectTime=now
|
||||||
|
, bOwnView=ownView
|
||||||
|
}
|
||||||
local (const newState) (eventLoop retries)
|
local (const newState) (eventLoop retries)
|
||||||
-- lift $ runReaderT (eventLoop retries) newState
|
-- lift $ runReaderT (eventLoop retries) newState
|
||||||
|
|
||||||
|
|
@ -279,6 +301,7 @@ eventLoop retries = do
|
||||||
E.EuphEvent e -> do
|
E.EuphEvent e -> do
|
||||||
handlePingStuff e
|
handlePingStuff e
|
||||||
handlePasswordStuff e
|
handlePasswordStuff e
|
||||||
|
handleOwnViewStuff e
|
||||||
handleNickStuff e
|
handleNickStuff e
|
||||||
eventLoop retries
|
eventLoop retries
|
||||||
|
|
||||||
|
|
@ -291,7 +314,7 @@ handlePingStuff _ = return ()
|
||||||
handleNickStuff :: E.Event -> Bot b c ()
|
handleNickStuff :: E.Event -> Bot b c ()
|
||||||
handleNickStuff (E.SnapshotEvent _ _ _ maybeNick) = do
|
handleNickStuff (E.SnapshotEvent _ _ _ maybeNick) = do
|
||||||
myNickVar <- asks bNick
|
myNickVar <- asks bNick
|
||||||
myNick <- liftIO $ atomically $ readTVar $ myNickVar
|
myNick <- liftIO $ atomically $ readTVar myNickVar
|
||||||
con <- getConnection
|
con <- getConnection
|
||||||
case maybeNick of
|
case maybeNick of
|
||||||
Nothing -> fork $ liftIO $ E.nick con myNick
|
Nothing -> fork $ liftIO $ E.nick con myNick
|
||||||
|
|
@ -305,7 +328,7 @@ handlePasswordStuff :: E.Event -> Bot b c ()
|
||||||
handlePasswordStuff (E.BounceEvent _ (Just options))
|
handlePasswordStuff (E.BounceEvent _ (Just options))
|
||||||
| "passcode" `elem` options = do
|
| "passcode" `elem` options = do
|
||||||
myPasswordVar <- asks bPassword
|
myPasswordVar <- asks bPassword
|
||||||
myPassword <- liftIO $ atomically $ readTVar $ myPasswordVar
|
myPassword <- liftIO $ atomically $ readTVar myPasswordVar
|
||||||
con <- getConnection
|
con <- getConnection
|
||||||
case myPassword of
|
case myPassword of
|
||||||
Nothing -> fork $ liftIO $ E.disconnect con -- TODO: Do something here
|
Nothing -> fork $ liftIO $ E.disconnect con -- TODO: Do something here
|
||||||
|
|
@ -313,6 +336,22 @@ handlePasswordStuff (E.BounceEvent _ (Just options))
|
||||||
| otherwise = return () -- TODO: And also here
|
| otherwise = return () -- TODO: And also here
|
||||||
handlePasswordStuff _ = return ()
|
handlePasswordStuff _ = return ()
|
||||||
|
|
||||||
|
handleOwnViewStuff :: E.Event -> Bot b c ()
|
||||||
|
handleOwnViewStuff (E.HelloEvent view _ _) = do
|
||||||
|
var <- asks bOwnView
|
||||||
|
liftIO $ atomically $ writeTVar var (Just view)
|
||||||
|
handleOwnViewStuff (E.SnapshotEvent _ _ _ (Just curNick)) = do
|
||||||
|
var <- asks bOwnView
|
||||||
|
liftIO $ atomically $ changeOwnNick var curNick
|
||||||
|
handleOwnViewStuff _ = return ()
|
||||||
|
|
||||||
|
changeOwnNick :: TVar (Maybe E.SessionView) -> T.Text -> STM ()
|
||||||
|
changeOwnNick var newNick = do
|
||||||
|
mView <- readTVar var
|
||||||
|
case mView of
|
||||||
|
Just view -> writeTVar var (Just view{E.sessName=newNick})
|
||||||
|
Nothing -> return ()
|
||||||
|
|
||||||
{-
|
{-
|
||||||
- Commands
|
- Commands
|
||||||
-}
|
-}
|
||||||
|
|
@ -342,10 +381,13 @@ reply parentID content = do
|
||||||
nick :: T.Text -> Bot b c (T.Text, T.Text)
|
nick :: T.Text -> Bot b c (T.Text, T.Text)
|
||||||
nick newNick = do
|
nick newNick = do
|
||||||
myNick <- asks bNick
|
myNick <- asks bNick
|
||||||
con <- asks bConnection
|
var <- asks bOwnView
|
||||||
|
con <- asks bConnection
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
atomically $ writeTVar myNick newNick
|
atomically $ writeTVar myNick newNick
|
||||||
E.nick con newNick
|
r@(_, to) <- E.nick con newNick
|
||||||
|
atomically $ changeOwnNick var to
|
||||||
|
return r
|
||||||
|
|
||||||
-- | Request an untruncated message.
|
-- | Request an untruncated message.
|
||||||
getMessage :: E.Snowflake -> Bot b c E.Message
|
getMessage :: E.Snowflake -> Bot b c E.Message
|
||||||
|
|
@ -370,3 +412,17 @@ who :: Bot b c [E.SessionView]
|
||||||
who = do
|
who = do
|
||||||
con <- asks bConnection
|
con <- asks bConnection
|
||||||
liftIO $ E.who con
|
liftIO $ E.who con
|
||||||
|
|
||||||
|
{-
|
||||||
|
- Exceptions
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Exceptions that the bot throws.
|
||||||
|
data BotException = NoOwnViewYet
|
||||||
|
-- ^ The bot has not received a SessionView for its current connection
|
||||||
|
-- from the server yet.
|
||||||
|
|
||||||
|
instance Show BotException where
|
||||||
|
show NoOwnViewYet = "Bot hasn't received a SessionView of itself yet."
|
||||||
|
|
||||||
|
instance Exception BotException
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ sendPacket euphCon packetType packetData = do
|
||||||
atomically $ writeSend euphCon packet
|
atomically $ writeSend euphCon packet
|
||||||
result <- readMVar var
|
result <- readMVar var
|
||||||
case result of
|
case result of
|
||||||
Left f -> throw f
|
Left f -> throwIO f
|
||||||
Right r -> return r
|
Right r -> return r
|
||||||
|
|
||||||
sendPacketNoReply :: (ToJSON p) => Connection -> T.Text -> p -> IO ()
|
sendPacketNoReply :: (ToJSON p) => Connection -> T.Text -> p -> IO ()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ module EuphApi.Utils (
|
||||||
|
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import Data.Maybe
|
|
||||||
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Text.Megaparsec as P
|
import qualified Text.Megaparsec as P
|
||||||
|
|
@ -35,7 +34,7 @@ import qualified EuphApi.Types as E
|
||||||
-- This removes spaces and some extra characters, while trying to stay close to
|
-- This removes spaces and some extra characters, while trying to stay close to
|
||||||
-- the original nick.
|
-- the original nick.
|
||||||
mention :: T.Text -> T.Text
|
mention :: T.Text -> T.Text
|
||||||
mention = T.filter (\c -> (not $ isSpace c) && (not $ c `elem` ".!?;&<'\""))
|
mention = T.filter (\c -> not (isSpace c) && notElem c ".!?;&<'\"")
|
||||||
|
|
||||||
-- | Same as 'atMention', but prepends an `@` character.
|
-- | Same as 'atMention', but prepends an `@` character.
|
||||||
atMention :: T.Text -> T.Text
|
atMention :: T.Text -> T.Text
|
||||||
|
|
@ -70,7 +69,7 @@ runCommandsFromMessage cs = runCommands cs . E.msgContent
|
||||||
|
|
||||||
-- | Creates a 'Command' from a parser and a bot action.
|
-- | Creates a 'Command' from a parser and a bot action.
|
||||||
commandFromParser :: (Ord e) => P.Parsec e T.Text a -> (a -> B.Bot b c ()) -> Command b c
|
commandFromParser :: (Ord e) => P.Parsec e T.Text a -> (a -> B.Bot b c ()) -> Command b c
|
||||||
commandFromParser p f t = fromMaybe (return ()) $ f <$> P.parseMaybe p t
|
commandFromParser p f t = maybe (return ()) f $ P.parseMaybe p t
|
||||||
|
|
||||||
command :: T.Text -> B.Bot b c () -> Command b c
|
command :: T.Text -> B.Bot b c () -> Command b c
|
||||||
command = undefined
|
command = undefined
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue