Public/Search-SpotifyTrack.ps1

<#
.SYNOPSIS
Search Spotify for a track by artist and title

.DESCRIPTION
Queries the Spotify search API for a track by artist name and title.
Returns a single best match by default. Use -Limit 5 or -Limit 10 to
retrieve a short list, and add -Interactive to pick one from that list
in the terminal.

If the search returns no results, use -TopTracksOnNoMatch to fall back to
the artist's top 3 tracks on Spotify instead.

.PARAMETER Artist
The artist name to search for.

.PARAMETER Title
The track title to search for.

.PARAMETER Limit
Number of results to return. Allowed values: 1 (default), 5, 10.
With -Limit 1 (the default), the top match is returned directly.

.PARAMETER Interactive
When combined with -Limit 5 or -Limit 10, displays a numbered list and
prompts for a selection. Returns a single SpotifyTrack.

.PARAMETER TopTracksOnNoMatch
When the artist+title search returns no results, fall back to the artist's
top 3 tracks on Spotify and return those instead.

.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
Search-SpotifyTrack -Artist "Radiohead" -Title "Creep"

.EXAMPLE
Search-SpotifyTrack -Artist "Radiohead" -Title "Creep" -Limit 5

.EXAMPLE
$track = Search-SpotifyTrack -Artist "Radiohead" -Title "Creep" -Limit 10 -Interactive
Add-SpotifyPlaylistTracks -PlaylistId $pl.id -TrackUris $track.uri

.EXAMPLE
Search-SpotifyTrack -Artist "Radiohead" -Title "TypoInTitle" -TopTracksOnNoMatch
#>

function Search-SpotifyTrack {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $Artist,

        [Parameter(Mandatory=$true)]
        [string] $Title,

        [ValidateSet(1, 5, 10)]
        [Parameter(Mandatory=$false)]
        [int] $Limit = 1,

        [Parameter(Mandatory=$false)]
        [switch] $Interactive,

        [Parameter(Mandatory=$false)]
        [switch] $TopTracksOnNoMatch,

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

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

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

    Set-StrictMode -Version 1.0
    $ErrorActionPreference = 'Stop'

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

    $q = [uri]::EscapeDataString("artist:`"$Artist`" track:`"$Title`"")
    $uri = "$script:SEARCH_URI`?type=track&q=$q&limit=$Limit"

    $response = (
        Invoke-SpotifyRequest -Uri $uri -Headers $headers
    ).Content | ConvertFrom-Json

    $items = $response.tracks.items
    if (!$items -or $items.Count -eq 0) {
        if (!$TopTracksOnNoMatch) {
            Write-Warning "No results found for '$Title' by '$Artist'"
            return
        }

        Write-Warning "No results found for '$Title' by '$Artist' — falling back to top tracks"

        $artistQ = [uri]::EscapeDataString("artist:`"$Artist`"")
        [System.Threading.Thread]::Sleep($script:API_DELAY)
        $fallbackResponse = (
            Invoke-SpotifyRequest -Uri "$script:SEARCH_URI`?type=track&q=$artistQ&limit=3" -Headers $headers
        ).Content | ConvertFrom-Json

        $items = $fallbackResponse.tracks.items
        if (!$items -or $items.Count -eq 0) {
            Write-Warning "No tracks found for artist '$Artist'"
            return
        }
    }

    $tracks = ConvertTo-SpotifyTrack -Tracks $items

    if ($Limit -eq 1 -or $tracks.Count -eq 1) {
        return $tracks[0]
    }

    if ($Interactive) {
        for ($i = 0; $i -lt $tracks.Count; $i++) {
            $t = $tracks[$i]
            Write-Host ("[{0}] {1} — {2} ({3})" -f ($i + 1), $t.artists[0], $t.name, $t.album)
        }
        $raw = Read-Host "Select (1-$($tracks.Count))"
        $idx = [int]$raw - 1
        if ($idx -lt 0 -or $idx -ge $tracks.Count) {
            throw "Invalid selection '$raw'"
        }
        return $tracks[$idx]
    }

    return $tracks
}