Public/Get-PatPlaylist.ps1

function Get-PatPlaylist {
    <#
    .SYNOPSIS
        Retrieves playlists from a Plex server.
 
    .DESCRIPTION
        Gets a list of playlists from the Plex server. Can retrieve all playlists,
        filter by ID or name, and optionally include the items within each playlist.
        Only returns regular (non-smart) playlists by default.
 
    .PARAMETER PlaylistId
        The unique identifier of a specific playlist to retrieve.
 
    .PARAMETER PlaylistName
        The name of a specific playlist to retrieve. Supports tab completion.
 
    .PARAMETER IncludeItems
        When specified, also retrieves the items within each playlist.
        Items are returned in a nested 'Items' property on each playlist object.
 
    .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 will fail.
 
    .EXAMPLE
        Get-PatPlaylist
 
        Retrieves all playlists from the default Plex server.
 
    .EXAMPLE
        Get-PatPlaylist -ServerName 'Home'
 
        Retrieves all playlists from the stored server named 'Home'.
 
    .EXAMPLE
        Get-PatPlaylist -PlaylistId 12345
 
        Retrieves the playlist with the specified ID.
 
    .EXAMPLE
        Get-PatPlaylist -PlaylistName 'My Favorites'
 
        Retrieves the playlist named 'My Favorites'.
 
    .EXAMPLE
        Get-PatPlaylist -IncludeItems
 
        Retrieves all playlists with their items included.
 
    .EXAMPLE
        Get-PatPlaylist -PlaylistName 'Watch Later' -IncludeItems | Select-Object -ExpandProperty Items
 
        Retrieves only the items from the 'Watch Later' playlist.
 
    .OUTPUTS
        PlexAutomationToolkit.Playlist
 
        Objects with properties:
        - PlaylistId: Unique playlist identifier (ratingKey)
        - Title: Name of the playlist
        - Type: Playlist type (video, audio, photo)
        - ItemCount: Number of items in the playlist
        - Duration: Total duration in milliseconds
        - Smart: Whether this is a smart playlist
        - Composite: URI of the playlist composite image
        - ServerUri: The Plex server URI
        - Items: (Only with -IncludeItems) Array of playlist items
    #>

    [CmdletBinding(DefaultParameterSetName = 'All')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'ById', ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $PlaylistId,

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

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

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

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

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

    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
    }

    process {
        try {
            # Determine endpoint based on parameter set
            if ($PSCmdlet.ParameterSetName -eq 'ById') {
                $endpoint = "/playlists/$PlaylistId"
                Write-Verbose "Retrieving playlist $PlaylistId from $effectiveUri"
            }
            else {
                $endpoint = '/playlists'
                Write-Verbose "Retrieving all playlists from $effectiveUri"
            }

            $uri = Join-PatUri -BaseUri $effectiveUri -Endpoint $endpoint
            $result = Invoke-PatApi -Uri $uri -Headers $headers -ErrorAction 'Stop'

            # Handle empty response
            if (-not $result) {
                Write-Verbose "No playlists found"
                return
            }

            # Extract playlist data from Metadata array (both single and multiple queries return this structure)
            $playlistData = if ($result.Metadata) {
                $result.Metadata
            }
            else {
                @()
            }

            # Filter by name if specified
            if ($PSCmdlet.ParameterSetName -eq 'ByName') {
                $playlistData = $playlistData | Where-Object { $_.title -eq $PlaylistName }
                if (-not $playlistData) {
                    throw "No playlist found with name '$PlaylistName'"
                }
            }

            # Filter out smart playlists (we only support dumb playlists)
            $playlistData = $playlistData | Where-Object { $_.smart -ne '1' -and $_.smart -ne 1 }

            # Transform each playlist into a structured object
            foreach ($playlist in $playlistData) {
                $playlistObj = [PSCustomObject]@{
                    PSTypeName  = 'PlexAutomationToolkit.Playlist'
                    PlaylistId  = [int]$playlist.ratingKey
                    Title       = $playlist.title
                    Type        = $playlist.playlistType
                    ItemCount   = [int]$playlist.leafCount
                    Duration    = [long]$playlist.duration
                    Smart       = ($playlist.smart -eq '1' -or $playlist.smart -eq 1)
                    Composite   = $playlist.composite
                    AddedAt     = if ($playlist.addedAt) {
                        [DateTimeOffset]::FromUnixTimeSeconds([long]$playlist.addedAt).LocalDateTime
                    } else { $null }
                    UpdatedAt   = if ($playlist.updatedAt) {
                        [DateTimeOffset]::FromUnixTimeSeconds([long]$playlist.updatedAt).LocalDateTime
                    } else { $null }
                    ServerUri   = $effectiveUri
                }

                # Fetch items if requested
                if ($IncludeItems) {
                    $itemsEndpoint = "/playlists/$($playlist.ratingKey)/items"
                    $itemsUri = Join-PatUri -BaseUri $effectiveUri -Endpoint $itemsEndpoint

                    try {
                        $itemsResult = Invoke-PatApi -Uri $itemsUri -Headers $headers -ErrorAction 'Stop'

                        $items = @()
                        if ($itemsResult -and $itemsResult.Metadata) {
                            $items = foreach ($item in $itemsResult.Metadata) {
                                [PSCustomObject]@{
                                    PSTypeName      = 'PlexAutomationToolkit.PlaylistItem'
                                    PlaylistItemId  = [int]$item.playlistItemID
                                    RatingKey       = [int]$item.ratingKey
                                    Title           = $item.title
                                    Type            = $item.type
                                    Duration        = [long]$item.duration
                                    AddedAt         = if ($item.addedAt) {
                                        [DateTimeOffset]::FromUnixTimeSeconds([long]$item.addedAt).LocalDateTime
                                    } else { $null }
                                    PlaylistId      = [int]$playlist.ratingKey
                                    ServerUri       = $effectiveUri
                                }
                            }
                        }

                        Add-Member -InputObject $playlistObj -MemberType NoteProperty -Name 'Items' -Value $items
                    }
                    catch {
                        Write-Warning "Failed to retrieve items for playlist '$($playlist.title)': $($_.Exception.Message)"
                        Add-Member -InputObject $playlistObj -MemberType NoteProperty -Name 'Items' -Value @()
                    }
                }

                $playlistObj
            }
        }
        catch {
            throw "Failed to retrieve playlists: $($_.Exception.Message)"
        }
    }
}