I'm trying to build a Telegram bot using telegram-api. I haven't had an issues with that so far since I could read the tests to understand how things worked, but I've had a lot of trouble when it comes to building a webhook endpoint with Servant. The overall idea is that when I receive an Update
from the webhook, I send a reply back.
The problem, is to do with my postWebhook
code, where it expects to receive a Message
but instead receives an IO Message
. I think this is because Servant isn't expecting me to make a request inside that function because I have the type EitherT ServantError IO (IO Message)
(partially applied by BotHandler
) when really it should be EitherT ServantError IO Message
.
I'm still learning Haskell but I understand that I somehow have to get the message out of the IO monad? Updating BotAPI
to return a Post '[JSON] (IO Message)
gives me this: No instance for (Foldable IO) arising from a use of ‘serve’
, which is getting beyond my beginner knowledge, and I can see that fiddling with the types just moves the same problem to a different part of the code. I just don't know how to work around it.
Here is the code with sensitive strings removed:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
module Main where
import Control.Monad
import Control.Monad.Trans.Either
import Data.Proxy
import Data.Text (Text, pack)
import Network.Wai.Handler.Warp
import Servant
import Web.Telegram.API.Bot
reply :: Token -> Message -> Text -> IO Message
reply token msg response = do
Right MessageResponse { message_result = m } <-
sendMessage token $ SendMessageRequest chatId response (Just Markdown) Nothing Nothing Nothing
return m
where chatId = pack . show $ chat_id (chat msg)
type BotAPI = "webhook" :> ReqBody '[JSON] Update :> Post '[JSON] Message
type BotHandler a = EitherT ServantErr IO a
botApi :: Proxy BotAPI
botApi = Proxy
initWebhook :: Token -> IO Bool
initWebhook token = do
Right SetWebhookResponse { webhook_result = w } <-
setWebhook token $ Just "https://example.com/webhook"
return w
postWebhook :: Token -> Update -> BotHandler (IO Message)
postWebhook token update = case message update of
Just msg -> return $ reply token msg "Testing!"
Nothing -> left err400
server :: Token -> Server BotAPI
server = postWebhook
main :: IO ()
main = do
initWebhook token
run port $ serve botApi (server token)
where token = Token "<token>"
port = 8080
Apologies for what might not be ideal Haskell code. Thank you in advance :)
Your error is at
Just msg → return $ reply token msg "Testing!"
you are on
EitherT ServantErr IO Message
monad but reply
has type
reply :: Token → Message → Text → IO Message
then simply lift
that IO
action into your monad, it works
postWebhook :: Token → Update → BotHandler Message
postWebhook token update = case message update of
Just msg → lift $ reply token msg "Testing!"
Nothing → left err400
(Is not easy to me explain all things involved here) I think you should practice more about monads, transformers, ... before these complex examples but you're brave! :)