Public/Get-YtmLikedMusic.ps1

function Get-YtmLikedMusic {
    <#
    .SYNOPSIS
        Retrieves your liked songs from YouTube Music.

    .DESCRIPTION
        Gets the list of songs you have liked (thumbs up) on YouTube Music.
        Requires authentication via Connect-YtmAccount first.
        Supports pagination to retrieve your entire library.

    .PARAMETER Limit
        Maximum number of songs to retrieve. Default is 0 which retrieves all songs.

    .EXAMPLE
        Get-YtmLikedMusic

        Gets all liked songs.

    .EXAMPLE
        Get-YtmLikedMusic -Limit 50

        Gets up to 50 liked songs.

    .EXAMPLE
        Get-YtmLikedMusic | Select-Object Title, Artist, Album

        Gets liked songs and displays selected properties.

    .EXAMPLE
        Get-YtmLikedMusic | Export-Csv -Path liked_songs.csv

        Exports all liked songs to a CSV file.

    .OUTPUTS
        YouTubeMusicPS.Song

        Objects with properties:
        - VideoId: YouTube video identifier
        - Title: Song title
        - Artist: Artist name(s)
        - ArtistId: Artist channel ID
        - Album: Album name (if available)
        - AlbumId: Album browse ID
        - Duration: Duration as string (e.g., "3:45")
        - DurationSeconds: Duration in seconds
        - ThumbnailUrl: URL to thumbnail image
        - LikeStatus: Current like status
    #>

    [CmdletBinding()]
    [OutputType('YouTubeMusicPS.Song')]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$Limit = 0
    )

    # Check authentication
    $cookies = Get-YtmStoredCookies
    if (-not $cookies) {
        throw 'Not authenticated. Please run Connect-YtmAccount first.'
    }

    Write-Verbose "Fetching liked music from YouTube Music..."

    # Make the initial API request
    $body = @{
        browseId = 'FEmusic_liked_videos'
    }

    try {
        $response = Invoke-YtmApi -Endpoint 'browse' -Body $body
    }
    catch {
        throw "Failed to retrieve liked music: $($_.Exception.Message)"
    }

    # Find the music shelf in the initial response
    $musicShelf = Find-YtmMusicShelf -Response $response

    if (-not $musicShelf -or -not $musicShelf.PSObject.Properties['contents']) {
        Write-Warning "No liked songs found or unable to parse response."
        return
    }

    $totalCount = 0
    $shouldContinue = $true

    # Process initial batch - check for random mix entry to skip
    $contents = $musicShelf.contents
    $startIndex = 0
    if ($contents.Count -gt 1) {
        $firstItem = $contents[0]
        if ($firstItem.PSObject.Properties['musicResponsiveListItemRenderer']) {
            $renderer = $firstItem.musicResponsiveListItemRenderer
            if (-not $renderer.PSObject.Properties['overlay']) {
                $startIndex = 1
                Write-Verbose "Skipping random mix entry"
            }
        }
    }

    # Output initial songs
    for ($i = $startIndex; $i -lt $contents.Count; $i++) {
        if ($Limit -gt 0 -and $totalCount -ge $Limit) {
            $shouldContinue = $false
            break
        }

        $song = ConvertTo-YtmSong -InputObject $contents[$i]
        if ($song.VideoId -and $song.Title) {
            $song  # Output to pipeline
            $totalCount++
        }
    }

    # Get continuation token for pagination
    $continuationToken = Get-YtmContinuationToken -MusicShelf $musicShelf

    # Continue fetching while we have a token and haven't hit the limit
    $pageNumber = 1
    while ($shouldContinue -and $continuationToken) {
        $pageNumber++
        Write-Verbose "Fetching more songs (retrieved $totalCount so far)..."

        $progressParams = @{
            Activity = 'Retrieving liked songs'
            Status   = "Retrieved $totalCount songs (page $pageNumber)"
        }
        if ($Limit -gt 0) {
            $progressParams['PercentComplete'] = [math]::Min(100, [int]($totalCount / $Limit * 100))
        }
        Write-Progress @progressParams

        try {
            $response = Invoke-YtmApi -Endpoint 'browse' -Body $body -ContinuationToken $continuationToken
        }
        catch {
            Write-Progress -Activity 'Retrieving liked songs' -Completed
            Write-Warning "Failed to fetch continuation: $($_.Exception.Message)"
            break
        }

        # Continuation responses have a different structure
        $contents = $null
        $newContinuationToken = $null

        if ($response.PSObject.Properties['continuationContents']) {
            $shelfContinuation = $response.continuationContents.musicShelfContinuation
            if ($shelfContinuation) {
                $contents = $shelfContinuation.contents
                # Check for next continuation
                if ($shelfContinuation.PSObject.Properties['continuations']) {
                    $continuationItem = $shelfContinuation.continuations[0]
                    if ($continuationItem.PSObject.Properties['nextContinuationData']) {
                        $newContinuationToken = $continuationItem.nextContinuationData.continuation
                    }
                }
            }
        }

        if (-not $contents -or $contents.Count -eq 0) {
            Write-Verbose "No more songs in continuation response"
            break
        }

        # Output songs from continuation
        foreach ($item in $contents) {
            if ($Limit -gt 0 -and $totalCount -ge $Limit) {
                $shouldContinue = $false
                break
            }

            $song = ConvertTo-YtmSong -InputObject $item
            if ($song.VideoId -and $song.Title) {
                $song  # Output to pipeline
                $totalCount++
            }
        }

        $continuationToken = $newContinuationToken
    }

    # Clear progress bar
    if ($pageNumber -gt 1) {
        Write-Progress -Activity 'Retrieving liked songs' -Completed
    }

    Write-Verbose "Retrieved $totalCount liked songs total"
}