module Spotify where

import Spotify.Servant.Albums
import Spotify.Servant.Artists
import Spotify.Servant.Categories
import Spotify.Servant.Core
import Spotify.Servant.Player
import Spotify.Servant.Playlists
import Spotify.Servant.Search
import Spotify.Servant.Tracks
import Spotify.Servant.Users
import Spotify.Types.Albums
import Spotify.Types.Artists
import Spotify.Types.Auth
import Spotify.Types.Categories
import Spotify.Types.Misc
import Spotify.Types.Player
import Spotify.Types.Playlists
import Spotify.Types.Search
import Spotify.Types.Simple
import Spotify.Types.Tracks
import Spotify.Types.Users

import Control.Applicative ((<|>))
import Control.Exception (throwIO)
import Control.Monad.Except (ExceptT, MonadError, liftEither, runExceptT, throwError)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Loops (unfoldrM)
import Control.Monad.Reader (MonadReader, ReaderT, asks, runReaderT)
import Control.Monad.State (MonadState, StateT, get, put, runStateT)
import Data.Aeson (FromJSON, eitherDecode)
import Data.Bifunctor (bimap)
import Data.Coerce (coerce)
import Data.Composition ((.:), (.:.))
import Data.Proxy (Proxy (Proxy))
import Data.Set (Set)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import GHC.Generics (Generic)
import Network.HTTP.Client (Manager)
import Network.HTTP.Client.TLS (newTlsManager)
import Network.HTTP.Types (Status (statusCode))
import Servant.API (NoContent (NoContent))
import Servant.Client (BaseUrl (BaseUrl, baseUrlHost), ClientError (DecodeFailure, FailureResponse), ClientM, HasClient (Client), Scheme (Http), client, mkClientEnv, responseBody, responseStatusCode, runClientM)
import Servant.Links (allLinks, linkURI)
import System.Directory (XdgDirectory (XdgConfig), createDirectoryIfMissing, getTemporaryDirectory, getXdgDirectory)
import System.FilePath ((</>))
import System.IO (hFlush, stdout)

class (MonadIO m) => MonadSpotify m where
    getAuth :: m Auth
    getManager :: m Manager
    getToken :: m AccessToken
    putToken :: AccessToken -> m ()
    throwClientError :: ClientError -> m a

instance MonadSpotify IO where
    throwClientError :: forall a. ClientError -> IO a
throwClientError = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall e a. Exception e => e -> IO a
throwIO
    getAuth :: IO Auth
getAuth = do
        String
dir <- XdgDirectory -> String -> IO String
getXdgDirectory XdgDirectory
XdgConfig String
"spotify-haskell"
        let getData :: String -> Text -> IO Text
getData String
file Text
prompt =
                -- look for file - otherwise get from stdin
                String -> IO Text
T.readFile String
path forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> do
                    Text
res <- Text -> IO ()
T.putStr (Text
prompt forall a. Semigroup a => a -> a -> a
<> Text
": ") forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO Text
T.getLine
                    Bool -> String -> IO ()
createDirectoryIfMissing Bool
False String
dir
                    String -> Text -> IO ()
T.writeFile String
path Text
res
                    forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
res
              where
                path :: String
path = String
dir String -> String -> String
</> String
file
        RefreshToken -> ClientId -> ClientSecret -> Auth
Auth
            forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Text -> RefreshToken
RefreshToken forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Text -> IO Text
getData String
"refresh" Text
"Refresh token")
            forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Text -> ClientId
ClientId forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Text -> IO Text
getData String
"id" Text
"Client id")
            forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Text -> ClientSecret
ClientSecret forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Text -> IO Text
getData String
"secret" Text
"Client secret")
    getManager :: IO Manager
getManager = forall (m :: * -> *). MonadIO m => m Manager
newTlsManager
    getToken :: IO AccessToken
getToken = do
        String
path <- IO String
monadSpotifyIOTokenPath
        Text -> AccessToken
AccessToken forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO Text
T.readFile String
path forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> do
            TokenResponse{AccessToken
$sel:accessToken:TokenResponse :: TokenResponse -> AccessToken
accessToken :: AccessToken
accessToken} <- forall (m :: * -> *). MonadSpotify m => m TokenResponse
newToken
            forall (m :: * -> *). MonadSpotify m => AccessToken -> m ()
