telegram-api 0.6.3.0 → 0.7.2.0
raw patch · 22 files changed
Files
- CHANGELOG.md +11/−0
- README.md +10/−9
- src/Servant/Client/MultipartFormData.hs +59/−51
- src/Web/Telegram/API/Bot/API.hs +4/−1
- src/Web/Telegram/API/Bot/API/Chats.hs +138/−8
- src/Web/Telegram/API/Bot/API/Core.hs +5/−0
- src/Web/Telegram/API/Bot/API/Edit.hs +22/−2
- src/Web/Telegram/API/Bot/API/Messages.hs +11/−1
- src/Web/Telegram/API/Bot/API/Stickers.hs +114/−0
- src/Web/Telegram/API/Bot/API/Updates.hs +1/−1
- src/Web/Telegram/API/Bot/Data.hs +143/−11
- src/Web/Telegram/API/Bot/Requests.hs +243/−29
- telegram-api.cabal +14/−5
- test-data/sticker_1.png binary
- test-data/sticker_2.png binary
- test/InlineSpec.hs +2/−2
- test/MainSpec.hs +42/−28
- test/PaymentsSpec.hs +6/−12
- test/Spec.hs +2/−0
- test/StickersSpec.hs +87/−0
- test/TestCore.hs +19/−0
- test/UpdatesSpec.hs +9/−10
CHANGELOG.md view
@@ -1,3 +1,14 @@+## 0.7.1.0++* Added support up to Bot API v3.5: sticker sets, chats administration, live location, etc+* Added `runTelegramClient` with different than in `runClient` order of arguments. Now possible to write `runTelegramClient token manager $ do`+* Stop adding request specific responses+* Stop adding IO calls, use functions with `M` suffix, for example `runClient getWebhookInfoM`++## 0.7.0.0++* Upgraded to servant-0.11 + ## 0.6.3.0 * New fields *gif_duration* in `InlineQueryResultGif` and *mpeg4_duration* in `InlineQueryResultMpeg4Gif`.
README.md view
@@ -13,7 +13,7 @@ Inline mode is supported. Uploading stickers, documents, video, etc is not supported yet, but you can send items which are already uploaded on the Telegram servers. -**Support of [Bot-3.0 API][bot-3.0]**+**Support of [Bot-3.5 API][bot-api]** ## Usage @@ -32,16 +32,17 @@ main = do let token = Token "bot<token>" -- entire Token should be bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 manager <- newManager tlsManagerSettings- result <- runClient ( do+ result <- runTelegramClient token manager $ do info <- getWebhookInfoM let request = setWebhookRequest' "https://example.com/hook" isSet <- setWebhookM request- getMeM) token manager+ getMeM print result print "done!" ``` -### Runing IO directly+### Running IO directly (planning to depricate this option)+:warning: This method to interact with a Telegram bot is about to be depricated and all new API calls will only have their `M` versions. You can run them using `runTelegramClient` function, for example `runTelegramClient token manager $ sendMessageM message` or in example below replace `getMe token manager` with `runTelegramClient token manager getMeM` to get the same behavior. `getMe` example @@ -89,7 +90,7 @@ print $ message_id m print $ text m where token = Token "bot<token>" -- entire Token should be bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11- chatId = "<chat_id> or <@channelusername>"+ chatId = ChatId <chat_id> -- use ChatId 10231 or ChatChannel "<@channelusername>" message = "text *bold* _italic_ [github](github.com/klappvisor/haskell-telegram-api)" ``` @@ -100,16 +101,16 @@ With data type constructor: ```haskell-let request = SendMessageRequest "chatId" "text" Nothing (Just True) Nothing Nothing Nothing+let request = SendMessageRequest (ChatId int64_chatId) "text" Nothing (Just True) Nothing Nothing Nothing ``` Using default instance: ```haskell-let request = sendMessageRequest "chatId" "text" -- only with required fields+let request = sendMessageRequest (ChatId int64_chatId) "text" -- only with required fields ``` ```haskell-let request = (sendMessageRequest "chatId" "text") {+let request = (sendMessageRequest ChatId int64_chatId) "text") { message_disable_notification = Just True -- with optional fields } ```@@ -164,4 +165,4 @@ [telegram-bot-api]: https://core.telegram.org/bots/api [servant]: https://haskell-servant.github.io/ [hspec-args]: https://hspec.github.io/running-specs.html-[bot-3.0]: https://core.telegram.org/bots/api+[bot-api]: https://core.telegram.org/bots/api
src/Servant/Client/MultipartFormData.hs view
@@ -1,38 +1,46 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} module Servant.Client.MultipartFormData ( ToMultipartFormData (..) , MultipartFormDataReqBody ) where -import Control.Exception+--import Control.Exception import Control.Monad-import Control.Monad.Reader.Class-import Control.Monad.IO.Class import Control.Monad.Error.Class-import Data.ByteString.Lazy hiding (pack, filter, map, null, elem)+import Control.Monad.IO.Class+import Control.Monad.Reader.Class+import Data.ByteString.Lazy hiding (any, elem,+ filter, map, null, pack) import Data.Proxy-import Data.String.Conversions-import Data.Typeable (Typeable)-import Network.HTTP.Client hiding (Proxy, path)-import qualified Network.HTTP.Client as Client+--import Data.Foldable (toList)+import qualified Data.List.NonEmpty as NonEmpty+--import Data.String.Conversions+import qualified Data.Sequence as Sequence+import Data.Text (pack)+import Data.Typeable (Typeable)+import Network.HTTP.Client hiding (Proxy, path)+import qualified Network.HTTP.Client as Client import Network.HTTP.Client.MultipartFormData import Network.HTTP.Media import Network.HTTP.Types-import qualified Network.HTTP.Types as H-import qualified Network.HTTP.Types.Header as HTTP+import qualified Network.HTTP.Types as H+import qualified Network.HTTP.Types.Header as HTTP import Servant.API-import Servant.Common.BaseUrl import Servant.Client-import Servant.Common.Req (Req, catchConnectionError,- reqAccept, reqToRequest)+import qualified Servant.Client.Core as Core+import Servant.Client.Internal.HttpClient (catchConnectionError,+ clientResponseToReponse,+ requestToClientRequest)+ -- | A type that can be converted to a multipart/form-data value. class ToMultipartFormData a where -- | Convert a Haskell value to a multipart/form-data-friendly intermediate type.@@ -42,60 +50,60 @@ data MultipartFormDataReqBody a deriving (Typeable) -instance (ToMultipartFormData b, MimeUnrender ct a, cts' ~ (ct ': cts)- ) => HasClient (MultipartFormDataReqBody b :> Post cts' a) where- type Client (MultipartFormDataReqBody b :> Post cts' a)- = b -> ClientM a- clientWithRoute Proxy req reqData =- let reqToRequest' req' baseurl' = do- requestWithoutBody <- reqToRequest req' baseurl'+instance (Core.RunClient m, ToMultipartFormData b, MimeUnrender ct a, cts' ~ (ct ': cts)+ ) => HasClient m (MultipartFormDataReqBody b :> Post cts' a) where+ type Client m (MultipartFormDataReqBody b :> Post cts' a) = b-> ClientM a+ clientWithRoute _pm Proxy req reqData =+ let requestToClientRequest' req' baseurl' = do+ let requestWithoutBody = requestToClientRequest baseurl' req' formDataBody (toMultipartFormData reqData) requestWithoutBody- in snd <$> performRequestCT' reqToRequest' (Proxy :: Proxy ct) H.methodPost req+ in snd <$> performRequestCT' requestToClientRequest' (Proxy :: Proxy ct) H.methodPost req --- copied `performRequest` from servant-0.7.1, then modified so it takes a variant of `reqToRequest`+-- copied `performRequest` from servant-0.11, then modified so it takes a variant of `requestToClientRequest` -- as an argument.-performRequest' :: (Req -> BaseUrl -> IO Request)- -> Method -> Req -> Manager -> BaseUrl+performRequest' :: (Core.Request -> BaseUrl -> IO Request)+ -> Method -> Core.Request -> ClientM ( Int, ByteString, MediaType- , [HTTP.Header], Response ByteString)-performRequest' reqToRequest' reqMethod req manager reqHost = do- partialRequest <- liftIO $ reqToRequest' req reqHost+ , [HTTP.Header], Client.Response ByteString)+performRequest' requestToClientRequest' reqMethod req = do+ m <- asks manager+ reqHost <- asks baseUrl+ partialRequest <- liftIO $ requestToClientRequest' req reqHost - let request = partialRequest { Client.method = reqMethod- , checkResponse = \ _request _response -> return ()- }+ let request = partialRequest { Client.method = reqMethod } - eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request manager+ eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m case eResponse of Left err ->- throwError . ConnectionError $ SomeException err+ throwError . ConnectionError $ pack $ show err Right response -> do let status = Client.responseStatus response body = Client.responseBody response hdrs = Client.responseHeaders response status_code = statusCode status+ coreResponse = clientResponseToReponse response ct <- case lookup "Content-Type" $ Client.responseHeaders response of Nothing -> pure $ "application"//"octet-stream" Just t -> case parseAccept t of- Nothing -> throwError $ InvalidContentTypeHeader (cs t) body+ Nothing -> throwError $ InvalidContentTypeHeader coreResponse Just t' -> pure t' unless (status_code >= 200 && status_code < 300) $- throwError $ FailureResponse status ct body+ throwError $ FailureResponse coreResponse return (status_code, body, ct, hdrs, response) --- copied `performRequestCT` from servant-0.7.1, then modified so it takes a variant of `reqToRequest`+-- copied `performRequestCT` from servant-0.11, then modified so it takes a variant of `requestToClientRequest` -- as an argument. performRequestCT' :: MimeUnrender ct result =>- (Req -> BaseUrl -> IO Request) ->- Proxy ct -> Method -> Req+ (Core.Request -> BaseUrl -> IO Request)+ -> Proxy ct -> Method -> Core.Request -> ClientM ([HTTP.Header], result)-performRequestCT' reqToRequest' ct reqMethod req = do- let acceptCT = contentType ct- ClientEnv manager reqHost <- ask+performRequestCT' requestToClientRequest' ct reqMethod req = do+ let acceptCTS = contentTypes ct (_status, respBody, respCT, hdrs, _response) <-- performRequest' reqToRequest' reqMethod (req { reqAccept = [acceptCT] }) manager reqHost- unless (matches respCT acceptCT) $ throwError $ UnsupportedContentType respCT respBody+ performRequest' requestToClientRequest' reqMethod (req { Core.requestAccept = Sequence.fromList $ NonEmpty.toList acceptCTS })+ let coreResponse = clientResponseToReponse _response+ unless (any (matches respCT) acceptCTS) $ throwError $ UnsupportedContentType respCT coreResponse case mimeUnrender ct respBody of- Left err -> throwError $ DecodeFailure err respCT respBody+ Left err -> throwError $ DecodeFailure (pack err) coreResponse Right val -> return (hdrs, val)
src/Web/Telegram/API/Bot/API.hs view
@@ -7,6 +7,7 @@ module API , runClient , runClient'+ , runTelegramClient -- * API , TelegramBotAPI , api@@ -22,9 +23,10 @@ import Web.Telegram.API.Bot.API.Edit as API import Web.Telegram.API.Bot.API.Get as API import Web.Telegram.API.Bot.API.Messages as API+import Web.Telegram.API.Bot.API.Payments as API import Web.Telegram.API.Bot.API.Queries as API+import Web.Telegram.API.Bot.API.Stickers as API import Web.Telegram.API.Bot.API.Updates as API-import Web.Telegram.API.Bot.API.Payments as API type TelegramBotAPI = TelegramBotMessagesAPI@@ -34,6 +36,7 @@ :<|> TelegramBotQueriesAPI :<|> TelegramBotGetAPI :<|> TelegramBotPaymentsAPI+ :<|> TelegramBotStickersAPI -- | Proxy for Thelegram Bot API api :: Proxy TelegramBotAPI
src/Web/Telegram/API/Bot/API/Chats.hs view
@@ -1,15 +1,25 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-} module Web.Telegram.API.Bot.API.Chats ( -- * Functions kickChatMember , kickChatMemberM+ , kickChatMemberUntilM , leaveChat , leaveChatM , unbanChatMember , unbanChatMemberM+ , restrictChatMemberM+ , promoteChatMemberM+ , exportChatInviteLinkM+ , setChatPhotoM+ , deleteChatPhotoM+ , setChatTitleM+ , setChatDescriptionM+ , pinChatMessageM+ , unpinChatMessageM , getChat , getChatM , getChatAdministrators@@ -18,6 +28,8 @@ , getChatMembersCountM , getChatMember , getChatMemberM+ , setChatStickerSetM+ , deleteChatStickerSetM -- * API , TelegramBotChatsAPI , chatsApi@@ -27,8 +39,10 @@ import Data.Text (Text) import Network.HTTP.Client (Manager) import Servant.API-import Servant.Client+import Servant.Client hiding (Response)+import Servant.Client.MultipartFormData import Web.Telegram.API.Bot.API.Core+import Web.Telegram.API.Bot.Requests import Web.Telegram.API.Bot.Responses -- | Telegram Bot API@@ -36,6 +50,7 @@ TelegramToken :> "kickChatMember" :> QueryParam "chat_id" Text :> QueryParam "user_id" Int+ :> QueryParam "until_date" Int :> Post '[JSON] KickChatMemberResponse :<|> TelegramToken :> "leaveChat" :> QueryParam "chat_id" Text@@ -44,6 +59,37 @@ :> QueryParam "chat_id" Text :> QueryParam "user_id" Int :> Post '[JSON] UnbanChatMemberResponse+ :<|> TelegramToken :> "restrictChatMember"+ :> ReqBody '[JSON] RestrictChatMemberRequest+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "promoteChatMember"+ :> ReqBody '[JSON] PromoteChatMemberRequest+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "exportChatInviteLink"+ :> QueryParam "chat_id" Text+ :> Post '[JSON] (Response Text)+ :<|> TelegramToken :> "setChatPhoto"+ :> MultipartFormDataReqBody SetChatPhotoRequest+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "deleteChatPhoto"+ :> QueryParam "chat_id" Text+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "setChatTitle"+ :> QueryParam "chat_id" Text+ :> QueryParam "title" Text+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "setChatDescription"+ :> QueryParam "chat_id" Text+ :> QueryParam "description" Text+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "pinChatMessage"+ :> QueryParam "chat_id" Text+ :> QueryParam "message_id" Int+ :> QueryParam "disable_notification" Bool+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "unpinChatMessage"+ :> QueryParam "chat_id" Text+ :> Post '[JSON] (Response Bool) :<|> TelegramToken :> "getChat" :> QueryParam "chat_id" Text :> Post '[JSON] GetChatResponse@@ -57,34 +103,73 @@ :> QueryParam "chat_id" Text :> QueryParam "user_id" Int :> Post '[JSON] GetChatMemberResponse+ :<|> TelegramToken :> "setChatStickerSet"+ :> QueryParam "chat_id" Text+ :> QueryParam "sticker_set_name" Text+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "deleteChatStickerSet"+ :> QueryParam "chat_id" Text+ :> Post '[JSON] (Response Bool) -- | Proxy for Thelegram Bot API to administrate chats chatsApi :: Proxy TelegramBotChatsAPI chatsApi = Proxy -kickChatMember_ :: Token -> Maybe Text -> Maybe Int -> ClientM KickChatMemberResponse+kickChatMember_ :: Token -> Maybe Text -> Maybe Int -> Maybe Int -> ClientM KickChatMemberResponse leaveChat_ :: Token -> Maybe Text -> ClientM LeaveChatResponse unbanChatMember_ :: Token -> Maybe Text -> Maybe Int -> ClientM UnbanChatMemberResponse+restrictChatMember_ :: Token -> RestrictChatMemberRequest -> ClientM (Response Bool)+promoteChatMember_ :: Token -> PromoteChatMemberRequest -> ClientM (Response Bool)+exportChatInviteLink_ :: Token -> Maybe Text -> ClientM (Response Text)+setChatPhoto_ :: Token -> SetChatPhotoRequest -> ClientM (Response Bool)+deleteChatPhoto_ :: Token -> Maybe Text -> ClientM (Response Bool)+setChatTitle_ :: Token -> Maybe Text -> Maybe Text -> ClientM (Response Bool)+setChatDescription_ :: Token -> Maybe Text -> Maybe Text -> ClientM (Response Bool)+pinChatMessage_ :: Token -> Maybe Text -> Maybe Int -> Maybe Bool -> ClientM (Response Bool)+unpinChatMessage_ :: Token -> Maybe Text -> ClientM (Response Bool) getChat_ :: Token -> Maybe Text -> ClientM GetChatResponse getChatAdministrators_ :: Token -> Maybe Text -> ClientM GetChatAdministratorsResponse getChatMembersCount_ :: Token -> Maybe Text -> ClientM GetChatMembersCountResponse getChatMember_ :: Token -> Maybe Text -> Maybe Int -> ClientM GetChatMemberResponse+setChatStickerSet_ :: Token -> Maybe Text -> Maybe Text -> ClientM (Response Bool)+deleteChatStickerSet_ :: Token -> Maybe Text -> ClientM (Response Bool) kickChatMember_ :<|> leaveChat_ :<|> unbanChatMember_+ :<|> restrictChatMember_+ :<|> promoteChatMember_+ :<|> exportChatInviteLink_+ :<|> setChatPhoto_+ :<|> deleteChatPhoto_+ :<|> setChatTitle_+ :<|> setChatDescription_+ :<|> pinChatMessage_+ :<|> unpinChatMessage_ :<|> getChat_ :<|> getChatAdministrators_ :<|> getChatMembersCount_- :<|> getChatMember_ = client chatsApi+ :<|> getChatMember_+ :<|> setChatStickerSet_+ :<|> deleteChatStickerSet_+ = client chatsApi -- | Use this method to kick a user from a group or a supergroup. In the case of supergroups, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first. The bot must be an administrator in the group for this to work.-kickChatMember :: Token -> Text -> Int -> Manager -> IO (Either ServantError KickChatMemberResponse)+kickChatMember :: Token+ -> Text -- ^ Unique identifier for the target group or username of the target supergroup or channel (in the format @channelusername)+ -> Int -- ^ Unique identifier of the target user+ -> Manager -> IO (Either ServantError KickChatMemberResponse) kickChatMember token chatId userId = runClient (kickChatMemberM chatId userId) token -- | See 'kickChatMember' kickChatMemberM :: Text -> Int -> TelegramClient KickChatMemberResponse-kickChatMemberM chatId userId = asking $ \t -> kickChatMember_ t (Just chatId) (Just userId)+kickChatMemberM chatId userId = asking $ \t -> kickChatMember_ t (Just chatId) (Just userId) Nothing +kickChatMemberUntilM :: Text -- ^ Unique identifier for the target group or username of the target supergroup or channel (in the format @channelusername)+ -> Int -- ^ Unique identifier of the target user+ -> Int -- ^ Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever+ -> TelegramClient KickChatMemberResponse+kickChatMemberUntilM chatId userId untilDate = asking $ \t -> kickChatMember_ t (Just chatId) (Just userId) (Just untilDate)+ -- | Use this method for your bot to leave a group, supergroup or channel. Returns True on success. leaveChat :: Token -> Text -> Manager -> IO (Either ServantError LeaveChatResponse) leaveChat = runM leaveChatM@@ -101,6 +186,42 @@ unbanChatMemberM :: Text -> Int -> TelegramClient UnbanChatMemberResponse unbanChatMemberM chatId userId = asking $ \t -> unbanChatMember_ t (Just chatId) (Just userId) +-- | Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. Returns True on success.+restrictChatMemberM :: RestrictChatMemberRequest -> TelegramClient (Response Bool)+restrictChatMemberM = run_ restrictChatMember_++-- | Use this method to promote or demote a user in a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Pass False for all boolean parameters to demote a user. Returns True on success.+promoteChatMemberM :: PromoteChatMemberRequest -> TelegramClient (Response Bool)+promoteChatMemberM = run_ promoteChatMember_++-- | Use this method to export an invite link to a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns exported invite link as String on success.+exportChatInviteLinkM :: Text -> TelegramClient (Response Text)+exportChatInviteLinkM chatId = run_ exportChatInviteLink_ (Just chatId)++-- | Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns True on success.+setChatPhotoM :: SetChatPhotoRequest -> TelegramClient (Response Bool)+setChatPhotoM = run_ setChatPhoto_++-- | Use this method to delete a chat photo. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns True on success.+deleteChatPhotoM :: Text -> TelegramClient (Response Bool)+deleteChatPhotoM chatId = run_ deleteChatPhoto_ (Just chatId)++-- | Use this method to change the title of a chat. Titles can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns True on success.+setChatTitleM :: Text -> Maybe Text -> TelegramClient (Response Bool)+setChatTitleM chatId title = asking $ \t -> setChatTitle_ t (Just chatId) title++-- | Use this method to change the description of a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns True on success.+setChatDescriptionM :: Text -> Maybe Text -> TelegramClient (Response Bool)+setChatDescriptionM chatId description = asking $ \t -> setChatDescription_ t (Just chatId) description++-- | Use this method to pin a message in a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the 'can_pin_messages' admin right in the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success.+pinChatMessageM :: Text -> Int -> Maybe Bool -> TelegramClient (Response Bool)+pinChatMessageM chatId messageId disableNotifications = asking $ \tkn -> pinChatMessage_ tkn (Just chatId) (Just messageId) disableNotifications++-- | Use this method to unpin a message in a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the 'can_pin_messages' admin right in the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success.+unpinChatMessageM :: Text -> TelegramClient (Response Bool)+unpinChatMessageM chatId = run_ unpinChatMessage_ (Just chatId)+ -- | Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.) getChat :: Token -> Text -> Manager -> IO (Either ServantError GetChatResponse) getChat = runM getChatM@@ -132,3 +253,12 @@ -- | See 'getChatMember' getChatMemberM :: Text -> Int -> TelegramClient GetChatMemberResponse getChatMemberM chatId userId = asking $ \t -> getChatMember_ t (Just chatId) (Just userId)++setChatStickerSetM :: Text -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)+ -> Text -- ^ Name of the sticker set to be set as the group sticker set+ -> TelegramClient (Response Bool)+setChatStickerSetM chatId stickerSetName = asking $ \t -> setChatStickerSet_ t (Just chatId) (Just stickerSetName)++deleteChatStickerSetM :: Text -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)+ -> TelegramClient (Response Bool)+deleteChatStickerSetM chatId = run_ deleteChatStickerSet_ (Just chatId)
src/Web/Telegram/API/Bot/API/Core.hs view
@@ -14,6 +14,7 @@ , asking , runClient , runClient'+ , runTelegramClient , telegramBaseUrl ) where @@ -44,6 +45,10 @@ -- | Runs 'TelegramClient' runClient :: TelegramClient a -> Token -> Manager -> IO (Either ServantError a) runClient tcm token manager = runClient' tcm token (ClientEnv manager telegramBaseUrl)++-- | Runs 'TelegramClient'+runTelegramClient :: Token -> Manager -> TelegramClient a -> IO (Either ServantError a)+runTelegramClient token manager tcm = runClient tcm token manager asking :: Monad m => (t -> m b) -> ReaderT t m b asking op = ask >>= \t -> lift $ op t
src/Web/Telegram/API/Bot/API/Edit.hs view
@@ -16,6 +16,8 @@ , editInlineMessageCaptionM , editInlineMessageReplyMarkup , editInlineMessageReplyMarkupM+ , editMessageLiveLocationM+ , stopMessageLiveLocationM -- * API , TelegramBotEditAPI , editApi@@ -23,9 +25,9 @@ ) where import Data.Proxy-import Network.HTTP.Client (Manager)+import Network.HTTP.Client (Manager) import Servant.API-import Servant.Client+import Servant.Client hiding (Response) import Web.Telegram.API.Bot.API.Core import Web.Telegram.API.Bot.Requests import Web.Telegram.API.Bot.Responses@@ -50,6 +52,12 @@ :<|> TelegramToken :> "editMessageReplyMarkup" :> ReqBody '[JSON] EditMessageReplyMarkupRequest :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "editMessageLiveLocation"+ :> ReqBody '[JSON] EditMessageLiveLocationRequest+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "stopMessageLiveLocation"+ :> ReqBody '[JSON] StopMessageLiveLocationRequest+ :> Post '[JSON] (Response Bool) -- | Proxy for Thelegram Bot API@@ -62,12 +70,16 @@ editMessageText__ :: Token -> EditMessageTextRequest -> ClientM (Response Bool) editMessageCaption__ :: Token -> EditMessageCaptionRequest -> ClientM (Response Bool) editMessageReplyMarkup__ :: Token -> EditMessageReplyMarkupRequest -> ClientM (Response Bool)+editMessageLiveLocation_ :: Token -> EditMessageLiveLocationRequest -> ClientM (Response Bool)+stopMessageLiveLocation_ :: Token -> StopMessageLiveLocationRequest -> ClientM (Response Bool) editMessageText_ :<|> editMessageCaption_ :<|> editMessageReplyMarkup_ :<|> editMessageText__ :<|> editMessageCaption__ :<|> editMessageReplyMarkup__+ :<|> editMessageLiveLocation_+ :<|> stopMessageLiveLocation_ = client editApi -- | Use this method to edit text messages sent by the bot. On success, the edited 'Message' is returned, otherwise True is returned.@@ -116,3 +128,11 @@ -- | See 'editInlineMessageReplyMarkup' editInlineMessageReplyMarkupM :: EditMessageReplyMarkupRequest -> TelegramClient (Response Bool) editInlineMessageReplyMarkupM = run_ editMessageReplyMarkup__++-- | Use this method to edit live location messages sent by the bot or via the bot (for inline bots). A location can be edited until its live_period expires or editing is explicitly disabled by a call to 'stopMessageLiveLocationM'.+editMessageLiveLocationM :: EditMessageLiveLocationRequest -> TelegramClient (Response Bool)+editMessageLiveLocationM = run_ editMessageLiveLocation_++-- | Use this method to stop updating a live location message sent by the bot or via the bot (for inline bots) before live_period expires.+stopMessageLiveLocationM :: StopMessageLiveLocationRequest -> TelegramClient (Response Bool)+stopMessageLiveLocationM = run_ stopMessageLiveLocation_
src/Web/Telegram/API/Bot/API/Messages.hs view
@@ -36,6 +36,7 @@ , uploadVideoNoteM , sendVideoNote , sendVideoNoteM+ , sendMediaGroupM , sendLocation , sendLocationM , sendVenue@@ -56,9 +57,10 @@ import Data.Text (Text) import Network.HTTP.Client (Manager) import Servant.API-import Servant.Client+import Servant.Client hiding (Response) import Servant.Client.MultipartFormData import Web.Telegram.API.Bot.API.Core+import Web.Telegram.API.Bot.Data import Web.Telegram.API.Bot.Requests import Web.Telegram.API.Bot.Responses @@ -112,6 +114,9 @@ :<|> TelegramToken :> "sendVideoNote" :> ReqBody '[JSON] (SendVideoNoteRequest Text) :> Post '[JSON] MessageResponse+ :<|> TelegramToken :> "sendMediaGroup"+ :> ReqBody '[JSON] SendMediaGroupRequest+ :> Post '[JSON] (Response [Message]) :<|> TelegramToken :> "sendLocation" :> ReqBody '[JSON] SendLocationRequest :> Post '[JSON] MessageResponse@@ -148,6 +153,7 @@ sendVoice_ :: Token -> SendVoiceRequest Text -> ClientM MessageResponse uploadVideoNote_ :: Token -> SendVideoNoteRequest FileUpload -> ClientM MessageResponse sendVideoNote_ :: Token -> SendVideoNoteRequest Text -> ClientM MessageResponse+sendMediaGroup_ :: Token -> SendMediaGroupRequest -> ClientM (Response [Message]) sendLocation_ :: Token -> SendLocationRequest -> ClientM MessageResponse sendVenue_ :: Token -> SendVenueRequest-> ClientM MessageResponse sendContact_ :: Token -> SendContactRequest -> ClientM MessageResponse@@ -169,6 +175,7 @@ :<|> sendVoice_ :<|> uploadVideoNote_ :<|> sendVideoNote_+ :<|> sendMediaGroup_ :<|> sendLocation_ :<|> sendVenue_ :<|> sendContact_@@ -315,6 +322,9 @@ -- | See 'sendLocation' sendLocationM :: SendLocationRequest -> TelegramClient MessageResponse sendLocationM = run_ sendLocation_++sendMediaGroupM :: SendMediaGroupRequest -> TelegramClient (Response [Message])+sendMediaGroupM = run_ sendMediaGroup_ -- | Use this method to send information about a venue. On success, the sent 'Message' is returned. sendVenue :: Token -> SendVenueRequest -> Manager -> IO (Either ServantError MessageResponse)
+ src/Web/Telegram/API/Bot/API/Stickers.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}++module Web.Telegram.API.Bot.API.Stickers+ ( -- * Functions+ getStickerSetM+ , uploadStickerFileM+ , createNewStickerSetM+ , createNewStickerSetM'+ , addStickerToSetM+ , uploadStickerToSetM+ , setStickerPositionInSetM+ , deleteStickerFromSetM+ -- * API+ , TelegramBotStickersAPI+ , stickerApi+ -- * Types+ ) where++import Data.Proxy+import Data.Text (Text)+import Servant.API+import Servant.Client hiding (Response)+import Servant.Client.MultipartFormData+import Web.Telegram.API.Bot.API.Core+import Web.Telegram.API.Bot.Data+import Web.Telegram.API.Bot.Requests++import Web.Telegram.API.Bot.Responses (Response)++-- | Telegram Bot API+type TelegramBotStickersAPI =+ TelegramToken :> "getStickerSet"+ :> QueryParam "name" Text+ :> Get '[JSON] (Response StickerSet)+ :<|> TelegramToken :> "uploadStickerFile"+ :> MultipartFormDataReqBody UploadStickerFileRequest+ :> Post '[JSON] (Response File)+ :<|> TelegramToken :> "createNewStickerSet"+ :> ReqBody '[JSON] (CreateNewStickerSetRequest Text)+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "createNewStickerSet"+ :> MultipartFormDataReqBody (CreateNewStickerSetRequest FileUpload)+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "addStickerToSet"+ :> ReqBody '[JSON] (AddStickerToSetRequest Text)+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "addStickerToSet"+ :> MultipartFormDataReqBody (AddStickerToSetRequest FileUpload)+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "setStickerPositionInSet"+ :> QueryParam "sticker" Text+ :> QueryParam "position" Int+ :> Post '[JSON] (Response Bool)+ :<|> TelegramToken :> "deleteStickerFromSet"+ :> QueryParam "sticker" Text+ :> Post '[JSON] (Response Bool)++-- | Proxy for Thelegram Bot API+stickerApi :: Proxy TelegramBotStickersAPI+stickerApi = Proxy++getStickerSet_ :: Token -> Maybe Text -> ClientM (Response StickerSet)+uploadStickerFile_ :: Token -> UploadStickerFileRequest -> ClientM (Response File)+createNewStickerSet_ :: Token -> CreateNewStickerSetRequest Text -> ClientM (Response Bool)+createNewStickerSet_' :: Token -> CreateNewStickerSetRequest FileUpload -> ClientM (Response Bool)+addStickerToSet_ :: Token -> AddStickerToSetRequest Text -> ClientM (Response Bool)+addStickerToSet_' :: Token -> AddStickerToSetRequest FileUpload -> ClientM (Response Bool)+setStickerPositionInSet_ :: Token -> Maybe Text -> Maybe Int -> ClientM (Response Bool)+deleteStickerFromSet_ :: Token -> Maybe Text -> ClientM (Response Bool)+getStickerSet_+ :<|> uploadStickerFile_+ :<|> createNewStickerSet_+ :<|> createNewStickerSet_'+ :<|> addStickerToSet_+ :<|> addStickerToSet_'+ :<|> setStickerPositionInSet_+ :<|> deleteStickerFromSet_+ = client stickerApi++-- | Use this method to get a sticker set.+getStickerSetM :: Text -- ^ Name of the sticker set+ -> TelegramClient (Response StickerSet)+getStickerSetM name = run_ getStickerSet_ $ Just name++-- | Use this method to upload a .png file with a sticker for later use in 'createNewStickerSet' and 'addStickerToSet' methods (can be used multiple times).+uploadStickerFileM :: UploadStickerFileRequest -> TelegramClient (Response File)+uploadStickerFileM = run_ uploadStickerFile_++-- | Use this method to create new sticker set owned by a user. The bot will be able to edit the created sticker set.+createNewStickerSetM :: CreateNewStickerSetRequest Text -> TelegramClient (Response Bool)+createNewStickerSetM = run_ createNewStickerSet_++createNewStickerSetM' :: CreateNewStickerSetRequest FileUpload -> TelegramClient (Response Bool)+createNewStickerSetM' = run_ createNewStickerSet_'++-- | Use this method to add a new sticker to a set created by the bot.+addStickerToSetM :: AddStickerToSetRequest Text -> TelegramClient (Response Bool)+addStickerToSetM = run_ addStickerToSet_++uploadStickerToSetM :: AddStickerToSetRequest FileUpload -> TelegramClient (Response Bool)+uploadStickerToSetM = run_ addStickerToSet_'++-- | Use this method to move a sticker in a set created by the bot to a specific position.+setStickerPositionInSetM :: Text -- ^ File identifier of the sticker+ -> Int -- ^ New sticker position in the set, zero-based+ -> TelegramClient (Response Bool)+setStickerPositionInSetM fileId position = asking $ \t -> setStickerPositionInSet_ t (Just fileId) (Just position)++-- | Use this method to delete a sticker from a set created by the bot.+deleteStickerFromSetM :: Text -- ^ File identifier of the sticker+ -> TelegramClient (Response Bool)+deleteStickerFromSetM fileId = run_ deleteStickerFromSet_ (Just fileId)
src/Web/Telegram/API/Bot/API/Updates.hs view
@@ -25,7 +25,7 @@ import Data.Text (Text, empty) import Network.HTTP.Client (Manager) import Servant.API-import Servant.Client+import Servant.Client hiding (Response) import Servant.Client.MultipartFormData import Web.Telegram.API.Bot.API.Core import Web.Telegram.API.Bot.Requests
src/Web/Telegram/API/Bot/Data.hs view
@@ -8,6 +8,7 @@ User (..) , LanguageCode (..) , ChatMember (..)+ , ChatPhoto (..) , Chat (..) , Message (..) , MessageEntity (..)@@ -47,6 +48,10 @@ , SuccessfulPayment (..) , ShippingQuery (..) , PreCheckoutQuery (..)+ , MaskPositionPoint (..)+ , MaskPosition (..)+ , StickerSet (..)+ , InputMedia (..) -- * Functions , inlineKeyboardButton , keyboardButton@@ -70,7 +75,8 @@ , inlineQueryResultCachedSticker , inlineQueryResultCachedVideo , inlineQueryResultCachedVoice-+ , inputMediaPhoto+ , inputMediaVideo ) where import Prelude hiding (id)@@ -89,6 +95,7 @@ data User = User { user_id :: Int -- ^ Unique identifier for this user or bot+ , user_is_bot :: Bool -- ^ True, if this user is a bot , user_first_name :: Text -- ^ User‘s or bot’s first name , user_last_name :: Maybe Text -- ^ User‘s or bot’s last name , user_username :: Maybe Text -- ^ User‘s or bot’s username@@ -156,6 +163,12 @@ , chat_first_name :: Maybe Text -- ^ First name of the other party in a private chat , chat_last_name :: Maybe Text -- ^ Last name of the other party in a private chat , chat_all_members_are_administrators :: Maybe Bool -- ^ True if a group has ‘All Members Are Admins’ enabled.+ , chat_photo :: Maybe ChatPhoto -- ^ Chat photo. Returned only in 'getChat'.+ , chat_description :: Maybe Text -- ^ Description, for supergroups and channel chats. Returned only in `getChat`.+ , chat_invite_link :: Maybe Text -- ^ Chat invite link, for supergroups and channel chats. Returned only in `getChat`.+ , chat_pinned_message :: Maybe Message -- ^ Pinned message, for supergroups. Returned only in 'getChat'.+ , chat_sticker_set_name :: Maybe Text -- ^ For supergroups, name of group sticker set. Returned only in 'getChat'.+ , chat_can_set_sticker_set :: Maybe Bool -- ^ True, if the bot can change the group sticker set. Returned only in 'getChat'. } deriving (Show, Generic) instance ToJSON Chat where@@ -279,12 +292,14 @@ -- | This object represents a sticker. data Sticker = Sticker {- sticker_file_id :: Text -- ^ Unique identifier for this file- , sticker_width :: Int -- ^ Sticker width- , sticker_height :: Int -- ^ Sticker height- , sticker_thumb :: Maybe PhotoSize -- ^ Sticker thumbnail in .webp or .jpg format- , sticker_emoji :: Maybe Text -- ^ Emoji associated with the sticker- , sticker_file_size :: Maybe Int -- ^ File size+ sticker_file_id :: Text -- ^ Unique identifier for this file+ , sticker_width :: Int -- ^ Sticker width+ , sticker_height :: Int -- ^ Sticker height+ , sticker_thumb :: Maybe PhotoSize -- ^ Sticker thumbnail in .webp or .jpg format+ , sticker_emoji :: Maybe Text -- ^ Emoji associated with the sticker+ , sticker_set_name :: Maybe Text+ , sticker_mask_position :: Maybe MaskPosition+ , sticker_file_size :: Maybe Int -- ^ File size } deriving (Show, Generic) instance ToJSON Sticker where@@ -815,8 +830,22 @@ data ChatMember = ChatMember {- cm_user :: User -- ^ Information about the user- , cm_status :: Text -- ^ The member's status in the chat. Can be “creator”, “administrator”, “member”, “left” or “kicked”+ cm_user :: User -- ^ Information about the user+ , cm_status :: Text -- ^ The member's status in the chat. Can be “creator”, “administrator”, “member”, “left” or “kicked”+ , cm_until_date :: Maybe Integer -- ^ Restictred and kicked only. Date when restrictions will be lifted for this user, unix time+ , cm_can_be_edited :: Maybe Bool -- ^ Administrators only. True, if the bot is allowed to edit administrator privileges of that user+ , cm_can_change_info :: Maybe Bool -- ^ Administrators only. True, if the administrator can change the chat title, photo and other settings+ , cm_can_post_messages :: Maybe Bool -- ^ Administrators only. True, if the administrator can post in the channel, channels only+ , cm_can_edit_messages :: Maybe Bool -- ^ Administrators only. True, if the administrator can edit messages of other users and can pin messages, channels only+ , cm_can_delete_messages :: Maybe Bool -- ^ Administrators only. True, if the administrator can delete messages of other users+ , cm_can_invite_users :: Maybe Bool -- ^ Administrators only. True, if the administrator can invite new users to the chat+ , cm_can_restrict_members :: Maybe Bool -- ^ Administrators only. True, if the administrator can restrict, ban or unban chat members+ , cm_can_pin_messages :: Maybe Bool -- ^ Administrators only. True, if the administrator can pin messages, supergroups only+ , cm_can_promote_members :: Maybe Bool -- ^ Administrators only. True, if the administrator can add new administrators with a subset of his own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by the user)+ , cm_can_send_messages :: Maybe Bool -- ^ Restricted only. True, if the user can send text messages, contacts, locations and venues+ , cm_can_send_media_messages :: Maybe Bool -- ^ Restricted only. True, if the user can send audios, documents, photos, videos, video notes and voice notes, implies can_send_messages+ , cm_can_send_other_messages :: Maybe Bool -- ^ Restricted only. True, if the user can send animations, games, stickers and use inline bots, implies can_send_media_messages+ , cm_can_add_web_page_previews :: Maybe Bool -- ^ Restricted only. True, if user may add web page previews to his messages, implies can_send_media_messages } deriving (Show, Generic) instance ToJSON ChatMember where@@ -825,6 +854,18 @@ instance FromJSON ChatMember where parseJSON = parseJsonDrop 3 +data ChatPhoto = ChatPhoto+ {+ chat_photo_small_file_id :: Text -- ^ Unique file identifier of small (160x160) chat photo. This file_id can be used only for photo download.+ , chat_photo_big_file_id :: Text -- ^ Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download.+ } deriving (Show, Generic)++instance ToJSON ChatPhoto where+ toJSON = toJsonDrop 11++instance FromJSON ChatPhoto where+ parseJSON = parseJsonDrop 11+ -- | This object represents a message. data Message = Message {@@ -835,11 +876,15 @@ , forward_from :: Maybe User -- ^ For forwarded messages, sender of the original message , forward_from_chat :: Maybe Chat -- ^ For messages forwarded from a channel, information about the original channel , forward_from_message_id :: Maybe Int -- ^ For forwarded channel posts, identifier of the original message in the channel+ , forward_signature :: Maybe Text -- ^ For messages forwarded from channels, signature of the post author if present , forward_date :: Maybe Int -- ^ For forwarded messages, date the original message was sent in Unix time , reply_to_message :: Maybe Message -- ^ For replies, the original message. Note that the 'Message' object in this field will not contain further 'reply_to_message' fields even if it itself is a reply. , edit_date :: Maybe Int -- ^ Date the message was last edited in Unix time+ , media_group_id :: Maybe Text -- ^ The unique identifier of a media message group this message belongs to+ , author_signature :: Maybe Text -- ^ Signature of the post author for messages in channels , text :: Maybe Text -- ^ For text messages, the actual UTF-8 text of the message , entities :: Maybe [MessageEntity] -- ^ For text messages, special entities like usernames, URLs, bot commands, etc. that appear in the text+ , caption_entities :: Maybe [MessageEntity] -- ^ or messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear in the caption , audio :: Maybe Audio -- ^ Message is an audio file, information about the file , document :: Maybe Document -- ^ Message is a general file, information about the file , game :: Maybe Game -- ^ Message is a game, information about the game@@ -847,11 +892,13 @@ , sticker :: Maybe Sticker -- ^ Message is a sticker, information about the sticker , video :: Maybe Video -- ^ Message is a video, information about the video , voice :: Maybe Voice -- ^ Message is a voice message, information about the file+ , video_note :: Maybe VideoNote -- ^ Message is a video note, information about the video message , caption :: Maybe Text -- ^ Caption for the photo or video , contact :: Maybe Contact -- ^ Message is a shared contact, information about the contact , location :: Maybe Location -- ^ Message is a shared location, information about the location , venue :: Maybe Venue -- ^ Message is a venue, information about the venue , new_chat_member :: Maybe User -- ^ A new member was added to the group, information about them (this member may be the bot itself)+ , new_chat_members :: Maybe [User] -- ^ New members that were added to the group or supergroup and information about them (the bot itself may be one of these members) , left_chat_member :: Maybe User -- ^ A member was removed from the group, information about them (this member may be the bot itself) , new_chat_title :: Maybe Text -- ^ A chat title was changed to this value , new_chat_photo :: Maybe [PhotoSize] -- ^ A chat photo was change to this value@@ -864,8 +911,6 @@ , pinned_message :: Maybe Message -- ^ Specified message was pinned. Note that the Message object in this field will not contain further reply_to_message fields even if it is itself a reply. , invoice :: Maybe Invoice -- ^ Message is an invoice for a payment, information about the invoice. , successful_payment :: Maybe SuccessfulPayment -- ^ Message is a service message about a successful payment, information about the payment.- , video_note :: Maybe VideoNote -- ^ Message is a video note, information about the video message- , new_chat_members :: Maybe [User] -- ^ New members that were added to the group or supergroup and information about them (the bot itself may be one of these members) } deriving (FromJSON, ToJSON, Show, Generic) -- | This object represents one special entity in a text message. For example, hashtags, usernames, URLs, etc.@@ -1066,3 +1111,90 @@ instance FromJSON PreCheckoutQuery where parseJSON = parseJsonDrop 8++data StickerSet = StickerSet+ {+ stcr_set_name :: Text -- ^ Sticker set name+ , stcr_set_title :: Text -- ^ Sticker set title+ , stcr_set_contains_masks :: Bool -- ^ True, if the sticker set contains masks+ , stcr_set_stickers :: [Sticker] -- ^ List of all set stickers+ } deriving (Show, Generic)++instance ToJSON StickerSet where+ toJSON = toJsonDrop 9++instance FromJSON StickerSet where+ parseJSON = parseJsonDrop 9++data MaskPositionPoint = Forehead+ | Eyes+ | Mouth+ | Chin deriving (Show, Generic)++instance ToJSON MaskPositionPoint where+ toJSON Forehead = "forehead"+ toJSON Eyes = "eyes"+ toJSON Mouth = "mouth"+ toJSON Chin = "chin"++instance FromJSON MaskPositionPoint where+ parseJSON "forehead" = pure Forehead+ parseJSON "eyes" = pure Eyes+ parseJSON "mouth" = pure Mouth+ parseJSON "chin" = pure Chin+ parseJSON _ = fail $ "Failed to parse MaskPositionPoint"++data MaskPosition = MaskPosition+ {+ mask_pos_point :: MaskPositionPoint -- ^ The part of the face relative to which the mask should be placed+ , mask_pos_x_shift :: Float -- ^ Shift by X-axis measured in widths of the mask scaled to the face size, from left to right. For example, choosing -1.0 will place mask just to the left of the default mask position.+ , mask_pos_y_shift :: Float -- ^ Shift by Y-axis measured in heights of the mask scaled to the face size, from top to bottom. For example, 1.0 will place the mask just below the default mask position.+ , mask_pos_scale :: Float -- ^ Mask scaling coefficient. For example, 2.0 means double size.+ } deriving (Show, Generic)++instance ToJSON MaskPosition where+ toJSON = toJsonDrop 9++instance FromJSON MaskPosition where+ parseJSON = parseJsonDrop 9++-- | This object represents the content of a media message to be sent.+data InputMedia =+ InputMediaPhoto+ {+ input_media_media :: Text -- ^ File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet+ , input_media_caption :: Maybe Text -- ^ Caption of the photo to be sent, 0-200 characters+ }+ | InputMediaVideo+ {+ input_media_media :: Text -- ^ File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet+ , input_media_caption :: Maybe Text -- ^ Caption of the video to be sent, 0-200 characters+ , input_media_width :: Maybe Int -- ^ Video width+ , input_media_height :: Maybe Int -- ^ Video height+ , input_media_duration :: Maybe Int -- ^ Video duration+ } deriving (Show, Generic)++inputMediaPhoto :: Text -> InputMedia+inputMediaPhoto media = InputMediaPhoto media Nothing++inputMediaVideo :: Text -> InputMedia+inputMediaVideo media = InputMediaVideo media Nothing Nothing Nothing Nothing++inputMediaTagModifier :: String -> String+inputMediaTagModifier "InputMediaPhoto" = "photo"+inputMediaTagModifier "InputMediaVideo" = "video"+inputMediaTagModifier x = x++inputMediaJSONOptions :: Options+inputMediaJSONOptions = defaultOptions {+ fieldLabelModifier = drop 12+ , omitNothingFields = True+ , sumEncoding = TaggedObject { tagFieldName = "type", contentsFieldName = undefined }+ , constructorTagModifier = inputMediaTagModifier+ }++instance ToJSON InputMedia where+ toJSON = genericToJSON inputMediaJSONOptions++instance FromJSON InputMedia where+ parseJSON = genericParseJSON inputMediaJSONOptions
src/Web/Telegram/API/Bot/Requests.hs view
@@ -19,6 +19,7 @@ , SendVideoRequest (..) , SendVoiceRequest (..) , SendVideoNoteRequest (..)+ , SendMediaGroupRequest (..) , SendLocationRequest (..) , SendVenueRequest (..) , SendContactRequest (..)@@ -34,6 +35,14 @@ , SendInvoiceRequest (..) , AnswerShippingQueryRequest (..) , AnswerPreCheckoutQueryRequest (..)+ , RestrictChatMemberRequest (..)+ , PromoteChatMemberRequest (..)+ , SetChatPhotoRequest (..)+ , UploadStickerFileRequest (..)+ , CreateNewStickerSetRequest (..)+ , AddStickerToSetRequest (..)+ , EditMessageLiveLocationRequest (..)+ , StopMessageLiveLocationRequest (..) -- * Functions , localFileUpload , setWebhookRequest@@ -55,6 +64,7 @@ , uploadVoiceRequest , sendVideoNoteRequest , uploadVideoNoteRequest+ , sendMediaGroupRequest , sendLocationRequest , sendVenueRequest , sendContactRequest@@ -75,6 +85,10 @@ , sendInvoiceRequest , okShippingQueryRequest , errorShippingQueryRequest+ , okAnswerPrecheckoutQueryRequest+ , errorAnswerPrecheckoutQueryRequest+ , restrictChatMemberRequest+ , promoteChatMemberRequest ) where import Data.Aeson@@ -94,8 +108,10 @@ InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResult,+ InputMedia, KeyboardButton,- LabeledPrice, ParseMode,+ LabeledPrice,+ MaskPosition, ParseMode, ShippingOption) import Web.Telegram.API.Bot.JsonExt @@ -453,13 +469,13 @@ data SendVideoNoteRequest payload = SendVideoNoteRequest {- _vid_note_chat_id :: ChatId -- ^ Unique identifier for the target chat or username of the target channel (in the format @channelusername)- , _vid_note_video_note :: payload -- ^ Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. More info on Sending Files ». Sending video notes by a URL is currently unsupported- , _vid_note_duration :: Maybe Int -- ^ Duration of sent video in seconds- , _vid_note_length :: Maybe Int -- ^ Video width and height+ _vid_note_chat_id :: ChatId -- ^ Unique identifier for the target chat or username of the target channel (in the format @channelusername)+ , _vid_note_video_note :: payload -- ^ Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. More info on Sending Files ». Sending video notes by a URL is currently unsupported+ , _vid_note_duration :: Maybe Int -- ^ Duration of sent video in seconds+ , _vid_note_length :: Maybe Int -- ^ Video width and height , _vid_note_disable_notification :: Maybe Bool -- ^ Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound.- , _vid_note_reply_to_message_id :: Maybe Int -- ^ If the message is a reply, ID of the original message- , _vid_note_reply_markup :: Maybe ReplyKeyboard -- ^ Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.+ , _vid_note_reply_to_message_id :: Maybe Int -- ^ If the message is a reply, ID of the original message+ , _vid_note_reply_markup :: Maybe ReplyKeyboard -- ^ Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. } deriving (Show, Generic) instance ToJSON (SendVideoNoteRequest Text) where@@ -492,6 +508,7 @@ location_chat_id :: ChatId -- ^ Unique identifier for the target chat or username of the target channel (in the format @@channelusername@) , location_latitude :: Float -- ^ Latitude of location , location_longitude :: Float -- ^ Longitude of location+ , location_live_period :: Maybe Int -- ^ Period in seconds for which the location will be updated, should be between 60 and 86400. , location_disable_notification :: Maybe Bool -- ^ Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound. , location_reply_to_message_id :: Maybe Int -- ^ If the message is a reply, ID of the original message , location_reply_markup :: Maybe ReplyKeyboard -- ^ Additional interface options. A JSON-serialized object for a custom reply keyboard, instructions to hide keyboard or to force a reply from the user.@@ -504,8 +521,26 @@ parseJSON = parseJsonDrop 9 sendLocationRequest :: ChatId -> Float -> Float -> SendLocationRequest-sendLocationRequest chatId latitude longitude = SendLocationRequest chatId latitude longitude Nothing Nothing Nothing+sendLocationRequest chatId latitude longitude = SendLocationRequest chatId latitude longitude Nothing Nothing Nothing Nothing +-- | This object represents request for 'sendMediaGroup'+data SendMediaGroupRequest = SendMediaGroupRequest+ {+ media_group_chat_id :: ChatId -- ^ Unique identifier for the target chat or username of the target channel (in the format @channelusername)+ , media_group_media :: [InputMedia] -- ^ Array describing photos and videos to be sent, must include 2–10 items+ , media_group_disable_notification :: Maybe Bool -- ^ Sends the messages silently. Users will receive a notification with no sound.+ , media_group_reply_to_message_id :: Maybe Int -- ^ If the messages are a reply, ID of the original message+ } deriving(Show, Generic)++instance ToJSON SendMediaGroupRequest where+ toJSON = toJsonDrop 12++instance FromJSON SendMediaGroupRequest where+ parseJSON = parseJsonDrop 12++sendMediaGroupRequest :: ChatId -> [InputMedia] -> SendMediaGroupRequest+sendMediaGroupRequest chatId inputMediaArray = SendMediaGroupRequest chatId inputMediaArray Nothing Nothing+ -- | This object represents request for 'sendVenue' data SendVenueRequest = SendVenueRequest {@@ -782,26 +817,29 @@ data SendInvoiceRequest = SendInvoiceRequest {- snd_inv_chat_id :: Int64 -- ^ Unique identifier for the target private chat- , snd_inv_title :: Text -- ^ Product name- , snd_inv_description :: Text -- ^ Product description- , snd_inv_payload :: Text -- ^ Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.- , snd_inv_provider_token :: Text -- ^ Payments provider token, obtained via Botfather- , snd_inv_start_parameter :: Text -- ^ Unique deep-linking parameter that can be used to generate this invoice when used as a start parameter- , snd_inv_currency :: CurrencyCode -- ^ Three-letter ISO 4217 <https://core.telegram.org/bots/payments#supported-currencies currency> code- , snd_inv_prices :: [LabeledPrice] -- ^ Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)- , snd_inv_photo_url :: Maybe Text -- ^ URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for.- , snd_inv_photo_size :: Maybe Int -- ^ Photo size- , snd_inv_photo_width :: Maybe Int -- ^ Photo width- , snd_inv_photo_height :: Maybe Int -- ^ Photo height- , snd_inv_need_name :: Maybe Bool -- ^ Pass `True`, if you require the user's full name to complete the order- , snd_inv_need_phone_number :: Maybe Bool -- ^ Pass `True`, if you require the user's phone number to complete the order- , snd_inv_need_email :: Maybe Bool -- ^ Pass `True`, if you require the user's email to complete the order- , snd_inv_need_shipping_address :: Maybe Bool -- ^ Pass `True`, if you require the user's shipping address to complete the order- , snd_inv_is_flexible :: Maybe Bool -- ^ Pass `True`, if the final price depends on the shipping method- , snd_inv_disable_notification :: Maybe Bool -- ^ Sends the message silently. Users will receive a notification with no sound.- , snd_inv_reply_to_message :: Maybe Int -- ^ If the message is a reply, ID of the original message- , snd_inv_reply_markup :: Maybe InlineKeyboardMarkup -- ^ A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button.+ snd_inv_chat_id :: Int64 -- ^ Unique identifier for the target private chat+ , snd_inv_title :: Text -- ^ Product name+ , snd_inv_description :: Text -- ^ Product description+ , snd_inv_payload :: Text -- ^ Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.+ , snd_inv_provider_token :: Text -- ^ Payments provider token, obtained via Botfather+ , snd_inv_start_parameter :: Text -- ^ Unique deep-linking parameter that can be used to generate this invoice when used as a start parameter+ , snd_inv_currency :: CurrencyCode -- ^ Three-letter ISO 4217 <https://core.telegram.org/bots/payments#supported-currencies currency> code+ , snd_inv_prices :: [LabeledPrice] -- ^ Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)+ , snd_inv_provider_data :: Maybe Text -- ^ JSON-encoded data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.+ , snd_inv_photo_url :: Maybe Text -- ^ URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for.+ , snd_inv_photo_size :: Maybe Int -- ^ Photo size+ , snd_inv_photo_width :: Maybe Int -- ^ Photo width+ , snd_inv_photo_height :: Maybe Int -- ^ Photo height+ , snd_inv_need_name :: Maybe Bool -- ^ Pass `True`, if you require the user's full name to complete the order+ , snd_inv_need_phone_number :: Maybe Bool -- ^ Pass `True`, if you require the user's phone number to complete the order+ , snd_inv_need_email :: Maybe Bool -- ^ Pass `True`, if you require the user's email to complete the order+ , snd_inv_need_shipping_address :: Maybe Bool -- ^ Pass `True`, if you require the user's shipping address to complete the order+ , snd_inv_send_phone_number_to_provider :: Maybe Bool -- ^ Pass True, if user's phone number should be sent to provider+ , snd_inv_send_email_to_provider :: Maybe Bool -- ^ Pass True, if user's email address should be sent to provider+ , snd_inv_is_flexible :: Maybe Bool -- ^ Pass `True`, if the final price depends on the shipping method+ , snd_inv_disable_notification :: Maybe Bool -- ^ Sends the message silently. Users will receive a notification with no sound.+ , snd_inv_reply_to_message :: Maybe Int -- ^ If the message is a reply, ID of the original message+ , snd_inv_reply_markup :: Maybe InlineKeyboardMarkup -- ^ A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. } deriving (Show, Generic) instance ToJSON SendInvoiceRequest where@@ -820,7 +858,7 @@ -> [LabeledPrice] -- ^ Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) -> SendInvoiceRequest sendInvoiceRequest chatId title description payload providerToken startParameter currency prices- = SendInvoiceRequest chatId title description payload providerToken startParameter currency prices Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing+ = SendInvoiceRequest chatId title description payload providerToken startParameter currency prices Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing data AnswerShippingQueryRequest -- | If you sent an invoice requesting a shipping address and the parameter is_flexible was specified, the Bot API will send an Update with a shipping_query field to the bot. Use this method to reply to shipping queries. On success, True is returned.@@ -858,6 +896,182 @@ instance FromJSON AnswerPreCheckoutQueryRequest where parseJSON = parseJsonDrop 4++okAnswerPrecheckoutQueryRequest :: Text -> AnswerPreCheckoutQueryRequest+okAnswerPrecheckoutQueryRequest queryId = AnswerPreCheckoutQueryRequest queryId True Nothing++errorAnswerPrecheckoutQueryRequest :: Text -> Text -> AnswerPreCheckoutQueryRequest+errorAnswerPrecheckoutQueryRequest queryId errorMessage = AnswerPreCheckoutQueryRequest queryId False $ Just errorMessage++data RestrictChatMemberRequest = RestrictChatMemberRequest+ {+ rcm_chat_id :: ChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)+ , rcm_user_id :: Int -- ^ Unique identifier of the target user+ , rcm_until_date :: Maybe Int -- ^ Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever+ , rcm_can_send_messages :: Maybe Bool -- ^ Pass True, if the user can send text messages, contacts, locations and venues+ , rcm_can_send_media_messages :: Maybe Bool -- ^ Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, implies can_send_messages+ , rcm_can_send_other_messages :: Maybe Bool -- ^ Pass True, if the user can send animations, games, stickers and use inline bots, implies can_send_media_messages+ , rcm_can_add_web_page_previews :: Maybe Bool -- ^ Pass True, if the user may add web page previews to their messages, implies can_send_media_messages+ } deriving (Show, Generic)++instance ToJSON RestrictChatMemberRequest where+ toJSON = toJsonDrop 4++instance FromJSON RestrictChatMemberRequest where+ parseJSON = parseJsonDrop 4++restrictChatMemberRequest :: ChatId -> Int -> RestrictChatMemberRequest+restrictChatMemberRequest chatId userId = RestrictChatMemberRequest chatId userId Nothing Nothing Nothing Nothing Nothing++data PromoteChatMemberRequest = PromoteChatMemberRequest+ {+ pcmr_chat_id :: ChatId -- ^ Unique identifier for the target chat or username of the target channel (in the format @channelusername)+ , pcmr_user_id :: Int -- ^ Unique identifier of the target user+ , pcmr_can_change_info :: Maybe Bool -- ^ Pass True, if the administrator can change chat title, photo and other settings+ , pcmr_can_post_messages :: Maybe Bool -- ^ Pass True, if the administrator can create channel posts, channels only+ , pcmr_can_edit_messages :: Maybe Bool -- ^ Pass True, if the administrator can edit messages of other users and can pin messages, channels only+ , pcmr_can_delete_messages :: Maybe Bool -- ^ Pass True, if the administrator can delete messages of other users+ , pcmr_can_invite_users :: Maybe Bool -- ^ Pass True, if the administrator can invite new users to the chat+ , pcmr_can_restrict_members :: Maybe Bool -- ^ Pass True, if the administrator can restrict, ban or unban chat members+ , pcmr_can_pin_messages :: Maybe Bool -- ^ Pass True, if the administrator can pin messages, supergroups only+ , pcmr_can_promote_members :: Maybe Bool -- ^ Pass True, if the administrator can add new administrators with a subset of his own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him)+ } deriving (Show, Generic)++instance ToJSON PromoteChatMemberRequest where+ toJSON = toJsonDrop 5++instance FromJSON PromoteChatMemberRequest where+ parseJSON = parseJsonDrop 5++promoteChatMemberRequest :: ChatId -> Int -> PromoteChatMemberRequest+promoteChatMemberRequest chatId userId = PromoteChatMemberRequest chatId userId Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing++data SetChatPhotoRequest = SetChatPhotoRequest+ {+ scp_chat_id :: ChatId+ , scp_photo :: FileUpload+ }++instance ToMultipartFormData SetChatPhotoRequest where+ toMultipartFormData req =+ [ utf8Part "chat_id" (chatIdToPart $ scp_chat_id req)+ , fileUploadToPart "photo" (scp_photo req) ]++data UploadStickerFileRequest = UploadStickerFileRequest+ {+ upload_sticker_user_id :: Int -- ^ User identifier of sticker file owner+ , upload_sticker_png_sticker :: FileUpload -- ^ Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px.+ }++instance ToMultipartFormData UploadStickerFileRequest where+ toMultipartFormData req =+ [ utf8Part "user_id" (tshow $ upload_sticker_user_id req)+ , fileUploadToPart "png_sticker" (upload_sticker_png_sticker req) ]++data CreateNewStickerSetRequest payload = CreateNewStickerSetRequest+ {+ new_sticker_set_user_id :: Int -- ^ User identifier of created sticker set owner+ , new_sticker_set_name :: Text -- ^ Short name of sticker set, to be used in t.me/addstickers/ URLs (e.g., animals). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in “_by_<bot username>”. <bot_username> is case insensitive. 1-64 characters.+ , new_sticker_set_title :: Text -- ^ Sticker set title, 1-64 characters+ , new_sticker_set_png_sticker :: payload -- ^ Yes Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More info on Sending Files »+ , new_sticker_set_emojis :: Text -- ^ One or more emoji corresponding to the sticker+ , new_sticker_set_contains_masks :: Maybe Bool -- ^ Pass True, if a set of mask stickers should be created+ , new_sticker_set_mask_position :: Maybe MaskPosition -- ^ A JSON-serialized object for position where the mask should be placed on faces+ } deriving (Show, Generic)++instance ToJSON (CreateNewStickerSetRequest Text) where+ toJSON = toJsonDrop 16++instance FromJSON (CreateNewStickerSetRequest Text) where+ parseJSON = parseJsonDrop 16++instance ToMultipartFormData (CreateNewStickerSetRequest FileUpload) where+ toMultipartFormData req =+ [ utf8Part "user_id" (tshow $ new_sticker_set_user_id req)+ , utf8Part "name" $ new_sticker_set_name req+ , utf8Part "title" $ new_sticker_set_title req+ , utf8Part "emojis" $ new_sticker_set_emojis req+ , fileUploadToPart "png_sticker" (new_sticker_set_png_sticker req) ] +++ catMaybes+ [ partLBS "contains_masks" . encode <$> new_sticker_set_contains_masks req+ , partLBS "mask_position" . encode <$> new_sticker_set_mask_position req ]++data AddStickerToSetRequest payload = AddStickerToSetRequest+ {+ add_sticker_to_set_user_id :: Int -- ^ User identifier of created sticker set owner+ , add_sticker_to_set_name :: Text -- ^ Short name of sticker set, to be used in t.me/addstickers/ URLs (e.g., animals). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in “_by_<bot username>”. <bot_username> is case insensitive. 1-64 characters.+ , add_sticker_to_set_png_sticker :: payload -- ^ Yes Png image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More info on Sending Files »+ , add_sticker_to_set_emojis :: Text -- ^ One or more emoji corresponding to the sticker+ , add_sticker_to_set_mask_position :: Maybe MaskPosition -- ^ A JSON-serialized object for position where the mask should be placed on faces+ } deriving (Show, Generic)++instance ToJSON (AddStickerToSetRequest Text) where+ toJSON = toJsonDrop 19++instance FromJSON (AddStickerToSetRequest Text) where+ parseJSON = parseJsonDrop 19++instance ToMultipartFormData (AddStickerToSetRequest FileUpload) where+ toMultipartFormData req =+ [ utf8Part "user_id" (tshow $ add_sticker_to_set_user_id req)+ , utf8Part "name" $ add_sticker_to_set_name req+ , utf8Part "emojis" $ add_sticker_to_set_emojis req+ , fileUploadToPart "png_sticker" (add_sticker_to_set_png_sticker req) ] +++ catMaybes+ [ partLBS "mask_position" . encode <$> add_sticker_to_set_mask_position req+ ]++data EditMessageLiveLocationRequest =+ EditMessageLiveLocationRequest+ {+ edit_live_loc_chat_id :: Text -- ^ Unique identifier for the target chat or username of the target channel (in the format @channelusername)+ , edit_live_loc_latitude :: Float -- ^ Latitude of new location+ , edit_live_loc_longitude :: Float -- ^ Longitude of new location+ , edit_live_loc_reply_markup :: Maybe InlineKeyboardMarkup -- ^ An object for a new inline keyboard.+ }+ | EditMessageLiveLocationMessageRequest+ {+ edit_live_loc_message_id :: Int -- ^ Identifier of the sent message+ , edit_live_loc_latitude :: Float -- ^ Latitude of new location+ , edit_live_loc_longitude :: Float -- ^ Longitude of new location+ , edit_live_loc_reply_markup :: Maybe InlineKeyboardMarkup -- ^ An object for a new inline keyboard.+ }+ | EditMessageLiveLocationInlineMessageRequest+ {+ edit_live_loc_inline_message_id :: Text -- ^ Identifier of the inline message+ , edit_live_loc_latitude :: Float -- ^ Latitude of new location+ , edit_live_loc_longitude :: Float -- ^ Longitude of new location+ , edit_live_loc_reply_markup :: Maybe InlineKeyboardMarkup -- ^ An object for a new inline keyboard.+ } deriving (Show, Generic)++instance ToJSON EditMessageLiveLocationRequest where+ toJSON = toJsonDrop 14++instance FromJSON EditMessageLiveLocationRequest where+ parseJSON = parseJsonDrop 14++data StopMessageLiveLocationRequest =+ StopMessageLiveLocationRequest+ {+ stop_live_loc_chat_id :: Text -- ^ Unique identifier for the target chat or username of the target channel (in the format @channelusername)+ , stop_live_loc_reply_markup :: Maybe InlineKeyboardMarkup -- ^ An object for a new inline keyboard.+ }+ | StopMessageLiveLocationMessageRequest+ {+ stop_live_loc_message_id :: Int -- ^ Identifier of the sent message+ , stop_live_loc_reply_markup :: Maybe InlineKeyboardMarkup -- ^ An object for a new inline keyboard.+ }+ | StopMessageLiveLocationInlineMessageRequest+ {+ stop_live_loc_inline_message_id :: Text -- ^ Identifier of the inline message+ , stop_live_loc_reply_markup :: Maybe InlineKeyboardMarkup -- ^ An object for a new inline keyboard.+ } deriving (Show, Generic)++instance ToJSON StopMessageLiveLocationRequest where+ toJSON = toJsonDrop 14++instance FromJSON StopMessageLiveLocationRequest where+ parseJSON = parseJsonDrop 14 tshow :: Show a => a -> Text tshow = T.pack . show
telegram-api.cabal view
@@ -1,5 +1,5 @@ name: telegram-api-version: 0.6.3.0+version: 0.7.2.0 synopsis: Telegram Bot API bindings description: High-level bindings to the Telegram Bot API homepage: http://github.com/klappvisor/haskell-telegram-api#readme@@ -20,6 +20,8 @@ , test-data/Possible_PDM_signal_labeled_as_Sputnik_by_NASA.ogg , test-data/concerto-for-2-trumpets-in-c-major.mp3 , test-data/wikipedia-telegram.txt+ , test-data/sticker_1.png+ , test-data/sticker_2.png extra-source-files: README.md@@ -36,6 +38,7 @@ , Web.Telegram.API.Bot.API.Updates , Web.Telegram.API.Bot.API.Chats , Web.Telegram.API.Bot.API.Payments+ , Web.Telegram.API.Bot.API.Stickers , Web.Telegram.API.Bot.Data , Web.Telegram.API.Bot.Responses , Web.Telegram.API.Bot.Requests@@ -44,10 +47,12 @@ , Servant.Client.MultipartFormData build-depends: base >= 4.7 && < 5 , aeson >= 1.0 && < 1.3+ , containers >= 0.5 && < 0.6 , http-api-data , http-client >= 0.5 && < 0.6- , servant >= 0.9 && <0.11- , servant-client >= 0.9 && <0.11+ , servant >= 0.12 && < 0.13+ , servant-client >= 0.12 && < 0.13+ , servant-client-core >= 0.12 && < 0.13 , mtl >= 2.2 && < 2.3 , text , transformers@@ -68,6 +73,8 @@ , PaymentsSpec , JsonSpec , UpdatesSpec+ , StickersSpec+ , TestCore build-depends: base , aeson >= 1.0 && < 1.3 , hjpath@@ -77,14 +84,16 @@ , http-types , hspec , optparse-applicative- , servant >= 0.9 && <0.11- , servant-client >= 0.9 && <0.11+ , servant >= 0.12 && <0.13+ , servant-client >= 0.12 && <0.13+ , servant-client-core >= 0.12 && < 0.13 , telegram-api , http-types , filepath , text , transformers , utf8-string+ , random ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fno-warn-name-shadowing default-language: Haskell2010
+ test-data/sticker_1.png view
binary file changed (absent → 410716 bytes)
+ test-data/sticker_2.png view
binary file changed (absent → 31313 bytes)
test/InlineSpec.hs view
@@ -48,6 +48,6 @@ inline_article = InlineQueryResultArticle "2131341" (Just "text article content") (Just message_content) Nothing Nothing Nothing Nothing Nothing Nothing Nothing inline_photo = InlineQueryResultPhoto "1430810" "http://vignette3.wikia.nocookie.net/victorious/images/f/f8/NyanCat.jpg" (Just "http://vignette3.wikia.nocookie.net/victorious/images/f/f8/NyanCat.jpg") Nothing Nothing Nothing Nothing Nothing Nothing Nothing-inline_gif = InlineQueryResultGif "131231234" "https://media.giphy.com/media/zEO5eq3ZsEwbS/giphy.gif" Nothing Nothing (Just "https://media.giphy.com/media/zEO5eq3ZsEwbS/100.gif") Nothing Nothing Nothing Nothing-inline_mpeg = InlineQueryResultMpeg4Gif "131251234" "https://media.giphy.com/media/zEO5eq3ZsEwbS/giphy.gif" Nothing Nothing (Just "https://media.giphy.com/media/zEO5eq3ZsEwbS/100.gif") Nothing Nothing Nothing Nothing+inline_gif = InlineQueryResultGif "131231234" "https://media.giphy.com/media/zEO5eq3ZsEwbS/giphy.gif" Nothing Nothing (Just "https://media.giphy.com/media/zEO5eq3ZsEwbS/100.gif") Nothing Nothing Nothing Nothing Nothing+inline_mpeg = InlineQueryResultMpeg4Gif "131251234" "https://media.giphy.com/media/zEO5eq3ZsEwbS/giphy.gif" Nothing Nothing (Just "https://media.giphy.com/media/zEO5eq3ZsEwbS/100.gif") Nothing Nothing Nothing Nothing Nothing inline_video = InlineQueryResultVideo "123413542" "https://www.youtube.com/embed/TBKN7_vx2xo" "text/html" (Just "https://i.ytimg.com/vi_webp/TBKN7_vx2xo/mqdefault.webp") (Just "Enjoykin — Nyash Myash") Nothing Nothing Nothing Nothing Nothing Nothing Nothing
test/MainSpec.hs view
@@ -7,25 +7,20 @@ import Control.Concurrent import Control.Monad-import Data.Either (isLeft, isRight) import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Network.HTTP.Client (newManager) import Network.HTTP.Client.TLS (tlsManagerSettings) import Network.HTTP.Types.Status-import Servant.Client+import Paths_telegram_api+import Servant.Client hiding (Response)+import qualified Servant.Client.Core as Core import System.FilePath import Test.Hspec+import TestCore import Web.Telegram.API.Bot -import Paths_telegram_api---- to print out remote response if response success not match-success, nosuccess :: (Show a, Show b) => Either a b -> Expectation-success e = e `shouldSatisfy` isRight-nosuccess e = e `shouldSatisfy` isLeft- spec :: Token -> ChatId -> Text -> Spec spec token chatId botName = do manager <- runIO $ newManager tlsManagerSettings@@ -47,7 +42,7 @@ it "should return error message" $ do res <- sendMessage token (sendMessageRequest (ChatChannel "") "test message") manager nosuccess res- let Left FailureResponse { responseStatus = Status { statusMessage = msg } } = res+ let Left (FailureResponse Core.Response { responseStatusCode = Status { statusMessage = msg } }) = res msg `shouldBe` "Bad Request" it "should send message markdown" $ do@@ -105,7 +100,7 @@ it "should forward message" $ do res <- forwardMessage token (forwardMessageRequest chatId chatId 123000) manager nosuccess res- let Left FailureResponse { responseStatus = Status { statusMessage = msg } } = res+ let Left (FailureResponse Core.Response { responseStatusCode = Status { statusMessage = msg } }) = res msg `shouldBe` "Bad Request" describe "/sendPhoto" $ do@@ -113,10 +108,10 @@ let photo = (sendPhotoRequest (ChatChannel "") "photo_id") { photo_caption = Just "photo caption" }- Left FailureResponse { responseStatus = Status { statusMessage = msg } } <- sendPhoto token photo manager+ Left (FailureResponse Core.Response { responseStatusCode = Status { statusMessage = msg } }) <- sendPhoto token photo manager msg `shouldBe` "Bad Request" it "should upload photo and resend it by id" $ do- let fileUpload = localFileUpload (testFile "christmas-cat.jpg")+ let fileUpload = localFileUpload $ testFile "christmas-cat.jpg" let upload = (uploadPhotoRequest chatId fileUpload) { photo_caption = Just "uploaded photo" }@@ -138,24 +133,25 @@ _audio_performer = Just "performer" , _audio_title = Just "title" }- Left FailureResponse { responseStatus = Status { statusMessage = msg } } <-+ Left (FailureResponse Core.Response { responseStatusCode = Status { statusMessage = msg } }) <- sendAudio token audio manager msg `shouldBe` "Bad Request" it "should upload audio and resend it by id" $ do- let fileUpload = localFileUpload (testFile "concerto-for-2-trumpets-in-c-major.mp3")+ let fileUpload = localFileUpload $ testFile "concerto-for-2-trumpets-in-c-major.mp3" audioTitle = "Concerto for 2 Trumpets in C major, RV. 537 (Rondeau arr.) All." audioPerformer = "Michel Rondeau" audio = (uploadAudioRequest chatId fileUpload) { _audio_performer = Just audioPerformer, _audio_title = Just audioTitle }- Right Response {+ res <- uploadAudio token audio manager+ let Right Response { result = Message { audio = Just Audio { audio_file_id = file_id, audio_title = Just title, audio_performer = Just performer } }- } <- uploadAudio token audio manager+ } = res title `shouldBe` audioTitle performer `shouldBe` audioPerformer let audio = sendAudioRequest chatId file_id@@ -170,19 +166,21 @@ sendSticker token sticker manager sticker_file_id sticker `shouldBe` "CAADAgADGgADkWgMAAGXlYGBiM_d2wI" --"BQADAgADGgADkWgMAAGXlYGBiM_d2wI" it "should upload sticker" $ do- let fileUpload = localFileUpload (testFile "haskell-logo.webp")+ let fileUpload = localFileUpload $ testFile "haskell-logo.webp" stickerReq = uploadStickerRequest chatId fileUpload- Right Response { result = Message { sticker = Just sticker } } <-- uploadSticker token stickerReq manager+ res <- uploadSticker token stickerReq manager+ success res+ let Right Response { result = Message { sticker = Just sticker } } = res sticker_height sticker `shouldBe` 128 describe "/sendVoice" $ it "should upload voice" $ do -- audio source: https://commons.wikimedia.org/wiki/File:Possible_PDM_signal_labeled_as_Sputnik_by_NASA.ogg- let fileUpload = localFileUpload (testFile "Possible_PDM_signal_labeled_as_Sputnik_by_NASA.ogg")+ let fileUpload = localFileUpload $ testFile "Possible_PDM_signal_labeled_as_Sputnik_by_NASA.ogg" voiceReq = (uploadVoiceRequest chatId fileUpload) { _voice_duration = Just 10 }- Right Response { result = Message { voice = Just voice } } <-- uploadVoice token voiceReq manager+ res <- uploadVoice token voiceReq manager+ success res+ let Right Response { result = Message { voice = Just voice } } = res voice_duration voice `shouldBe` 10 describe "/sendVideoNote" $ it "should upload video note" $ do@@ -197,15 +195,17 @@ describe "/sendVideo" $ it "should upload video" $ do -- video source: http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5- let fileUpload = localFileUpload (testFile "lego-video.mp4")+ let fileUpload = localFileUpload $ testFile "lego-video.mp4" videoReq = uploadVideoRequest chatId fileUpload- Right Response { result = Message { video = Just video } } <-- uploadVideo token videoReq manager+ res <- uploadVideo token videoReq manager+ success res+ let Right Response { result = Message { video = Just video } } = res+ video_width video `shouldBe` 560 describe "/sendDocument" $ it "should upload document" $ do- let fileUpload = localFileUpload (testFile "wikipedia-telegram.txt")+ let fileUpload = localFileUpload $ testFile "wikipedia-telegram.txt" documentReq = uploadDocumentRequest chatId fileUpload Right Response { result = Message { document = Just document } } <- uploadDocument token documentReq manager@@ -265,7 +265,7 @@ fmap (T.take 10) (file_path file) `shouldBe` Just "thumbnails" it "should return error" $ do- Left FailureResponse { responseStatus = Status { statusMessage = msg } } <-+ Left (FailureResponse Core.Response { responseStatusCode = Status { statusMessage = msg } }) <- getFile token "AAQEABMXDZEwAARC0Kj3twkzNcMkAmm" manager msg `shouldBe` "Bad Request" @@ -329,5 +329,19 @@ Right Response { result = Message { caption = Just cpt' } } <- editMessageCaption token editRequest manager cpt' `shouldBe` "edited cat picture"++ describe "/sendMediaGroup" $ do+ it "should send all media in group" $ do+ let photo1 = (inputMediaPhoto "http://s2.quickmeme.com/img/c9/c94711e0f933eb488e0cb0baa9d3eff1888a27ead4fd6089fd37d8f7d8f45a97.jpg") {+ input_media_caption = Just "meme"+ }+ photo2 = (inputMediaPhoto "http://adit.io/imgs/lenses/go_deeper.png") {+ input_media_caption = Just "Lenses"+ }+ request = sendMediaGroupRequest chatId [ photo1, photo2 ]+ res <- runTelegramClient token manager $ sendMediaGroupM request+ success res+ let Right Response { result = messages } = res+ length messages `shouldBe` 2 -- it "should edit caption" $ do ... after inline query tests are on place
test/PaymentsSpec.hs view
@@ -5,23 +5,17 @@ module PaymentsSpec (spec) where -import Data.Either (isLeft, isRight)-import qualified Data.Text as T-import Data.Text (Text)-import Network.HTTP.Client (newManager)-import Network.HTTP.Client.TLS (tlsManagerSettings)+import Data.Text (Text)+import Network.HTTP.Client (newManager)+import Network.HTTP.Client.TLS (tlsManagerSettings) import Test.Hspec+import TestCore import Web.Telegram.API.Bot --- to print out remote response if response success not match-success, nosuccess :: (Show a, Show b) => Either a b -> Expectation-success e = e `shouldSatisfy` isRight-nosuccess e = e `shouldSatisfy` isLeft- spec :: Token -> ChatId -> Text -> Text -> Spec-spec token (ChatId chatId) _ paymentToken = do+spec token chatId' _ paymentToken = do manager <- runIO $ newManager tlsManagerSettings-+ let ChatId chatId = chatId' describe "/sendInvoice" $ do it "should send invoice" $ do let description = "The best portal cannon in known universe"
test/Spec.hs view
@@ -12,6 +12,7 @@ import qualified MainSpec import Options.Applicative import qualified PaymentsSpec+import qualified StickersSpec import System.Environment (lookupEnv, withArgs) import Test.Hspec import qualified Text.PrettyPrint.ANSI.Leijen as PP@@ -75,6 +76,7 @@ describe "Main integration tests" $ MainSpec.spec token chatId botName describe "Payments integration tests" $ PaymentsSpec.spec token chatId botName paymentToken describe "Updates API spec" $ UpdatesSpec.spec token botName+ describe "Stickers API spec" $ StickersSpec.spec token chatId botName --describe "Inline integration tests" $ InlineSpec.spec token chatId botName runIntegrationSpec _ _ _ _ = describe "Integration tests" $ fail "Missing required arguments for integration tests. Run stack test --test-arguments \"--help\" for more info"
+ test/StickersSpec.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}++module StickersSpec (spec) where++import Data.Maybe+import Data.Monoid+import Data.Text (Text)+import qualified Data.Text as T+import Network.HTTP.Client (newManager)+import Network.HTTP.Client.TLS (tlsManagerSettings)+import Paths_telegram_api+import System.FilePath+import System.Random+import Test.Hspec+import TestCore+import Web.Telegram.API.Bot++spec :: Token -> ChatId -> Text -> Spec+spec token chatId _ = do+ manager <- runIO $ newManager tlsManagerSettings+ dataDir <- runIO getDataDir++ res <- runIO $ runTelegramClient token manager getMeM+ let testFile name = dataDir </> "test-data" </> name+ Right Response { result = meRes } = res+ botUsername = fromMaybe "???" $ user_username meRes+ ChatId chatId' = chatId+ userId :: Int = fromIntegral chatId'+ stickerFile1 = localFileUpload $ testFile "sticker_1.png"+ stickerFile2 = localFileUpload $ testFile "sticker_2.png"++ describe "/getStickerSet" $ do+ it "should get sticker set" $ do+ let stickerName = "non_existing_test_set_by_" <> botUsername+ res <- runTelegramClient token manager $ getStickerSetM stickerName+ nosuccess res++ describe "/uploadStickerFile" $ do+ it "should upload sticker PNG" $ do+ let uploadRequest = UploadStickerFileRequest userId stickerFile1+ Right res <- runTelegramClient token manager $ uploadStickerFileM uploadRequest+ (T.null . file_id . result) res `shouldBe` False++ describe "/createNewStickerSet" $ do+ it "should create sticker set" $ do+ rnd :: Integer <- randomRIO (10000, 99999)+ let stickerSetName = "set_" <> (showText rnd) <> "_by_" <> botUsername+ request = CreateNewStickerSetRequest userId stickerSetName "Haskell Bot API Test Set" stickerFile1 "😃" (Just True) Nothing+ res <- runTelegramClient token manager $ do+ _ <- createNewStickerSetM' request+ getStickerSetM stickerSetName+ success res+ let Right Response { result = set } = res+ stcr_set_name set `shouldBe` stickerSetName++ describe "StickerSet CRUD" $ do+ let setTitle = "Haskell Telegram Bot API Test"+ setName = "sticker_set_by_" <> botUsername+ runIO $ print setName+ _ <- runIO $ runTelegramClient token manager $ do+ uploadResult <- uploadStickerFileM $ UploadStickerFileRequest userId stickerFile2+ let fileId = (file_id . result) uploadResult+ request = CreateNewStickerSetRequest userId setName setTitle fileId "👍" (Just True) Nothing+ createNewStickerSetM request+ it "should add new sticker to the sicker set" $ do+ Right (set, setAfter) <- runTelegramClient token manager $ do+ let maskPosition = MaskPosition Eyes 0.0 0.0 0.0+ uploadRequest = AddStickerToSetRequest userId setName stickerFile1 "😃" (Just maskPosition)+ _ <- uploadStickerToSetM uploadRequest+ Response { result = set } <- getStickerSetM setName+ let firstId = sticker_file_id . head . stcr_set_stickers+ _ <- sendStickerM $ sendStickerRequest chatId $ firstId set+ let lastId = sticker_file_id . last . stcr_set_stickers+ _ <- deleteStickerFromSetM $ lastId set+ Response { result = setAfter } <- getStickerSetM setName+ return (set, setAfter)+ stcr_set_contains_masks set `shouldBe` True+ let stickerCount = length . stcr_set_stickers+ stickerCount set `shouldBe` 2+ stickerCount setAfter `shouldBe` 1+++
+ test/TestCore.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}++module TestCore (success, nosuccess, showText) where++import Data.Either (isLeft, isRight)+import Data.Text (Text)+import qualified Data.Text as T+import Test.Hspec++-- to print out remote response if response success not match+success, nosuccess :: (Show a, Show b) => Either a b -> Expectation+success e = e `shouldSatisfy` isRight+nosuccess e = e `shouldSatisfy` isLeft++showText :: (Show a) => a -> Text+showText = T.pack . show
test/UpdatesSpec.hs view
@@ -8,28 +8,28 @@ import Control.Concurrent import Control.Monad.IO.Class import Data.Text (Text)+import qualified Data.Text as T import Network.HTTP.Client (newManager) import Network.HTTP.Client.TLS (tlsManagerSettings) import Test.Hspec import Web.Telegram.API.Bot-import qualified Data.Text as T spec :: Token -> Text -> Spec spec token botName = do manager <- runIO $ newManager tlsManagerSettings describe "/getMe and /getWebhookInfo work together" $ it "responds with correct bot's name and empty webhook" $ do- res <- runClient ( do+ res <- runTelegramClient token manager $ do b <- getMeM info <- getWebhookInfoM- return (b, info)) token manager+ return (b, info) let Right (Response {result = me}, Response {result = whi}) = res user_first_name me `shouldBe` botName whi_url whi `shouldBe` "" describe "webhook operations" $ do let url = "https://example.com/bot" it "able to get and set webhook" $ do- res <- runClient ( do+ res <- runTelegramClient token manager $ do info <- getWebhookInfoM liftIO $ (whi_url . result) info `shouldBe` "" liftIO $ threadDelay $ 2 * 1000 * 1000 -- to avoid Too many request error@@ -40,32 +40,31 @@ liftIO $ threadDelay $ 2 * 1000 * 1000 -- to avoid Too many request error del <- deleteWebhookM liftIO $ result del `shouldBe` True- getWebhookInfoM ) token manager+ getWebhookInfoM let Right Response { result = whi } = res whi_url whi `shouldBe` "" it "should set allowed updates" $ do let allowedUpdates = map T.pack ["message", "callback_query"]- res <- runClient ( do+ res <- runTelegramClient token manager $ do let request = (setWebhookRequest' url) { webhook_allowed_updates = Just allowedUpdates } set <- setWebhookM request liftIO $ result set `shouldBe` True- getWebhookInfoM ) token manager+ getWebhookInfoM let Right Response { result = whi } = res whi_allowed_updates whi `shouldBe` Just allowedUpdates it "should set max connections" $ do- res <- runClient ( do-+ res <- runTelegramClient token manager $ do let request = (setWebhookRequest' url) { webhook_max_connections = Just 5 } liftIO $ threadDelay $ 2 * 1000 * 1000 -- to avoid Too many request error set <- setWebhookM request liftIO $ result set `shouldBe` True- getWebhookInfoM ) token manager+ getWebhookInfoM let Right Response { result = whi } = res whi_max_connections whi `shouldBe` Just 5