Public/Add-SpotifyTracks.ps1

<#
.SYNOPSIS
Adds songs to your Spotify liked tracks

.DESCRIPTION
Accepts a list of tracks from a CSV or from another cmdlet in this module,
searches for those songs on Spotify, and adds them to your Spotify liked tracks
if found.

.PARAMETER Tracks
Mutually exclusive with InputFile. An array of SpotifyTracks, as generated by
another cmdlet in this module, such as Get-TracksFromFolder or
Get-SpotifyTracks.

.PARAMETER InputFile
Mutually exclusive with Tracks. The Path to a CSV file containing a list of
tracks, where each track has values for three columns: name (song name), artists
(a comma-separated list of artist names), and album (album name).

.PARAMETER ClientId
Optional. The ClientId of the app you registered in the Spotify developer
portal. See the 'Authentication' section at
https://github.com/niwamo/SpotifyUtils

.PARAMETER RedirectURI
Optional. The redirect URI used for OAuth authentication. Must match what is
configured in the Spotify developer protal. See the 'Authentication' section at
https://github.com/niwamo/SpotifyUtils

.PARAMETER ConfigFile
Optional. The path to a JSON configuration file containing 'ClientId' and
'RedirectURI' properties. See the 'Authentication' section at
https://github.com/niwamo/SpotifyUtils

.EXAMPLE
Add-SpotifyTracks -Tracks $(Get-TracksFromFolder -Path ~\Songs)
#>

function Add-SpotifyTracks {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
        [Object[]] $Tracks,

        [ValidateScript({ Test-Path $_ })]
        [Parameter(Mandatory=$false)]
        [string] $InputFile,

        [Parameter(Mandatory=$false)]
        [string] $ClientId,

        [Parameter(Mandatory=$false)]
        [string] $RedirectURI,

        [ValidateScript({Test-Path $_})]
        [Parameter(Mandatory=$false)]
        [string] $ConfigFile
    )
    
    begin {
        Set-StrictMode -Version 1.0
        $ErrorActionPreference = 'Stop'
        $inputData = [System.Collections.ArrayList]::new()
    }

    process {
        if (!$Tracks -and !$InputFile) {
            throw "Either 'Tracks' or 'InputFile' required"
        }
        elseif ($Tracks) {
            $inputData.AddRange([array] $Tracks) | Out-Null
        }
        elseif ($InputFile.Substring($InputFile.Length-3, 3) -ne 'csv') {
            throw 'Only CSV files are supported for InputFile'
        }
        else {
            Write-Debug "Importing tracks from CSV"
            $inputData = Import-Csv -Path $InputFile
        }
    }
  
    end {
        Write-Debug "Received $($inputData.Count) tracks to add"
        $tracks = ConvertTo-SpotifyTrack -Tracks $inputData
        
        # authorization
        $TokenParams = @{
            Scopes = @('user-library-modify')
        }
        foreach ($param in @('ClientId', 'RedirectURI', 'ConfigFile')) {
            if ($PSBoundParameters.ContainsKey($param)) {
                $TokenParams.Add($param, $PSBoundParameters.TryGetValue($param))
            }
        }
        $token = Get-SpotifyToken @TokenParams
        $headers = @{ Authorization = "Bearer $token" }

        ##########################
        # Region: Add Tracks #
        ##########################

        $missing = [System.Collections.ArrayList]::new()
        $added = 0
        foreach ($song in $tracks) {
            # sleep at beginning in case of a 'continue'
            [System.Threading.Thread]::Sleep($script:API_DELAY)
            # FIND SONG
            # https://stackoverflow.com/questions/73680222
            $uri = [string]::Format(
                "{0}?type=track&q=artist:""{1}"" track:""{2}""",
                $script:SEARCH_URI, $song.artists[0], $song.name
            )
            $results = (
                Invoke-WebRequest -Uri $uri -Headers $headers
            ).Content | ConvertFrom-Json
            # HANDLE NULL RESULTS
            if (! $results.tracks.items -or $results.tracks.items.Count -eq 0) {
                $log = [string]::Format(
                    "Could not find {0} by {1}; search results were null",
                    $song.name, $song.artists[0]
                )
                Write-Debug $log
                $missing.Add($song) | Out-Null
                continue
            }
            $top = $results.tracks.items[0]
            # CHECK MATCH
            $match = $true
            $comparisons = @(
                @($song.name, $top.name),
                @($song.artists[0], $top.artists[0].name)
            )
            foreach ($set in $comparisons) {
                $cmp1, $cmp2 = $set
                $len = [math]::min($cmp1.Length, $cmp2.Length)
                $match = $cmp1.Substring(0, $len) -eq $cmp2.Substring(0, $len)
                if (! $match) { break }
            }
            # HANDLE MISSING MATCH
            if (! $match) {
                $logMessage = [string]::Format(
                    "Could not find {0} by {1}, top result was {2} by {3}",
                    $song.name, $song.artists[0],
                    $top.name, $top.artists[0].name
                )
                Write-Debug $logMessage
                $missing.Add($song) | Out-Null
            }
            # ADD MATCHED SONG
            $params = @{
                URI         = "$script:MYTRACKS_URI"
                Method      = 'Put'
                ContentType = 'application/json'
                Body        = "{""ids"":[""$($top.id)""]}"
                Headers     = $headers
            }
            Invoke-WebRequest @params | Out-Null
            Write-Debug "Added $($song.name)"
            $added += 1
        }

        Write-Output "${script:GREEN}Added $added songs${script:RESETANSI}"

        if ($missing.Count) {
            $msg = [string]::Format(
                "Failed to add {0} tracks, returning them in an array",
                $missing.Count
            )
            Write-Warning $msg
            return $missing
        }
    }
}