putToken AccessToken
accessToken
            forall (f :: * -> *) a. Applicative f => a -> f a
pure AccessToken
accessToken
    putToken :: AccessToken -> IO ()
putToken (AccessToken Text
t) = do
        String
path <- IO String
monadSpotifyIOTokenPath
        String -> Text -> IO ()
T.writeFile String
path Text
t
monadSpotifyIOTokenPath :: IO FilePath
monadSpotifyIOTokenPath :: IO String
monadSpotifyIOTokenPath = (String -> String -> String
</> String
"spotify-haskell-token") forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO String
getTemporaryDirectory

newtype Spotify a = Spotify
    { forall a.
Spotify a
-> StateT
     AccessToken (ReaderT (Auth, Manager) (ExceptT ClientError IO)) a
unwrap :: StateT AccessToken (ReaderT (Auth, Manager) (ExceptT ClientError IO)) a
    }
    deriving newtype
        ( forall a b. a -> Spotify b -> Spotify a
forall a b. (a -> b) -> Spotify a -> Spotify b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: forall a b. a -> Spotify b -> Spotify a
$c<$ :: forall a b. a -> Spotify b -> Spotify a
fmap :: forall a b. (a -> b) -> Spotify a -> Spotify b
$cfmap :: forall a b. (a -> b) -> Spotify a -> Spotify b
Functor
        , Functor Spotify
forall a. a -> Spotify a
forall a b. Spotify a -> Spotify b -> Spotify a
forall a b. Spotify a -> Spotify b -> Spotify b
forall a b. Spotify (a -> b) -> Spotify a -> Spotify b
forall a b c. (a -> b -> c) -> Spotify a -> Spotify b -> Spotify c
forall (f :: * -> *).
Functor f
-> (forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
<* :: forall a b. Spotify a -> Spotify b -> Spotify a
$c<* :: forall a b. Spotify a -> Spotify b -> Spotify a
*> :: forall a b. Spotify a -> Spotify b -> Spotify b
$c*> :: forall a b. Spotify a -> Spotify b -> Spotify b
liftA2 :: forall a b c. (a -> b -> c) -> Spotify a -> Spotify b -> Spotify c
$cliftA2 :: forall a b c. (a -> b -> c) -> Spotify a -> Spotify b -> Spotify c
<*> :: forall a b. Spotify (a -> b) -> Spotify a -> Spotify b
$c<*> :: forall a b. Spotify (a -> b) -> Spotify a -> Spotify b
pure :: forall a. a -> Spotify a
$cpure :: forall a. a -> Spotify a
Applicative
        , Applicative Spotify
forall a. a -> Spotify a
forall a b. Spotify a -> Spotify b -> Spotify b
forall a b. Spotify a -> (a -> Spotify b) -> Spotify b
forall (m :: * -> *).
Applicative m
-> (forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
return :: forall a. a -> Spotify a
$creturn :: forall a. a -> Spotify a
>> :: forall a b. Spotify a -> Spotify b -> Spotify b
$c>> :: forall a b. Spotify a -> Spotify b -> Spotify b
>>= :: forall a b. Spotify a -> (a -> Spotify b) -> Spotify b
$c>>= :: forall a b. Spotify a -> (a -> Spotify b) -> Spotify b
Monad
        , Monad Spotify
forall a. IO a -> Spotify a
forall (m :: * -> *).
Monad m -> (forall a. IO a -> m a) -> MonadIO m
liftIO :: forall a. IO a -> Spotify a
$cliftIO :: forall a. IO a -> Spotify a
MonadIO
        , MonadState AccessToken
        , MonadReader (Auth, Manager)
        , MonadError ClientError
        )

instance MonadSpotify Spotify where
    getAuth :: Spotify Auth
getAuth = forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks forall a b. (a, b) -> a
fst
    getManager :: Spotify Manager
getManager = forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks forall a b. (a, b) -> b
snd
    getToken :: Spotify AccessToken
getToken = forall s (m :: * -> *). MonadState s m => m s
get
    putToken :: AccessToken -> Spotify ()
putToken = forall s (m :: * -> *). MonadState s m => s -> m ()
put
    throwClientError :: forall a. ClientError -> Spotify a
throwClientError = forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError

runSpotify :: Auth -> Spotify a -> IO (Either ClientError a)
runSpotify :: forall a. Auth -> Spotify a -> IO (Either ClientError a)
runSpotify = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> a
fst) forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
.: forall a.
Maybe Manager
-> Maybe AccessToken
-> Auth
-> Spotify a
-> IO (Either ClientError (a, AccessToken))
runSpotify' forall a. Maybe a
Nothing forall a. Maybe a
Nothing
runSpotify' :: Maybe Manager -> Maybe AccessToken -> Auth -> Spotify a -> IO (Either ClientError (a, AccessToken))
runSpotify' :: forall a.
Maybe Manager
-> Maybe AccessToken
-> Auth
-> Spotify a
-> IO (Either ClientError (a, AccessToken))
runSpotify' Maybe Manager
mm Maybe AccessToken
mt Auth
a Spotify a
x = do
    Manager
man <- forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall (m :: * -> *). MonadIO m => m Manager
newTlsManager forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe Manager
mm
    let tok :: f AccessToken
tok = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (.accessToken) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall e (m :: * -> *) a. MonadError e m => Either e a -> m a
liftEither forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Auth -> Manager -> IO (Either ClientError TokenResponse)
newTokenIO Auth
a Manager
man)) forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe AccessToken
mt
    forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT forall a b. (a -> b) -> a -> b
$ forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (forall s (m :: * -> *) a. StateT s m a -> s -> m (a, s)
runStateT Spotify a
x.unwrap forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall {f :: * -> *}.
(MonadError ClientError f, MonadIO f) =>
f AccessToken
tok) (Auth
a, Manager
man)

liftEitherSpot :: (MonadSpotify m) => Either ClientError a -> m a
liftEitherSpot :: forall (m :: * -> *) a.
MonadSpotify m =>
Either ClientError a -> m a
liftEitherSpot = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall (m :: * -> *) a. MonadSpotify m => ClientError -> m a
throwClientError forall (f :: * -> *) a. Applicative f => a -> f a
pure

inSpot :: forall m a. (MonadSpotify m) => (AccessToken -> ClientM a) -> m a
inSpot :: forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot AccessToken -> ClientM a
x = do
    AccessToken
tok <- forall (m :: * -> *). MonadSpotify m => m AccessToken
getToken
    Manager
man <- forall (m :: * -> *). MonadSpotify m => m Manager
getManager
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (forall a. ClientM a -> ClientEnv -> IO (Either ClientError a)
runClientM (AccessToken -> ClientM a
x AccessToken
tok) forall a b. (a -> b) -> a -> b
$ Manager -> BaseUrl -> ClientEnv
mkClientEnv Manager
man BaseUrl
mainBase) forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
        Left ClientError
e ->
            forall {m :: * -> *}. MonadSpotify m => ClientError -> m Bool
expiry ClientError
e forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
                Bool
True -> forall {m :: * -> *}. MonadSpotify m => m a
retry
                Bool
False -> forall (m :: * -> *) a. MonadSpotify m => ClientError -> m a
throwClientError ClientError
e
        Right a
r -> forall (f :: * -> *) a. Applicative f => a -> f a
pure a
r
  where
    -- get a new token and try again
    retry :: m a
retry = do
        forall (m :: * -> *). MonadSpotify m => AccessToken -> m ()
putToken forall b c a. (b -> c) -> (a -> b) -> a -> c
. (.accessToken) forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall (m :: * -> *). MonadSpotify m => m TokenResponse
newToken
        forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot AccessToken -> ClientM a
x
    -- does the error indicate that the access token has expired?
    expiry :: ClientError -> m Bool
expiry = \case
        FailureResponse RequestF () (BaseUrl, ByteString)
_ Response
resp -> do
            if Status -> Int
statusCode (forall a. ResponseF a -> Status
responseStatusCode Response
resp) forall a. Eq a => a -> a -> Bool
== Int
401
                then do
                    Error{Text
$sel:message:Error :: Error -> Text
message :: Text
message} <- forall (m :: * -> *) a.
MonadSpotify m =>
Either ClientError a -> m a
liftEitherSpot forall a b. (a -> b) -> a -> b
$ forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap String -> ClientError
mkError (.error) forall a b. (a -> b) -> a -> b
$ forall a. FromJSON a => ByteString -> Either String a
eitherDecode @Error' forall a b. (a -> b) -> a -> b
$ forall a. ResponseF a -> a
responseBody Response
resp
                    if Text
