Track bot's current SessionView in the Bot monad

This commit is contained in:
Joscha 2018-02-18 20:10:28 +00:00
parent 2fc6bf98a3
commit fbf1402e24
3 changed files with 68 additions and 13 deletions

View file

@ -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

View file

@ -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 ()

View file

@ -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