Public/Add-PatPlaylistItem.ps1

function Add-PatPlaylistItem {
    <#
    .SYNOPSIS
        Adds items to an existing playlist on a Plex server.
 
    .DESCRIPTION
        Adds one or more media items to an existing playlist. Items are specified by
        their rating keys (unique identifiers in the Plex library). Items are added
        to the end of the playlist.
 
    .PARAMETER PlaylistId
        The unique identifier of the playlist to add items to.
 
    .PARAMETER PlaylistName
        The name of the playlist to add items to. Supports tab completion.
 
    .PARAMETER RatingKey
        One or more media item rating keys to add to the playlist.
        Rating keys can be obtained from library browsing commands like Get-PatLibraryItem.
 
    .PARAMETER ServerName
        The name of a stored server to use. Use Get-PatStoredServer to see available servers.
        This is more convenient than ServerUri as you don't need to remember the URI or token.
 
    .PARAMETER ServerUri
        The base URI of the Plex server (e.g., http://plex.example.com:32400).
        If not specified, uses the default stored server.
 
    .PARAMETER Token
        The Plex authentication token. Required when using -ServerUri to authenticate
        with the server. If not specified with -ServerUri, requests may fail with 401.
 
    .PARAMETER PassThru
        If specified, returns the updated playlist object.
 
    .EXAMPLE
        Add-PatPlaylistItem -PlaylistId 12345 -RatingKey 67890
 
        Adds the media item with rating key 67890 to playlist 12345.
 
    .EXAMPLE
        Add-PatPlaylistItem -PlaylistName 'My Playlist' -RatingKey 67890 -ServerName 'Home'
 
        Adds an item to a playlist on the stored server named 'Home'.
 
    .EXAMPLE
        Add-PatPlaylistItem -PlaylistName 'My Favorites' -RatingKey 111, 222, 333
 
        Adds three items to the playlist named 'My Favorites'.
 
    .EXAMPLE
        Get-PatLibraryItem -SectionId 1 -Filter 'year=2024' |
            ForEach-Object { $_.ratingKey } |
            Add-PatPlaylistItem -PlaylistName 'New Releases'
 
        Adds all 2024 items from library section 1 to the 'New Releases' playlist.
 
    .EXAMPLE
        Add-PatPlaylistItem -PlaylistId 12345 -RatingKey 67890 -PassThru
 
        Adds an item and returns the updated playlist object.
 
    .OUTPUTS
        PlexAutomationToolkit.Playlist (when -PassThru is specified)
 
        Returns the updated playlist object showing the new item count.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ById')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'ById')]
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $PlaylistId,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByName')]
        [ValidateNotNullOrEmpty()]
        [string]
        $PlaylistName,

        [Parameter(Mandatory = $true, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateRange(1, [int]::MaxValue)]
        [int[]]
        $RatingKey,

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

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-PatServerUri -Uri $_ })]
        [string]
        $ServerUri,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Token,

        [Parameter(Mandatory = $false)]
        [switch]
        $PassThru
    )

    begin {
        try {
            $script:serverContext = Resolve-PatServerContext -ServerName $ServerName -ServerUri $ServerUri -Token $Token
        }
        catch {
            throw "Failed to resolve server: $($_.Exception.Message)"
        }

        $effectiveUri = $script:serverContext.Uri
        $headers = $script:serverContext.Headers

        # Get machine identifier for URI construction
        $machineIdentifier = $null
        try {
            $serverInformationUri = Join-PatUri -BaseUri $effectiveUri -Endpoint '/'
            $serverInformation = Invoke-PatApi -Uri $serverInformationUri -Headers $headers -ErrorAction 'Stop'
            $machineIdentifier = $serverInformation.machineIdentifier
            Write-Verbose "Server machine identifier: $machineIdentifier"
        }
        catch {
            throw "Failed to retrieve server machine identifier: $($_.Exception.Message)"
        }

        # Resolve playlist ID if using name
        $resolvedId = $PlaylistId
        $playlistInformation = $null

        if ($PSCmdlet.ParameterSetName -eq 'ByName') {
            # Only pass ServerUri if explicitly specified
            $getParameters = @{ PlaylistName = $PlaylistName; ErrorAction = 'Stop' }
            if ($script:serverContext.WasExplicitUri) { $getParameters['ServerUri'] = $effectiveUri }
            $playlist = Get-PatPlaylist @getParameters
            if (-not $playlist) {
                throw "No playlist found with name '$PlaylistName'"
            }
            $resolvedId = $playlist.PlaylistId
            $playlistInformation = $playlist
        }
        else {
            try {
                # Only pass ServerUri if explicitly specified
                $getParameters = @{ PlaylistId = $PlaylistId; ErrorAction = 'Stop' }
                if ($script:serverContext.WasExplicitUri) { $getParameters['ServerUri'] = $effectiveUri }
                $playlistInformation = Get-PatPlaylist @getParameters
            }
            catch {
                Write-Verbose "Could not retrieve playlist info for ID $PlaylistId"
            }
        }

        # Collect all rating keys from pipeline
        $allRatingKeys = [System.Collections.ArrayList]::new()
    }

    process {
        # Collect rating keys from pipeline
        foreach ($key in $RatingKey) {
            $null = $allRatingKeys.Add($key)
        }
    }

    end {
        if ($allRatingKeys.Count -eq 0) {
            Write-Verbose "No rating keys provided, nothing to add"
            return
        }

        # Build descriptive target for confirmation
        $playlistDesc = if ($playlistInformation) {
            "'$($playlistInformation.Title)'"
        }
        else {
            "Playlist $resolvedId"
        }
        $target = "$($allRatingKeys.Count) item(s) to $playlistDesc"

        if (-not $PSCmdlet.ShouldProcess($target, 'Add to playlist')) {
            return
        }

        try {
            # Build the URI with items
            # Format: server://machineIdentifier/com.plexapp.plugins.library/library/metadata/ratingKey
            $itemUris = foreach ($key in $allRatingKeys) {
                "server://$machineIdentifier/com.plexapp.plugins.library/library/metadata/$key"
            }
            $uriParam = $itemUris -join ','

            $queryString = "uri=$([System.Uri]::EscapeDataString($uriParam))"
            $endpoint = "/playlists/$resolvedId/items"
            $uri = Join-PatUri -BaseUri $effectiveUri -Endpoint $endpoint -QueryString $queryString

            Write-Verbose "Adding $($allRatingKeys.Count) item(s) to playlist $resolvedId"

            $null = Invoke-PatApi -Uri $uri -Method 'PUT' -Headers $headers -ErrorAction 'Stop'

            if ($PassThru) {
                # Only pass ServerUri if explicitly specified
                $getParameters = @{ PlaylistId = $resolvedId; ErrorAction = 'Stop' }
                if ($script:serverContext.WasExplicitUri) { $getParameters['ServerUri'] = $effectiveUri }
                Get-PatPlaylist @getParameters
            }
        }
        catch {
            throw "Failed to add items to playlist: $($_.Exception.Message)"
        }
    }
}