Public/Sync-PatWatchStatus.ps1
|
function Sync-PatWatchStatus { <# .SYNOPSIS Syncs watch status from one Plex server to another. .DESCRIPTION Compares watch status between source and target Plex servers and marks items as watched on the target server that are watched on the source. Uses the Plex scrobble endpoint to mark items as watched. .PARAMETER SourceServerName The name of the source server (as stored with Add-PatServer). .PARAMETER TargetServerName The name of the target server (as stored with Add-PatServer). .PARAMETER SectionId Optional array of library section IDs to sync. If not specified, syncs all sections. .PARAMETER PassThru Returns the sync results after completion. .EXAMPLE Sync-PatWatchStatus -SourceServerName 'Travel' -TargetServerName 'Home' Syncs all watched status from Travel server to Home server. .EXAMPLE Sync-PatWatchStatus -SourceServerName 'Travel' -TargetServerName 'Home' -SectionId 1, 2 Syncs watched status only for library sections 1 and 2. .EXAMPLE Sync-PatWatchStatus -SourceServerName 'Travel' -TargetServerName 'Home' -WhatIf Shows what would be synced without making changes. .OUTPUTS PlexAutomationToolkit.WatchStatusSyncResult (with -PassThru) Objects with properties: - Title: Item title - Type: 'movie' or 'episode' - ShowName: Series name (episodes only) - Season: Season number (episodes only) - Episode: Episode number (episodes only) - RatingKey: Target server rating key - Status: 'Success' or 'Failed' - Error: Error message if failed #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SourceServerName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $TargetServerName, [Parameter(Mandatory = $false)] [int[]] $SectionId, [Parameter(Mandatory = $false)] [switch] $PassThru ) begin { # Get target server configuration for scrobble operations try { $targetServer = Get-PatStoredServer -Name $TargetServerName -ErrorAction Stop if (-not $targetServer) { throw "Target server '$TargetServerName' not found. Use Add-PatServer to configure it." } } catch { throw "Failed to get server configuration: $($_.Exception.Message)" } Write-Verbose "Syncing watch status from '$SourceServerName' to '$TargetServerName'" } process { try { # Get differences (items watched on source but not target) $compareParams = @{ SourceServerName = $SourceServerName TargetServerName = $TargetServerName WatchedOnSourceOnly = $true ErrorAction = 'Stop' } if ($SectionId) { $compareParams['SectionId'] = $SectionId } $differences = @(Compare-PatWatchStatus @compareParams) if ($differences.Count -eq 0) { Write-Verbose "No differences found - watch status is already in sync" return } Write-Verbose "Found $($differences.Count) items to mark as watched on target" $results = @() $successCount = 0 $failCount = 0 foreach ($item in $differences) { $itemDisplay = if ($item.Type -eq 'episode') { "$($item.ShowName) - S$($item.Season.ToString('D2'))E$($item.Episode.ToString('D2')) - $($item.Title)" } else { "$($item.Title) ($($item.Year))" } $percentComplete = [int]((($successCount + $failCount) / $differences.Count) * 100) Write-Progress -Activity "Syncing watch status" ` -Status "Processing $($successCount + $failCount + 1) of $($differences.Count)" ` -PercentComplete $percentComplete ` -CurrentOperation $itemDisplay ` -Id 1 if ($PSCmdlet.ShouldProcess($itemDisplay, "Mark as watched on $TargetServerName")) { try { # Use scrobble endpoint to mark as watched $scrobbleEndpoint = "/:/scrobble?key=$($item.TargetRatingKey)&identifier=com.plexapp.plugins.library" $scrobbleUri = Join-PatUri -BaseUri $targetServer.uri -Endpoint $scrobbleEndpoint $headers = Get-PatAuthHeaders -Server $targetServer Invoke-PatApi -Uri $scrobbleUri -Headers $headers -ErrorAction Stop | Out-Null $successCount++ Write-Verbose "Marked as watched: $itemDisplay" $results += [PSCustomObject]@{ PSTypeName = 'PlexAutomationToolkit.WatchStatusSyncResult' Title = $item.Title Type = $item.Type ShowName = $item.ShowName Season = $item.Season Episode = $item.Episode RatingKey = $item.TargetRatingKey Status = 'Success' Error = $null } } catch { $failCount++ Write-Warning "Failed to mark as watched: $itemDisplay - $($_.Exception.Message)" $results += [PSCustomObject]@{ PSTypeName = 'PlexAutomationToolkit.WatchStatusSyncResult' Title = $item.Title Type = $item.Type ShowName = $item.ShowName Season = $item.Season Episode = $item.Episode RatingKey = $item.TargetRatingKey Status = 'Failed' Error = $_.Exception.Message } } } } Write-Progress -Activity "Syncing watch status" -Completed -Id 1 Write-Verbose "Sync completed: $successCount succeeded, $failCount failed" if ($PassThru) { $results } } catch { throw "Failed to sync watch status: $($_.Exception.Message)" } } } |