Add threading and more commands
This commit is contained in:
parent
e72e647b5f
commit
f0c9f92d44
3 changed files with 124 additions and 18 deletions
|
|
@ -5,7 +5,7 @@ module Haboli.Euphoria.Api
|
||||||
-- * Basic types
|
-- * Basic types
|
||||||
, AuthOption(..)
|
, AuthOption(..)
|
||||||
, Message(..)
|
, Message(..)
|
||||||
, PersonalAccountView
|
, PersonalAccountView(..)
|
||||||
, SessionView(..)
|
, SessionView(..)
|
||||||
, Snowflake
|
, Snowflake
|
||||||
, UserType(..)
|
, UserType(..)
|
||||||
|
|
@ -36,6 +36,9 @@ module Haboli.Euphoria.Api
|
||||||
-- ** nick
|
-- ** nick
|
||||||
, NickCommand(..)
|
, NickCommand(..)
|
||||||
, NickReply(..)
|
, NickReply(..)
|
||||||
|
-- ** send
|
||||||
|
, SendCommand(..)
|
||||||
|
, SendReply(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
|
@ -201,6 +204,10 @@ data JoinEvent = JoinEvent
|
||||||
{ joinSession :: SessionView
|
{ joinSession :: SessionView
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
|
instance FromJSON JoinEvent where
|
||||||
|
parseJSON = fromPacket "join-event" $ \o -> JoinEvent
|
||||||
|
<$> parseJSON (Object o)
|
||||||
|
|
||||||
data LoginEvent = LoginEvent
|
data LoginEvent = LoginEvent
|
||||||
{ loginAccountId :: Snowflake
|
{ loginAccountId :: Snowflake
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
@ -230,6 +237,10 @@ data PartEvent = PartEvent
|
||||||
{ partSession :: SessionView
|
{ partSession :: SessionView
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
|
instance FromJSON PartEvent where
|
||||||
|
parseJSON = fromPacket "part-event" $ \o -> PartEvent
|
||||||
|
<$> parseJSON (Object o)
|
||||||
|
|
||||||
data PingEvent = PingEvent
|
data PingEvent = PingEvent
|
||||||
{ pingTime :: UTCTime
|
{ pingTime :: UTCTime
|
||||||
, pingNext :: UTCTime
|
, pingNext :: UTCTime
|
||||||
|
|
@ -251,6 +262,10 @@ data SendEvent = SendEvent
|
||||||
{ sendMessage :: Message
|
{ sendMessage :: Message
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
|
instance FromJSON SendEvent where
|
||||||
|
parseJSON = fromPacket "send-event" $ \o -> SendEvent
|
||||||
|
<$> parseJSON (Object o)
|
||||||
|
|
||||||
{- snapshot-event -}
|
{- snapshot-event -}
|
||||||
|
|
||||||
data SnapshotEvent = SnapshotEvent
|
data SnapshotEvent = SnapshotEvent
|
||||||
|
|
@ -315,9 +330,8 @@ data NickCommand = NickCommand T.Text
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
instance ToJSONObject NickCommand where
|
instance ToJSONObject NickCommand where
|
||||||
toJSONObject (NickCommand nick) = HMap.fromList
|
toJSONObject (NickCommand nick) = toPacket "nick" $ object
|
||||||
[ "type" .= String "nick"
|
[ "name" .= nick
|
||||||
, "data" .= object ["name" .= nick]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
data NickReply = NickReply
|
data NickReply = NickReply
|
||||||
|
|
@ -333,3 +347,21 @@ instance FromJSON NickReply where
|
||||||
<*> o .: "id"
|
<*> o .: "id"
|
||||||
<*> o .: "from"
|
<*> o .: "from"
|
||||||
<*> o .: "to"
|
<*> o .: "to"
|
||||||
|
|
||||||
|
{- send -}
|
||||||
|
|
||||||
|
data SendCommand = SendCommand T.Text (Maybe Snowflake)
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
instance ToJSONObject SendCommand where
|
||||||
|
toJSONObject (SendCommand content Nothing) =
|
||||||
|
toPacket "send" $ object ["content" .= content]
|
||||||
|
toJSONObject (SendCommand content (Just parent)) =
|
||||||
|
toPacket "send" $ object ["content" .= content, "parent" .= parent]
|
||||||
|
|
||||||
|
data SendReply = SendReply Message
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
instance FromJSON SendReply where
|
||||||
|
parseJSON = fromPacket "send-reply" $ \o -> SendReply
|
||||||
|
<$> parseJSON (Object o)
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,19 @@ module Haboli.Euphoria.Client
|
||||||
, ClientException(..)
|
, ClientException(..)
|
||||||
, Haboli.Euphoria.Client.throw
|
, Haboli.Euphoria.Client.throw
|
||||||
, Haboli.Euphoria.Client.catch
|
, Haboli.Euphoria.Client.catch
|
||||||
|
, Haboli.Euphoria.Client.handle
|
||||||
|
-- ** Threading
|
||||||
|
, Thread
|
||||||
|
, fork
|
||||||
|
, wait
|
||||||
-- ** Euphoria commands
|
-- ** Euphoria commands
|
||||||
-- *** Session commands
|
-- *** Session commands
|
||||||
, pingReply
|
, pingReply
|
||||||
-- *** Chat room commands
|
-- *** Chat room commands
|
||||||
, nick
|
, nick
|
||||||
|
, Haboli.Euphoria.Client.send
|
||||||
|
, reply
|
||||||
|
, reply'
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
|
|
@ -54,14 +62,28 @@ import Haboli.Euphoria.Api
|
||||||
|
|
||||||
--TODO: Add all the events
|
--TODO: Add all the events
|
||||||
data Event
|
data Event
|
||||||
= EventPing PingEvent
|
= EventBounce BounceEvent
|
||||||
|
| EventDisconnect DisconnectEvent
|
||||||
|
| EventHello HelloEvent
|
||||||
|
| EventJoin JoinEvent
|
||||||
|
| EventLogin LoginEvent
|
||||||
|
| EventLogout LogoutEvent
|
||||||
|
| EventNetwork NetworkEvent
|
||||||
|
| EventNick NickEvent
|
||||||
|
| EventEditMessage EditMessageEvent
|
||||||
|
| EventPart PartEvent
|
||||||
|
| EventPing PingEvent
|
||||||
|
| EventPmInitiate PmInitiateEvent
|
||||||
|
| EventSend SendEvent
|
||||||
| EventSnapshot SnapshotEvent
|
| EventSnapshot SnapshotEvent
|
||||||
| PlaceholderEvent --TODO: remove this event
|
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
instance FromJSON Event where
|
instance FromJSON Event where
|
||||||
parseJSON v = foldr (<|>) mempty
|
parseJSON v = foldr (<|>) mempty
|
||||||
[ EventPing <$> parseJSON v
|
[ EventJoin <$> parseJSON v
|
||||||
|
, EventPart <$> parseJSON v
|
||||||
|
, EventPing <$> parseJSON v
|
||||||
|
, EventSend <$> parseJSON v
|
||||||
, EventSnapshot <$> parseJSON v
|
, EventSnapshot <$> parseJSON v
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -74,6 +96,9 @@ data ClientException e
|
||||||
| StoppedException
|
| StoppedException
|
||||||
| DecodeException T.Text
|
| DecodeException T.Text
|
||||||
-- ^ At some point during decoding a websocket packet, something went wrong.
|
-- ^ At some point during decoding a websocket packet, something went wrong.
|
||||||
|
| UnexpectedException SomeException
|
||||||
|
-- ^ While a forked thread was executed, an unexpected exception was thrown in
|
||||||
|
-- the IO monad.
|
||||||
| CustomException e
|
| CustomException e
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
|
@ -160,10 +185,10 @@ parseAndSendReply msg awaiting = do
|
||||||
runWebsocketThread :: ClientInfo e -> IO ()
|
runWebsocketThread :: ClientInfo e -> IO ()
|
||||||
runWebsocketThread info
|
runWebsocketThread info
|
||||||
= WS.withPingThread (ciConnection info) pingInterval (pure ())
|
= WS.withPingThread (ciConnection info) pingInterval (pure ())
|
||||||
$ handle (cancellingAllReplies info) $ forever
|
$ Control.Exception.handle (cancellingAllReplies info) $ forever
|
||||||
$ handle ignoringInvalidMessages $ do
|
$ Control.Exception.handle ignoringInvalidMessages $ do
|
||||||
msg <- WS.receiveData (ciConnection info)
|
msg <- WS.receiveData (ciConnection info)
|
||||||
-- print msg
|
print msg
|
||||||
parseAndSendEvent msg (ciEventChan info)
|
parseAndSendEvent msg (ciEventChan info)
|
||||||
parseAndSendReply msg (ciAwaiting info)
|
parseAndSendReply msg (ciAwaiting info)
|
||||||
where
|
where
|
||||||
|
|
@ -210,7 +235,8 @@ runClient details (Client stack)
|
||||||
result <- runReaderT (runExceptT stack) info
|
result <- runReaderT (runExceptT stack) info
|
||||||
-- Close the connection if it is not already closed, and wait until the
|
-- Close the connection if it is not already closed, and wait until the
|
||||||
-- websocket thread stops
|
-- websocket thread stops
|
||||||
handle ignoreAllExceptions $ WS.sendClose connection $ T.pack "Goodbye :D"
|
Control.Exception.handle ignoreAllExceptions
|
||||||
|
$ WS.sendClose connection $ T.pack "Goodbye :D"
|
||||||
takeMVar wsThreadFinished
|
takeMVar wsThreadFinished
|
||||||
pure result
|
pure result
|
||||||
where
|
where
|
||||||
|
|
@ -238,7 +264,7 @@ newPacketId = do
|
||||||
safeSend :: ToJSON a => WS.Connection -> a -> Client e ()
|
safeSend :: ToJSON a => WS.Connection -> a -> Client e ()
|
||||||
safeSend connection packet = do
|
safeSend connection packet = do
|
||||||
result <- liftIO
|
result <- liftIO
|
||||||
$ handle convertToException
|
$ Control.Exception.handle convertToException
|
||||||
$ Nothing <$ WS.sendTextData connection (encode packet)
|
$ Nothing <$ WS.sendTextData connection (encode packet)
|
||||||
case result of
|
case result of
|
||||||
Nothing -> pure ()
|
Nothing -> pure ()
|
||||||
|
|
@ -280,8 +306,8 @@ sendPacketWithReply packet = do
|
||||||
case maybeReplyVar of
|
case maybeReplyVar of
|
||||||
Nothing -> throwRaw StoppedException
|
Nothing -> throwRaw StoppedException
|
||||||
Just replyVar -> do
|
Just replyVar -> do
|
||||||
reply <- liftIO $ atomically $ readTMVar replyVar
|
answer <- liftIO $ atomically $ readTMVar replyVar
|
||||||
case reply of
|
case answer of
|
||||||
Left e -> throwRaw e
|
Left e -> throwRaw e
|
||||||
Right r -> pure r
|
Right r -> pure r
|
||||||
|
|
||||||
|
|
@ -298,7 +324,7 @@ getPort = cdPort . ciDetails <$> getClientInfo
|
||||||
getPath :: Client e String
|
getPath :: Client e String
|
||||||
getPath = cdPath . ciDetails <$> getClientInfo
|
getPath = cdPath . ciDetails <$> getClientInfo
|
||||||
|
|
||||||
{- Special operations -}
|
{- Event handling -}
|
||||||
|
|
||||||
nextEvent :: Client e Event
|
nextEvent :: Client e Event
|
||||||
nextEvent = do
|
nextEvent = do
|
||||||
|
|
@ -330,6 +356,30 @@ catch c f = Client $ catchE (unclient c) (unclient . f)
|
||||||
where
|
where
|
||||||
unclient (Client m) = m
|
unclient (Client m) = m
|
||||||
|
|
||||||
|
handle :: (ClientException e -> Client e a) -> Client e a -> Client e a
|
||||||
|
handle = flip Haboli.Euphoria.Client.catch
|
||||||
|
|
||||||
|
{- Threading -}
|
||||||
|
|
||||||
|
data Thread e a = Thread (MVar (Either (ClientException e) a))
|
||||||
|
|
||||||
|
fork :: Client e a -> Client e (Thread e a)
|
||||||
|
fork (Client f) = do
|
||||||
|
info <- getClientInfo
|
||||||
|
waitVar <- liftIO newEmptyMVar
|
||||||
|
let thread = runReaderT (runExceptT f) info
|
||||||
|
andThen (Left e) = putMVar waitVar $ Left $ UnexpectedException e
|
||||||
|
andThen (Right r) = putMVar waitVar r
|
||||||
|
void $ liftIO $ forkFinally thread andThen
|
||||||
|
pure $ Thread waitVar
|
||||||
|
|
||||||
|
wait :: Thread e a -> Client e a
|
||||||
|
wait (Thread waitVar) = do
|
||||||
|
result <- liftIO $ readMVar waitVar
|
||||||
|
case result of
|
||||||
|
(Left e) -> throwRaw e
|
||||||
|
(Right a) -> pure a
|
||||||
|
|
||||||
{- Euphoria commands -}
|
{- Euphoria commands -}
|
||||||
|
|
||||||
{- Session commands -}
|
{- Session commands -}
|
||||||
|
|
@ -341,5 +391,18 @@ pingReply = void . sendPacket . PingReply
|
||||||
|
|
||||||
nick :: T.Text -> Client e T.Text
|
nick :: T.Text -> Client e T.Text
|
||||||
nick targetNick = do
|
nick targetNick = do
|
||||||
reply <- sendPacketWithReply $ NickCommand targetNick
|
answer <- sendPacketWithReply $ NickCommand targetNick
|
||||||
pure $ nickReplyTo reply
|
pure $ nickReplyTo answer
|
||||||
|
|
||||||
|
send :: T.Text -> Client e Message
|
||||||
|
send content = do
|
||||||
|
(SendReply msg) <- sendPacketWithReply $ SendCommand content Nothing
|
||||||
|
pure msg
|
||||||
|
|
||||||
|
reply' :: Snowflake -> T.Text -> Client e Message
|
||||||
|
reply' messageId content = do
|
||||||
|
(SendReply msg) <- sendPacketWithReply $ SendCommand content (Just messageId)
|
||||||
|
pure msg
|
||||||
|
|
||||||
|
reply :: Message -> T.Text -> Client e Message
|
||||||
|
reply = reply' . msgId
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import Haboli.Euphoria.Client
|
||||||
|
|
||||||
printAllEventsBot :: Client () ()
|
printAllEventsBot :: Client () ()
|
||||||
printAllEventsBot = forever $ do
|
printAllEventsBot = forever $ do
|
||||||
liftIO $ putStrLn "Waiting for the next event"
|
liftIO $ putStrLn "\nWaiting for the next event...\n"
|
||||||
liftIO . print =<< respondingToPing nextEvent
|
liftIO . print =<< respondingToPing nextEvent
|
||||||
|
|
||||||
setNickAndThenWaitBot :: Client () ()
|
setNickAndThenWaitBot :: Client () ()
|
||||||
|
|
@ -24,3 +24,14 @@ throwCustomExceptionBot = throw "Hello world"
|
||||||
|
|
||||||
immediatelyDisconnectBot :: Client () ()
|
immediatelyDisconnectBot :: Client () ()
|
||||||
immediatelyDisconnectBot = pure ()
|
immediatelyDisconnectBot = pure ()
|
||||||
|
|
||||||
|
sendMessagesUntilThrottledBot :: Client () ()
|
||||||
|
sendMessagesUntilThrottledBot = forever $ do
|
||||||
|
event <- respondingToPing nextEvent
|
||||||
|
case event of
|
||||||
|
EventSnapshot _ -> do
|
||||||
|
void $ nick "SpamBot"
|
||||||
|
msg <- send "start thread"
|
||||||
|
void $ fork $ handle (\_ -> reply msg "got throttled") $
|
||||||
|
forever $ reply msg "continue thread"
|
||||||
|
_ -> pure ()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue