Public/Add-SpotifyPlaylistTracks.ps1

<#
.SYNOPSIS
Adds tracks to a Spotify playlist

.DESCRIPTION
Adds one or more tracks to an existing Spotify playlist.
Accepts SpotifyTrack objects via -Tracks (or the pipeline) and prints
"Artist - Title successfully added" for each track.
Also accepts raw Spotify URIs via -TrackUris when track metadata is not needed.
Batches requests automatically (Spotify allows max 100 URIs per call).

.PARAMETER PlaylistId
The Spotify playlist ID (e.g. the 'id' from New-SpotifyPlaylist output).

.PARAMETER Tracks
One or more SpotifyTrack objects as returned by Search-SpotifyTrack,
Get-SpotifyTracks, or Get-SpotifyPlaylists. Accepts pipeline input.

.PARAMETER TrackUris
One or more Spotify track URIs in the format spotify:track:<id>.

.PARAMETER ClientId
Optional. The ClientId of the app you registered in the Spotify developer
portal.

.PARAMETER RedirectURI
Optional. The redirect URI used for OAuth authentication.

.PARAMETER ConfigFile
Optional. The path to a JSON configuration file containing 'ClientId' and
'RedirectURI' properties.

.EXAMPLE
$track = Search-SpotifyTrack -Artist "Radiohead" -Title "Creep"
Add-SpotifyPlaylistTracks -PlaylistId "3cEYpjA9oz9GiPac4AsH4n" -Tracks $track

.EXAMPLE
$songs | Add-SpotifyPlaylistTracks -PlaylistId "3cEYpjA9oz9GiPac4AsH4n"

.EXAMPLE
Add-SpotifyPlaylistTracks -PlaylistId "3cEYpjA9oz9GiPac4AsH4n" -TrackUris "spotify:track:4iV5W9uYEdYUVa79Axb7Rh"
#>

function Add-SpotifyPlaylistTracks {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $PlaylistId,

        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [object[]] $Tracks,

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

        [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'
        $collectedTracks = [System.Collections.ArrayList]::new()
    }

    process {
        if ($Tracks) {
            foreach ($t in $Tracks) {
                $collectedTracks.Add($t) | Out-Null
            }
        }
    }

    end {
        $uris   = [System.Collections.ArrayList]::new()
        $labels = [System.Collections.ArrayList]::new()

        foreach ($t in $collectedTracks) {
            $uris.Add($t.uri) | Out-Null
            $labels.Add("$($t.artists[0]) - $($t.name)") | Out-Null
        }
        if ($PSBoundParameters.ContainsKey('TrackUris')) {
            $uris.AddRange($TrackUris)
        }

        if ($uris.Count -eq 0) {
            throw "Either 'Tracks' or 'TrackUris' is required"
        }

        $TokenParams = @{ Scopes = @('playlist-modify-public', 'playlist-modify-private') }
        foreach ($param in @('ClientId', 'RedirectURI', 'ConfigFile')) {
            if ($PSBoundParameters.ContainsKey($param)) {
                $TokenParams.Add($param, $PSBoundParameters[$param])
            }
        }
        $token = Get-SpotifyToken @TokenParams
        $headers = @{ Authorization = "Bearer $token" }

        for ($i = 0; $i -lt $uris.Count; $i += 100) {
            [System.Threading.Thread]::Sleep($script:API_DELAY)
            $chunk = [array] $uris[$i .. [math]::Min($i + 99, $uris.Count - 1)]
            $body = @{ uris = $chunk } | ConvertTo-Json
            Invoke-SpotifyRequest `
                -Uri "$script:BASE_URI/playlists/$PlaylistId/items" `
                -Method Post `
                -ContentType 'application/json' `
                -Body $body `
                -Headers $headers | Out-Null
        }

        if ($labels.Count -gt 0) {
            foreach ($label in $labels) {
                Write-Host "${script:GREEN}${label} successfully added${script:RESETANSI}"
            }
        } else {
            Write-Host "${script:GREEN}Successfully added $($uris.Count) track(s) to playlist${script:RESETANSI}"
        }
    }
}