packages feed

spotify-0.1.0.0: examples/Spotify/CreatePlaylist.hs

{- HLINT ignore "Redundant <$>" -}
module Spotify.CreatePlaylist (main) where

import Spotify
import Spotify.Servant.Playlists
import Spotify.Types.Albums
import Spotify.Types.Misc
import Spotify.Types.Search
import Spotify.Types.Simple
import Spotify.Types.Tracks
import Spotify.Types.Users

import Control.Monad ((<=<))
import Control.Monad.Except (MonadError (throwError), runExceptT)
import Control.Monad.State (MonadIO (liftIO), MonadTrans (lift))
import Data.Foldable (traverse_)
import Data.Function (on)
import Data.List (find)
import Data.List.Extra (chunksOf)
import Data.Maybe (isJust)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Data.Traversable (for)
import System.Exit (exitFailure)

main :: (MonadSpotify m) => SearchType -> CreatePlaylistOpts -> m ()
main searchType opts = do
    SomeSearchTypeInfo @a (itemName, extractItems, getResult, getUris) <- case searchType of
        TrackSearch -> pure $ SomeSearchTypeInfo @Track ("track", (.tracks), (.artists), pure . pure . (.uri))
        AlbumSearch -> pure $ SomeSearchTypeInfo @AlbumSimple ("album", (.albums), (.artists), fmap (map (.uri) . (.tracks.items)) . getAlbum . (.id))
        _ -> exit "unsupported search type"
    liftIO $ T.putStrLn $ "Enter lines of artist;" <> itemName
    parsedLines <-
        (map (T.splitOn ";") . T.lines <$> liftIO T.getContents) >>= traverse \case
            [a, b] -> pure (a, b)
            _ -> exit "parse failure"
    items <- for parsedLines \(artist, item) ->
        runExceptT @a -- we actually use this monad to indicate success - it's just a way to return early
            ( allPages
                ( Just \p -> do
                    if p.offset > searchLimit
                        then pure False
                        else case find (isJust . find (((==) `on` T.toCaseFold) artist) . map (.name) . getResult) p.items of
                            Just t -> throwError t
                            Nothing -> pure True
                )
                ( maybe (exit $ "no " <> itemName <> "s") pure . extractItems
                    <=< lift . search (T.unwords [item, artist]) [searchType] Nothing Nothing
                )
            )
            >>= either
                pure
                ( exit
                    . T.unlines
                    . (("No " <> itemName <> " \"" <> item <> "\" with artist: \"" <> artist <> "\". Found:") :)
                    . map (T.intercalate "; " . map (.name) . getResult)
                )
    playlist <- flip createPlaylist opts . (.id) =<< getMe
    traverse_ (addToPlaylist playlist.id Nothing) =<< chunksOf playlistMaxBatchLimit . concat <$> traverse getUris items
  where
    playlistMaxBatchLimit = 100 -- API won't accept more than this
    exit s = liftIO $ T.putStrLn s >> exitFailure

data SomeSearchTypeInfo where
    SomeSearchTypeInfo ::
        ( Text
        , SearchResult -> Maybe (Paging a)
        , a -> [ArtistSimple]
        , forall m. (MonadSpotify m) => a -> m [URI]
        ) ->
        SomeSearchTypeInfo

searchLimit :: Int
searchLimit = 250