message forall a. Eq a => a -> a -> Bool
== Text
"The access token expired"
                        then forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True
                        else forall {f :: * -> *}. Applicative f => f Bool
no
                else forall {f :: * -> *}. Applicative f => f Bool
no
          where
            mkError :: String -> ClientError
mkError String
s = Text -> Response -> ClientError
DecodeFailure (Text
"Failed to decode a spotify error: " forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack String
s) Response
resp
        ClientError
_ -> forall {f :: * -> *}. Applicative f => f Bool
no
      where
        no :: f Bool
no = forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
newtype Error' = Error' {Error' -> Error
error :: Error} -- internal - used for decoding the errors we get from Spotify responses
    deriving stock (Error' -> Error' -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Error' -> Error' -> Bool
$c/= :: Error' -> Error' -> Bool
== :: Error' -> Error' -> Bool
$c== :: Error' -> Error' -> Bool
Eq, Eq Error'
Error' -> Error' -> Bool
Error' -> Error' -> Ordering
Error' -> Error' -> Error'
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Error' -> Error' -> Error'
$cmin :: Error' -> Error' -> Error'
max :: Error' -> Error' -> Error'
$cmax :: Error' -> Error' -> Error'
>= :: Error' -> Error' -> Bool
$c>= :: Error' -> Error' -> Bool
> :: Error' -> Error' -> Bool
$c> :: Error' -> Error' -> Bool
<= :: Error' -> Error' -> Bool
$c<= :: Error' -> Error' -> Bool
< :: Error' -> Error' -> Bool
$c< :: Error' -> Error' -> Bool
compare :: Error' -> Error' -> Ordering
$ccompare :: Error' -> Error' -> Ordering
Ord, Int -> Error' -> String -> String
[Error'] -> String -> String
Error' -> String
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Error'] -> String -> String
$cshowList :: [Error'] -> String -> String
show :: Error' -> String
$cshow :: Error' -> String
showsPrec :: Int -> Error' -> String -> String
$cshowsPrec :: Int -> Error' -> String -> String
Show, forall x. Rep Error' x -> Error'
forall x. Error' -> Rep Error' x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Error' x -> Error'
$cfrom :: forall x. Error' -> Rep Error' x
Generic)
    deriving anyclass (Value -> Parser [Error']
Value -> Parser Error'
forall a.
(Value -> Parser a) -> (Value -> Parser [a]) -> FromJSON a
parseJSONList :: Value -> Parser [Error']
$cparseJSONList :: Value -> Parser [Error']
parseJSON :: Value -> Parser Error'
$cparseJSON :: Value -> Parser Error'
FromJSON)

data Auth = Auth
    { Auth -> RefreshToken
refreshToken :: RefreshToken
    , Auth -> ClientId
clientId :: ClientId
    , Auth -> ClientSecret
clientSecret :: ClientSecret
    }
    deriving (Int -> Auth -> String -> String
[Auth] -> String -> String
Auth -> String
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Auth] -> String -> String
$cshowList :: [Auth] -> String -> String
show :: Auth -> String
$cshow :: Auth -> String
showsPrec :: Int -> Auth -> String -> String
$cshowsPrec :: Int -> Auth -> String -> String
Show)

mainBase, accountsBase :: BaseUrl
mainBase :: BaseUrl
mainBase = Scheme -> String -> Int -> String -> BaseUrl
BaseUrl Scheme
Http String
"api.spotify.com" Int
80 String
"v1"
accountsBase :: BaseUrl
accountsBase = Scheme -> String -> Int -> String -> BaseUrl
BaseUrl Scheme
Http String
"accounts.spotify.com" Int
80 String
"api"

-- helpers for wrapping Servant API
cli :: forall api. (HasClient ClientM api) => Client ClientM api
cli :: forall api. HasClient ClientM api => Client ClientM api
cli = forall api.
HasClient ClientM api =>
Proxy api -> Client ClientM api
client forall a b. (a -> b) -> a -> b
$ forall {k} (t :: k). Proxy t
Proxy @api
noContent :: (Functor f) => f NoContent -> f ()
noContent :: forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap \NoContent
NoContent -> ()
marketFromToken :: Maybe Market
marketFromToken :: Maybe Market
marketFromToken = forall a. a -> Maybe a
Just Market
"from_token"
withPagingParams :: PagingParams -> (Maybe Int -> Maybe Int -> t) -> t
withPagingParams :: forall t. PagingParams -> (Maybe Int -> Maybe Int -> t) -> t
withPagingParams PagingParams{Maybe Int
$sel:limit:PagingParams :: PagingParams -> Maybe Int
limit :: Maybe Int
limit, Maybe Int
$sel:offset:PagingParams :: PagingParams -> Maybe Int
offset :: Maybe Int
offset} Maybe Int -> Maybe Int -> t
f = Maybe Int -> Maybe Int -> t
f Maybe Int
limit Maybe Int
offset

data PagingParams = PagingParams
    { PagingParams -> Maybe Int
limit :: Maybe Int
    , PagingParams -> Maybe Int
offset :: Maybe Int
    }
noPagingParams :: PagingParams
noPagingParams :: PagingParams
noPagingParams = Maybe Int -> Maybe Int -> PagingParams
PagingParams forall a. Maybe a
Nothing forall a. Maybe a
Nothing

newToken :: (MonadSpotify m) => m TokenResponse
newToken :: forall (m :: * -> *). MonadSpotify m => m TokenResponse
newToken = forall (m :: * -> *) a.
MonadSpotify m =>
Either ClientError a -> m a
liftEitherSpot forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (Auth -> Manager -> IO (Either ClientError TokenResponse)
newTokenIO forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (m :: * -> *). MonadSpotify m => m Auth
getAuth forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall (m :: * -> *). MonadSpotify m => m Manager
getManager)
newTokenIO :: Auth -> Manager -> IO (Either ClientError TokenResponse)
newTokenIO :: Auth -> Manager -> IO (Either ClientError TokenResponse)
newTokenIO Auth
a Manager
m = forall a. ClientM a -> ClientEnv -> IO (Either ClientError a)
runClientM (Auth -> ClientM TokenResponse
requestToken Auth
a) (Manager -> BaseUrl -> ClientEnv
mkClientEnv Manager
m BaseUrl
accountsBase)
  where
    requestToken :: Auth -> ClientM TokenResponse
requestToken (Auth RefreshToken
t ClientId
i ClientSecret
s) =
        forall api. HasClient ClientM api => Client ClientM api
cli @RefreshAccessToken
            (RefreshToken -> RefreshAccessTokenForm
RefreshAccessTokenForm RefreshToken
t)
            (ClientId -> ClientSecret -> IdAndSecret
IdAndSecret ClientId
i ClientSecret
s)
newTokenIO' :: (MonadIO m) => Manager -> ClientId -> ClientSecret -> URL -> AuthCode -> m (Either ClientError TokenResponse')
newTokenIO' :: forall (m :: * -> *).
MonadIO m =>
Manager
-> ClientId
-> ClientSecret
-> URL
-> AuthCode
-> m (Either ClientError TokenResponse')
newTokenIO' Manager
man ClientId
clientId ClientSecret
clientSecret URL
redirectURI AuthCode
authCode =
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$
        forall a. ClientM a -> ClientEnv -> IO (Either ClientError a)
runClientM
            ( forall api. HasClient ClientM api => Client ClientM api
cli @RequestAccessToken
                (AuthCode -> URL -> RequestAccessTokenForm
RequestAccessTokenForm AuthCode
authCode URL
redirectURI)
                (ClientId -> ClientSecret -> IdAndSecret
IdAndSecret ClientId
clientId ClientSecret
clientSecret)
            )
            (Manager -> BaseUrl -> ClientEnv
mkClientEnv Manager
man BaseUrl
accountsBase)

-- spotipy-esque
getAuthCodeInteractive :: ClientId -> URL -> Maybe (Set Scope) -> IO (Maybe AuthCode)
getAuthCodeInteractive :: ClientId -> URL -> Maybe (Set Scope) -> IO (Maybe AuthCode)
getAuthCodeInteractive ClientId
clientId URL
redirectURI Maybe (Set Scope)
scopes = do
    Text -> IO ()
T.putStrLn forall a b. (a -> b) -> a -> b
$ Text
"Go to this URL: " forall a. Semigroup a => a -> a -> a
<> (ClientId -> URL -> Maybe (Set Scope) -> URL
authorizeUrl ClientId
clientId URL
redirectURI Maybe (Set Scope)
scopes).unwrap
    Text -> IO ()
T.putStr Text
"Copy the URL you are redirected to: " forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Handle -> IO ()
hFlush Handle
stdout
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> AuthCode
AuthCode forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Maybe Text
T.stripPrefix (URL
redirectURI.unwrap forall a. Semigroup a => a -> a -> a
<> Text
"/?code=") forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Text
T.getLine
authorizeUrl :: ClientId -> URL -> Maybe (Set Scope) -> URL
authorizeUrl :: ClientId -> URL -> Maybe (Set Scope) -> URL
authorizeUrl ClientId
clientId URL
redirectURI Maybe (Set Scope)
scopes =
    Text -> URL
URL forall a b. (a -> b) -> a -> b
$
        Text
"https://"
            forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack
                ( BaseUrl -> String
baseUrlHost BaseUrl
accountsBase
                    forall a. Semigroup a => a -> a -> a
<> String
"/"
                    forall a. Semigroup a => a -> a -> a
<> forall a. Show a => a -> String
show (Link -> URI
linkURI Link
link)
                )
  where
    link :: Link
link =
        forall {k} (api :: k). HasLink api => Proxy api -> MkLink api Link
allLinks
            (forall {k} (t :: k). Proxy t
Proxy @Authorize)
            ClientId
clientId
            Text
"code"
            URL
redirectURI
            forall a. Maybe a
Nothing
            (Set Scope -> ScopeSet
ScopeSet forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe (Set Scope)
scopes)
            forall a. Maybe a
Nothing

getAlbum :: (MonadSpotify m) => AlbumID -> m Album
getAlbum :: forall (m :: * -> *). MonadSpotify m => AlbumID -> m Album
getAlbum AlbumID
a = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetAlbum AlbumID
a Maybe Market
marketFromToken
getAlbumTracks :: (MonadSpotify m) => AlbumID -> PagingParams -> m (Paging TrackSimple)
getAlbumTracks :: forall (m :: * -> *).
MonadSpotify m =>
AlbumID -> PagingParams -> m (Paging TrackSimple)
getAlbumTracks AlbumID
a PagingParams
pps = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall t. PagingParams -> (Maybe Int -> Maybe Int -> t) -> t
withPagingParams PagingParams
pps forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetAlbumTracks AlbumID
a Maybe Market
marketFromToken
removeAlbums :: (MonadSpotify m) => [AlbumID] -> m ()
removeAlbums :: forall (m :: * -> *). MonadSpotify m => [AlbumID] -> m ()
removeAlbums = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @RemoveAlbums

getArtist :: (MonadSpotify m) => ArtistID -> m Artist
getArtist :: forall (m :: * -> *). MonadSpotify m => ArtistID -> m Artist
getArtist = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @GetArtist

getTrack :: (MonadSpotify m) => TrackID -> m Track
getTrack :: forall (m :: * -> *). MonadSpotify m => TrackID -> m Track
getTrack TrackID
t = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetTrack TrackID
t Maybe Market
marketFromToken
getSavedTracks :: (MonadSpotify m) => PagingParams -> m (Paging SavedTrack)
getSavedTracks :: forall (m :: * -> *).
MonadSpotify m =>
PagingParams -> m (Paging SavedTrack)
getSavedTracks PagingParams
pps = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall t. PagingParams -> (Maybe Int -> Maybe Int -> t) -> t
withPagingParams PagingParams
pps forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetSavedTracks Maybe Market
marketFromToken
saveTracks :: (MonadSpotify m) => [TrackID] -> m ()
saveTracks :: forall (m :: * -> *). MonadSpotify m => [TrackID] -> m ()
saveTracks = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @SaveTracks
removeTracks :: (MonadSpotify m) => [TrackID] -> m ()
removeTracks :: forall (m :: * -> *). MonadSpotify m => [TrackID] -> m ()
removeTracks = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @RemoveTracks

search :: (MonadSpotify m) => Text -> [SearchType] -> Maybe Text -> Maybe Market -> PagingParams -> m SearchResult
search :: forall (m :: * -> *).
MonadSpotify m =>
Text
-> [SearchType]
-> Maybe Text
-> Maybe Market
-> PagingParams
-> m SearchResult
search Text
q [SearchType]
t Maybe Text
e Maybe Market
m = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> b -> a -> c
flip forall t. PagingParams -> (Maybe Int -> Maybe Int -> t) -> t
withPagingParams \Maybe Int
limit Maybe Int
offset -> forall api. HasClient ClientM api => Client ClientM api
cli @GetSearch Text
q [SearchType]
t Maybe Text
e Maybe Int
limit Maybe Market
m Maybe Int
offset

getMe :: (MonadSpotify m) => m User
getMe :: forall (m :: * -> *). MonadSpotify m => m User
getMe = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetMe
getUser :: (MonadSpotify m) => UserID -> m User
getUser :: forall (m :: * -> *). MonadSpotify m => UserID -> m User
getUser UserID
u = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetUser UserID
u
unfollowPlaylist :: (MonadSpotify m) => PlaylistID -> m ()
unfollowPlaylist :: forall (m :: * -> *). MonadSpotify m => PlaylistID -> m ()
unfollowPlaylist = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @UnfollowPlaylist

getPlaylist :: (MonadSpotify m) => PlaylistID -> m Playlist
getPlaylist :: forall (m :: * -> *). MonadSpotify m => PlaylistID -> m Playlist
getPlaylist = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @GetPlaylist
addToPlaylist :: (MonadSpotify m) => PlaylistID -> Maybe Int -> [URI] -> m Text
addToPlaylist :: forall (m :: * -> *).
MonadSpotify m =>
PlaylistID -> Maybe Int -> [URI] -> m Text
addToPlaylist PlaylistID
p Maybe Int
position [URI]
uris = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap coerce :: forall a b. Coercible a b => a -> b
coerce forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @AddToPlaylist PlaylistID
p AddToPlaylistBody{[URI]
Maybe Int
$sel:position:AddToPlaylistBody :: Maybe Int
$sel:uris:AddToPlaylistBody :: [URI]
uris :: [URI]
position :: Maybe Int
..}
getMyPlaylists :: (MonadSpotify m) => PagingParams -> m (Paging PlaylistSimple)
getMyPlaylists :: forall (m :: * -> *).
MonadSpotify m =>
PagingParams -> m (Paging PlaylistSimple)
getMyPlaylists PagingParams
pps = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall t. PagingParams -> (Maybe Int -> Maybe Int -> t) -> t
withPagingParams PagingParams
pps forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetMyPlaylists
createPlaylist :: (MonadSpotify m) => UserID -> CreatePlaylistOpts -> m PlaylistSimple
createPlaylist :: forall (m :: * -> *).
MonadSpotify m =>
UserID -> CreatePlaylistOpts -> m PlaylistSimple
createPlaylist UserID
u CreatePlaylistOpts
opts = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @CreatePlaylist UserID
u CreatePlaylistOpts
opts

getCategories :: (MonadSpotify m) => CategoryID -> Maybe Country -> Maybe Locale -> m Category
getCategories :: forall (m :: * -> *).
MonadSpotify m =>
CategoryID -> Maybe Country -> Maybe Locale -> m Category
getCategories = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall d e a b c.
(d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e
.:. forall api. HasClient ClientM api => Client ClientM api
cli @GetCategories

getPlaybackState :: (MonadSpotify m) => Maybe Market -> m PlaybackState
getPlaybackState :: forall (m :: * -> *).
MonadSpotify m =>
Maybe Market -> m PlaybackState
getPlaybackState = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @GetPlaybackState
transferPlayback :: (MonadSpotify m) => [DeviceID] -> Bool -> m ()
transferPlayback :: forall (m :: * -> *). MonadSpotify m => [DeviceID] -> Bool -> m ()
transferPlayback [DeviceID]
device_ids Bool
play = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @TransferPlayback TransferPlaybackBody{Bool
[DeviceID]
$sel:device_ids:TransferPlaybackBody :: [DeviceID]
$sel:play:TransferPlaybackBody :: Bool
play :: Bool
device_ids :: [DeviceID]
..}
getAvailableDevices :: (MonadSpotify m) => m [Device]
getAvailableDevices :: forall (m :: * -> *). MonadSpotify m => m [Device]
getAvailableDevices = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (.devices) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall a b. (a -> b) -> a -> b
$ forall api. HasClient ClientM api => Client ClientM api
cli @GetAvailableDevices
getCurrentlyPlayingTrack :: (MonadSpotify m) => Maybe Market -> m CurrentlyPlayingTrack
getCurrentlyPlayingTrack :: forall (m :: * -> *).
MonadSpotify m =>
Maybe Market -> m CurrentlyPlayingTrack
getCurrentlyPlayingTrack = forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @GetCurrentlyPlayingTrack
startPlayback :: (MonadSpotify m) => Maybe DeviceID -> StartPlaybackOpts -> m ()
startPlayback :: forall (m :: * -> *).
MonadSpotify m =>
Maybe DeviceID -> StartPlaybackOpts -> m ()
startPlayback = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
.: forall api. HasClient ClientM api => Client ClientM api
cli @StartPlayback
pausePlayback :: (MonadSpotify m) => Maybe DeviceID -> m ()
pausePlayback :: forall (m :: * -> *). MonadSpotify m => Maybe DeviceID -> m ()
pausePlayback = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @PausePlayback
skipToNext :: (MonadSpotify m) => Maybe DeviceID -> m ()
skipToNext :: forall (m :: * -> *). MonadSpotify m => Maybe DeviceID -> m ()
skipToNext = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @SkipToNext
skipToPrevious :: (MonadSpotify m) => Maybe DeviceID -> m ()
skipToPrevious :: forall (m :: * -> *). MonadSpotify m => Maybe DeviceID -> m ()
skipToPrevious = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall api. HasClient ClientM api => Client ClientM api
cli @SkipToPrevious
seekToPosition :: (MonadSpotify m) => Int -> Maybe DeviceID -> m ()
seekToPosition :: forall (m :: * -> *).
MonadSpotify m =>
Int -> Maybe DeviceID -> m ()
seekToPosition = forall (f :: * -> *). Functor f => f NoContent -> f ()
noContent forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
MonadSpotify m =>
(AccessToken -> ClientM a) -> m a
inSpot forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
.: forall api. HasClient ClientM api => Client ClientM api
cli @SeekToPosition

-- higher-level wrappers around main API
-- takes a callback which can be used for side effects, or to return False for early exit
allPages :: (Monad m) => Maybe (Paging a -> m Bool) -> (PagingParams -> m (Paging a)) -> m [a]
allPages :: forall (m :: * -> *) a.
Monad m =>
Maybe (Paging a -> m Bool)
-> (PagingParams -> m (Paging a)) -> m [a]
allPages Maybe (Paging a -> m Bool)
callback PagingParams -> m (Paging a)
x =
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a b c. (a -> b -> c) -> b -> a -> c
flip forall (m :: * -> *) a b.
Monad m =>
(a -> m (Maybe (b, a))) -> a -> m [b]
unfoldrM (Int
0, forall a. Maybe a
Nothing, Bool
True) \(Int
i, Maybe Int
total, Bool
keepGoing) -> do
        if Bool
keepGoing Bool -> Bool -> Bool
&& forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
True (Int
i <) Maybe Int
total
            then do
                Paging a
p <- PagingParams -> m (Paging a)
x forall a b. (a -> b) -> a -> b
$ PagingParams{$sel:limit:PagingParams :: Maybe Int
limit = forall a. a -> Maybe a
Just forall {a}. Num a => a
limit, $sel:offset:PagingParams :: Maybe Int
offset = forall a. a -> Maybe a
Just Int
i}
                Bool
keepGoing' <- forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True) (forall a b. (a -> b) -> a -> b
$ Paging a
p) Maybe (Paging a -> m Bool)
callback
                forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a
Just (Paging a
p.items, (Int
i forall a. Num a => a -> a -> a
+ forall {a}. Num a => a
limit, forall a. a -> Maybe a
Just Paging a
p.total, Bool
keepGoing'))
            else forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a. Maybe a
Nothing
  where
    limit :: a
limit = a
50 -- API docs say this is the max