packages feed

fbmessenger-api-0.1.0.3: example-app/example.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}

import Control.Concurrent (forkIO, threadDelay)
import Control.Monad (when, forever)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Except (ExceptT)
import qualified Data.ByteString.Lazy.Char8 as C
--import Data.Either (either)
import Data.Foldable (traverse_)
import Data.Maybe (fromMaybe)
import Data.Proxy
import qualified Data.Text as T
import Network.HTTP.Client (newManager)
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Network.Wai
import Network.Wai.Handler.Warp
import Network.Wai.Logger (withStdoutLogger)
import Servant
import System.Environment
import System.Exit (exitFailure)
import System.IO
import Text.Printf (printf)
import Web.FBMessenger.API.Bot

type WebHookAPI = "webhook" :> 
    QueryParam "hub.verify_token" String :> 
    QueryParam "hub.challenge" String :> 
    Get '[PlainText] String
  :<|> "webhook" :>
    ReqBody '[JSON] RemoteEventList :>
    Post '[PlainText,JSON] String

webHookAPI :: Proxy WebHookAPI
webHookAPI = Proxy

-- TODO: use monad-logger to implement proper logging

server :: String -> String -> Server WebHookAPI
server verifyTokenStored pageTokenStored = 
  webhook_verify
  :<|> webhook_message
  where
    webhook_verify :: Maybe String -> Maybe String -> ExceptT ServantErr IO String
    webhook_verify (Just verifyToken) (Just challenge) 
      | verifyToken == verifyTokenStored = return challenge
    webhook_verify tk ch = let
      eB :: String
      eB = printf "[ERROR]: wrong validation request. Got (token, challenge) = (%s, %s)" (show tk) (show ch)
      in do 
        liftIO $ putStrLn eB
        throwError err500 { errBody = C.pack eB}

    webhook_message :: RemoteEventList -> ExceptT ServantErr IO String
    webhook_message (RemoteEventList res) = do
      r <- traverse echoMessage (concatMap evt_messaging res)
      liftIO $ traverse_ (putStrLn . T.unpack) r
      return "{\"status\":\"fulfilled\"}"

    token = Token $ T.pack pageTokenStored

    echoMessage :: EventMessage -> ExceptT ServantErr IO T.Text
    echoMessage msg = do
      case evtContent msg of
        EmTextMessage _ _ text -> liftIO $ echoTextMessage text (evtSenderId msg) 
        _                      -> return "[WARN]: this is just an example, no complex message is echoed."
    
    echoTextMessage text rcptId = do 
      m <- newManager tlsManagerSettings
      -- create the recipient using the sender id
      -- (we know that this is successful when only one argument is not Nothing) 
      let (Just rcpt) = recipient (Just rcptId) Nothing
      -- prepare the Send API request body 
      let messageReq = sendTextMessageRequest Nothing rcpt text
      -- finally send the message request using the tls http connection manager  
      logRsp <- sendTextMessage (Just token) messageReq m
      -- restructure the response to get a decent log out of it
      -- e.g. one could add the response or the error in the log message
      let logMsg = either (\_ -> printf "[ERROR]: failed to echo message")
                          (\_ -> printf "[INFO]: sent \"%s\" to %s" (T.unpack text) (show rcpt))
                          logRsp
      return (T.pack logMsg)

app :: String -> String -> Application
app verifyToken pageToke = serve webHookAPI $ server verifyToken pageToken 

main :: IO ()
main = do
    hSetBuffering stdout LineBuffering
    env <- getEnvironment

    let port = maybe 3000 read $ lookup "PORT" env

    let verifyToken = fromMaybe "" $ lookup "VERIFY_TOKEN" env
    when (verifyToken == "") $ do
      putStrLn "[WARN]: please set VERIFY_TOKEN to a safe string"
      exitFailure

    let pageToken = fromMaybe "" $ lookup "PAGE_TOKEN" env
    when (pageToken == "") $ putStrLn "[WARN]: make sure to set up the correct page token"

    putStrLn $ printf "[INFO]: server listening on port %i" port
    withStdoutLogger $ \aplogger -> do
      let settings = setPort port $ setLogger aplogger defaultSettings
      runSettings settings (app verifyToken pageToken)