From 9c55a4bb2cf10e3f6c1543edc625b4a3bb57449c Mon Sep 17 00:00:00 2001 From: Andrei Borzenkov Date: Wed, 15 May 2024 12:48:51 +0400 Subject: [PATCH] Ignore updates that produce parse failures, allowing bots to partially work after API changes on the side of telegram --- .../src/Telegram/Bot/API/GettingUpdates.hs | 16 +++++++++++++--- .../src/Telegram/Bot/Simple/BotApp/Internal.hs | 18 +++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/telegram-bot-api/src/Telegram/Bot/API/GettingUpdates.hs b/telegram-bot-api/src/Telegram/Bot/API/GettingUpdates.hs index 629c80b..041365e 100644 --- a/telegram-bot-api/src/Telegram/Bot/API/GettingUpdates.hs +++ b/telegram-bot-api/src/Telegram/Bot/API/GettingUpdates.hs @@ -6,7 +6,7 @@ {-# LANGUAGE TypeOperators #-} module Telegram.Bot.API.GettingUpdates where -import Data.Aeson (FromJSON (..), ToJSON (..)) +import Data.Aeson (FromJSON (..), ToJSON (..), Value) import Data.Foldable (asum) import Data.Proxy import GHC.Generics (Generic) @@ -70,8 +70,10 @@ extractUpdateMessage Update{..} = asum -- ** 'getUpdates' -type GetUpdates - = "getUpdates" :> ReqBody '[JSON] GetUpdatesRequest :> Get '[JSON] (Response [Update]) +type GetUpdatesAs a + = "getUpdates" :> ReqBody '[JSON] GetUpdatesRequest :> Get '[JSON] (Response [a]) + +type GetUpdates = GetUpdatesAs Update -- | Use this method to receive incoming updates using long polling. -- An list of 'Update' objects is returned. @@ -82,6 +84,14 @@ type GetUpdates getUpdates :: GetUpdatesRequest -> ClientM (Response [Update]) getUpdates = client (Proxy @GetUpdates) +-- | More liberal version of `getUpdates` funcion. +-- +-- It's useful when you aren't sure that you can +-- parse all the types of updates, so you would recieve +-- a list of Value with updates, that you can parse later. +getUpdatesAsValue :: GetUpdatesRequest -> ClientM (Response [Value]) +getUpdatesAsValue = client (Proxy @(GetUpdatesAs Value)) + -- | Request parameters for 'getUpdates'. data GetUpdatesRequest = GetUpdatesRequest { getUpdatesOffset :: Maybe UpdateId -- ^ Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as getUpdates is called with an offset higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten. diff --git a/telegram-bot-simple/src/Telegram/Bot/Simple/BotApp/Internal.hs b/telegram-bot-simple/src/Telegram/Bot/Simple/BotApp/Internal.hs index 070243c..2af47fa 100644 --- a/telegram-bot-simple/src/Telegram/Bot/Simple/BotApp/Internal.hs +++ b/telegram-bot-simple/src/Telegram/Bot/Simple/BotApp/Internal.hs @@ -16,6 +16,8 @@ import qualified System.Cron as Cron import qualified Telegram.Bot.API as Telegram import Telegram.Bot.Simple.Eff +import Data.Either (partitionEithers) +import Data.Aeson.Types (parseEither, parseJSON) -- | A bot application. data BotApp model action = BotApp @@ -141,7 +143,7 @@ startPolling handleUpdate = go Nothing let inc (Telegram.UpdateId n) = Telegram.UpdateId (n + 1) offset = fmap inc lastUpdateId res <- - (Right <$> Telegram.getUpdates + (Right <$> Telegram.getUpdatesAsValue (Telegram.GetUpdatesRequest offset Nothing (Just 25) Nothing)) `catchError` (pure . Left) @@ -150,19 +152,29 @@ startPolling handleUpdate = go Nothing liftIO (print servantErr) pure lastUpdateId Right result -> do - let updates = Telegram.responseResult result + let updateValues = Telegram.responseResult result + (errors, updates) = parseUpdates updateValues updateIds = map Telegram.updateUpdateId updates maxUpdateId = maximum (Nothing : map Just updateIds) + mapM_ reportParseError errors mapM_ handleUpdate updates pure maxUpdateId liftIO $ threadDelay 1000000 go nextUpdateId + parseUpdates updates = + partitionEithers (map (parseEither parseJSON) updates) + + reportParseError err = + liftIO $ putStrLn $ + "Failed to parse an update! Please, make sure you have the latest version of `telegram-bot-api`\ + \ library and consider opening an issue if so. Error message: " <> err + -- ** Helpers -- | Instead of 'forkIO' which hides exceptions, -- allow users to handle those exceptions separately. --- +-- -- See . asyncLink :: IO a -> IO (Async a) asyncLink action = do