lacymorrow/album-art

View on GitHub
index.js

Summary

Maintainability
B
4 hrs
Test Coverage
'use strict';

( ( root, cx ) => {

    if ( typeof define === 'function' && define.amd ) {

        // AMD
        define( ['cross-fetch'], cx )

    } else if ( typeof exports === 'object' ) {

        // Node, CommonJS-like
        module.exports = cx( require( 'cross-fetch' ) )

    } else {

        // Browser globals (root is window)
        root.albumArt = cx( root.fetch )

    }

} )( this, fetch => {

    const albumArt = async ( artist, options, cb ) => {

        // Massage inputs
        if ( typeof artist !== 'string' ) {

            throw new TypeError( 'Expected search query to be a string' )

        }

        if ( typeof options === 'function' ) {

            cb = options
            options = null

        }

        if ( typeof cb !== 'function' ) {

            cb = null

        }

        // Default options
        let query = artist.replace( '&', 'and' )
        const opts = Object.assign( {
            album: null,
            size: null
        }, options )

        // Image size options
        const SIZES = {
            SMALL: 'small',
            MEDIUM: 'medium',
            LARGE: 'large'
        }

        // Public Key on purpose - don't make me regret this
        const apiEndpoint = 'https://api.spotify.com/v1'
        const authEndpoint = 'https://accounts.spotify.com/api/token'
        const clientId = '3f974573800a4ff5b325de9795b8e603'
        const clientSecret = 'ff188d2860ff44baa57acc79c121a3b9'

        let method = 'artist'
        if ( opts.album !== null ) {

            method = 'album'
            query += ` ${opts.album}` // add space + album name

        }

        // Create a query like "<artist> <album>" and escape it
        const queryParams = `?q=${encodeURIComponent( query )}&type=${method}&limit=1`

        // Create request URL
        const searchUrl = `${apiEndpoint}/search${queryParams}`
        const authString = `${clientId}:${clientSecret}`

        let authorization
        if ( typeof btoa !== 'undefined' ) {

            authorization = btoa( authString )

        } else if ( Buffer ) {

            authorization = Buffer.from( authString ).toString( 'base64' )

        } else {

            throw new Error( 'No suitable environment found' )

        }

        // Start by authorizing a session
        let error = null
        const authToken = await fetch( authEndpoint, {
            method: 'post',
            body: 'grant_type=client_credentials',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                Authorization: `Basic ${authorization}`
            }
        } )
            .then(
                res => res.json()
            )
            .then(
                json => json.access_token
            )
            .catch(
                err => {

                    error = err

                }
            )

        // Perform image search
        const response = !error && await fetch( searchUrl, {
            method: 'get',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                Authorization: `Bearer ${authToken}`
            }
        } )
            .then(
                res => res.json()
            )
            .then(
                json => {

                    if ( typeof ( json.error ) !== 'undefined' ) {

                        // Error
                        return Promise.reject( new Error( `JSON - ${json.error} ${json.message}` ) )

                    }

                    if ( !json[method + 's'] || json[method + 's'].items.length === 0 ) {

                        // Error
                        return Promise.reject( new Error( 'No results found' ) )

                    }

                    // Select image size
                    const images = json[method + 's'].items[0].images

                    let smallest = images[0]
                    let largest = images[0]

                    for ( const element of images ) {

                        if ( parseInt( element.width ) < parseInt( smallest.width ) ) {

                            smallest = element

                        }

                        if ( parseInt( element.width ) > parseInt( largest.width ) ) {

                            largest = element

                        }

                    }

                    if ( opts.size === SIZES.SMALL ) {

                        return smallest.url

                    }

                    if ( opts.size === SIZES.MEDIUM && images.length > 1 ) {

                        return images[1].url

                    }

                    // Large by default
                    return largest.url

                }
            )
            .catch( err => {

                error = err

            } )

        // Callback
        if ( cb ) {

            return cb( error, response )

        }

        // Non-callback, throw errors
        if ( error ) {

            throw error

        }

        // Promise
        return response

    }

    // Exposed public method
    return albumArt

} )