habit-0.2.1.2: src/Web/Bot/Story.hs
-- |
-- Module : Web.Bot.Story
-- Copyright : Alexander Krupenkin 2016
-- License : BSD3
--
-- Maintainer : mail@akru.me
-- Stability : experimental
-- Portability : portable
--
-- Story is a dialog like abstraction for processing sparsed
-- by messages data from user.
--
-- @
-- hello :: Monad m => Text -> Text -> Int -> m BotMessage
-- hello name surname age = toMessage $
-- "Hello, " <> name <> " " <> surname <> "!\n"
-- <> "You lost " <> (T.pack $ show age) <> " years =)"
--
-- helloStory :: BotConfig a => Story a
-- helloStory _ = hello <$> question "How your name?"
-- <*> question "How your surname?"
-- <*> question "How old are you?"
-- @
--
module Web.Bot.Story where
import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
import Data.Text.Read (signed, decimal, double)
import Control.Monad.IO.Class (MonadIO)
import Pipes (Pipe, await, yield)
import qualified Data.Text as T
import Data.Text (Text)
import Web.Bot.Message (Message(..), ToMessage(..))
import Web.Bot.Platform (Bot)
import Web.Bot.User (User)
-- | Story is a pipe from user message to bot message
-- and result is a final bot message.
type Story a = User -> StoryT (Bot a) Message
-- | Story transformer is based on 'Pipe'
-- with fixed 'Message' in/out.
type StoryT = Pipe Message Message
-- | User message reply parser.
class Answer a where
parse :: MonadIO m => Message -> ExceptT Text m a
instance Answer Text where
parse (MsgText x) = return x
parse _ = throwE "Please send text message."
instance Answer Double where
parse x = do
t <- parse x
case signed double t of
Left e -> throwE (T.pack e)
Right (v, "") -> return v
Right _ -> throwE "Please use only 0-9 and '.' chars."
instance Answer Integer where
parse x = do
t <- parse x
case signed decimal t of
Left e -> throwE (T.pack e)
Right (v, x) -> if T.null x
then return v
else throwE "Please use only 0-9 chars."
instance Answer Int where
parse x = do
v <- parse x
return (fromIntegral (v :: Integer))
instance Answer Word where
parse x = do
v <- parse x
return (fromIntegral (v :: Integer))
-- | Reply keyboard selection
select :: (MonadIO m, Answer a) => Text -> [[Text]] -> StoryT m a
{-# INLINE select #-}
select q = replica . MsgKeyboard q
-- | Bot text question.
question :: (MonadIO m, Answer a) => Text -> StoryT m a
{-# INLINE question #-}
question = replica
-- | Generalized story maker.
-- The replica send message to user, when answer isn't parsed
-- the error be sended and wait for correct answer.
replica :: (ToMessage a, MonadIO m, Answer b) => a -> StoryT m b
replica msg = do
yield (toMessage msg)
res <- runExceptT . parse =<< await
yield MsgTyping
case res of
Left e -> replica e
Right a -> return a