{-# LANGUAGE DataKinds, OverloadedStrings, MultiParamTypeClasses #-} {-# LANGUAGE AllowAmbiguousTypes, FlexibleInstances, UndecidableInstances #-} {-# LANGUAGE FunctionalDependencies, KindSignatures #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Utility and base types and functions for the Discord Rest API module Network.Discord.Rest.Prelude where import Control.Concurrent (threadDelay) import Control.Monad (when) import Control.Exception (throwIO) import Control.Monad.IO.Class import Data.Default import Data.Hashable import Data.Monoid ((<>)) import Data.Time.Clock.POSIX import Network.HTTP.Req (Option, Scheme(..), (=:), MonadHttp(..)) import System.Log.Logger import Network.Discord.Types -- | The base url for API requests baseURL :: String baseURL = "https://discordapp.com/api/v6" class (MonadIO m, DiscordAuth m) => DiscordRest m where getRateLimit :: DoFetch f a => f a -> m (Maybe Int) setRateLimit :: DoFetch f a => f a -> Int -> m () waitRateLimit :: DoFetch f a => f a -> m () waitRateLimit endpoint = do rl <- getRateLimit endpoint case rl of Nothing -> return () Just l -> do now <- liftIO (fmap round getPOSIXTime :: IO Int) when (l > now) . liftIO $ do infoM "Discord-hs.Rest" "Hit rate limit, backing off" threadDelay $ 1000000 * (l - now) infoM "Discord-hs.Rest" "Done waiting" return () instance (MonadIO m, DiscordRest m) => MonadHttp m where handleHttpException = liftIO . throwIO -- | Class over which performing a data retrieval action is defined class Hashable (a b) => DoFetch (a :: * -> *) b | a b -> b where doFetch :: DiscordRest m => a b -> m b -- | Represents a range of 'Snowflake's data Range = Range { after :: Snowflake, before :: Snowflake, limit :: Int} instance Default Range where def = Range 0 18446744073709551615 100 -- | Convert a Range to a query string toQueryString :: Range -> Option 'Https toQueryString (Range a b l) = "after" =: show a <> "before" =: show b <> "limit" =: show l