{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Telegram.Bot.API.Webhook
( setUpWebhook,
webhookApp,
deleteWebhook,
SetWebhookRequest (..),
)
where
import Control.Concurrent (forkIO)
import Control.Concurrent.STM
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.Aeson (ToJSON (toJSON))
import Data.Bool (bool)
import Data.Functor (void, (<&>))
import Data.Maybe (catMaybes, fromJust,
isJust)
import qualified Data.Text as Text
import GHC.Generics (Generic)
import Servant
import Servant.Client (ClientEnv, ClientError,
client, runClientM)
import Servant.Multipart.API
import Servant.Multipart.Client (genBoundary)
import Telegram.Bot.API.GettingUpdates (Update)
import Telegram.Bot.API.Internal.Utils (gtoJSON)
import Telegram.Bot.API.MakingRequests (Response)
import Telegram.Bot.API.Types (InputFile, makeFile)
import Telegram.Bot.Simple.BotApp.Internal
type WebhookAPI = ReqBody '[JSON] Update :> Post '[JSON] ()
server :: BotApp model action -> BotEnv model action -> Server WebhookAPI
server BotApp {..} botEnv@BotEnv {..} =
updateHandler
where
updateHandler :: Update -> Handler ()
updateHandler update = liftIO $ handleUpdate update
handleUpdate update = liftIO . void . forkIO $ do
maction <- botAction update <$> readTVarIO botModelVar
case maction of
Nothing -> return ()
Just action -> issueAction botEnv (Just update) (Just action)
webhookAPI :: Proxy WebhookAPI
webhookAPI = Proxy
app :: BotApp model action -> BotEnv model action -> Application
app botApp botEnv = serve webhookAPI $ server botApp botEnv
data SetWebhookRequest = SetWebhookRequest
{ setWebhookUrl :: String,
setWebhookCertificate :: Maybe InputFile,
setWebhookIpAddress :: Maybe String,
setWebhookMaxConnections :: Maybe Int,
setWebhookAllowedUpdates :: Maybe [String],
setWebhookDropPendingUpdates :: Maybe Bool,
setWebhookSecretToken :: Maybe String
}
deriving (Generic)
instance ToJSON SetWebhookRequest where toJSON = gtoJSON
newtype DeleteWebhookRequest = DeleteWebhookRequest
{ deleteWebhookDropPendingUpdates :: Maybe Bool
}
deriving (Generic)
instance ToJSON DeleteWebhookRequest where toJSON = gtoJSON
instance ToMultipart Tmp SetWebhookRequest where
toMultipart SetWebhookRequest {..} =
makeFile "certificate" (fromJust setWebhookCertificate) (MultipartData fields [])
where
fields =
[Input "url" $ Text.pack setWebhookUrl]
<> catMaybes
[ setWebhookSecretToken <&> \t -> Input "secret_token" $ Text.pack t,
setWebhookIpAddress <&> \t -> Input "ip_address" $ Text.pack t,
setWebhookMaxConnections <&> \t -> Input "max_connections" $ Text.pack $ show t,
setWebhookDropPendingUpdates <&> \t -> Input "drop_pending_updates" (bool "false" "true" t),
setWebhookAllowedUpdates <&> \t -> Input "allowed_updates" (arrToJson t)
]
arrToJson arr = Text.intercalate "" ["[", Text.intercalate "," (map (\s -> Text.pack $ "\"" ++ s ++ "\"") arr), "]"]
type SetWebhookForm =
"setWebhook" :> MultipartForm Tmp SetWebhookRequest :> Get '[JSON] (Response Bool)
type SetWebhookJson =
"setWebhook" :> ReqBody '[JSON] SetWebhookRequest :> Get '[JSON] (Response Bool)
type DeleteWebhook =
"deleteWebhook" :> ReqBody '[JSON] DeleteWebhookRequest :> Get '[JSON] (Response Bool)
setUpWebhook :: SetWebhookRequest -> ClientEnv -> IO (Either ClientError ())
setUpWebhook requestData = (void <$>) <$> runClientM setUpWebhookRequest
where
setUpWebhookRequest =
if isJust $ setWebhookCertificate requestData
then do
boundary <- liftIO genBoundary
client (Proxy @SetWebhookForm) (boundary, requestData)
else client (Proxy @SetWebhookJson) requestData
deleteWebhook :: ClientEnv -> IO (Either ClientError ())
deleteWebhook = (void <$>) <$> runClientM deleteWebhookRequest
where
requestData = DeleteWebhookRequest {deleteWebhookDropPendingUpdates = Nothing}
deleteWebhookRequest = client (Proxy @DeleteWebhook) requestData
webhookApp :: BotApp model action -> BotEnv model action -> Application
webhookApp = app