Add threading and more commands

This commit is contained in:
Joscha 2020-01-06 20:58:16 +00:00
parent e72e647b5f
commit f0c9f92d44
3 changed files with 124 additions and 18 deletions

View file

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

View file

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

View file

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