modules/Quiz/QuizCommands.psm1
|
# Quiz Commands Module - Multiple Choice Music Quiz using liked songs function Get-QuizHighscore { param([int]$Rounds = 5) $highscorePath = Join-Path $env:APPDATA "SpotifyCLI\quiz_highscore.json" if (Test-Path $highscorePath) { try { $data = Get-Content $highscorePath -Raw -Encoding UTF8 | ConvertFrom-Json # Return highscore matching the round count if ($data.rounds -eq $Rounds) { return $data } } catch { # Corrupt file, ignore } } return $null } function Save-QuizHighscore { param( [int]$Score, [int]$Rounds ) $dir = Join-Path $env:APPDATA "SpotifyCLI" if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $highscorePath = Join-Path $dir "quiz_highscore.json" $data = @{ highScore = $Score date = (Get-Date).ToString("yyyy-MM-dd HH:mm") rounds = $Rounds } $data | ConvertTo-Json | Set-Content $highscorePath -Encoding UTF8 } function Get-ShuffledOptions { param( [Parameter(Mandatory)][string]$Correct, [Parameter(Mandatory)][string[]]$DecoyPool, [int]$Count = 3 ) # Filter out the correct answer and deduplicate $available = $DecoyPool | Where-Object { $_ -ne $Correct } | Select-Object -Unique # Pick random decoys if ($available.Count -le $Count) { $decoys = @($available) } else { $decoys = @($available | Get-Random -Count $Count) } # Combine with correct answer and shuffle $options = @($decoys) + @($Correct) $shuffled = $options | Get-Random -Count $options.Count # Find the index of the correct answer (1-based) $correctIndex = -1 for ($i = 0; $i -lt $shuffled.Count; $i++) { if ($shuffled[$i] -eq $Correct) { $correctIndex = $i + 1 break } } return @{ Options = $shuffled CorrectIndex = $correctIndex } } function Start-QuizRound { param( [Parameter(Mandatory)]$Track, [int]$RoundNumber, [int]$TotalRounds, [Parameter(Mandatory)][string[]]$DecoyArtists, [Parameter(Mandatory)][string[]]$DecoyTracks ) $trackName = $Track.track.name $artistName = ($Track.track.artists | ForEach-Object { $_.name }) -join ", " $trackUri = $Track.track.uri $durationMs = $Track.track.duration_ms # Calculate random start position (avoid first/last 30s) if ($durationMs -gt 60000) { $minPos = 30000 $maxPos = $durationMs - 30000 - 8000 # leave room for up to 8s of playback if ($maxPos -lt $minPos) { $maxPos = $minPos } $positionMs = Get-Random -Minimum $minPos -Maximum ($maxPos + 1) } else { $positionMs = 0 } Write-Host "" Write-Host " Round $RoundNumber/$TotalRounds" -ForegroundColor Cyan Write-Host " Listening..." -ForegroundColor Yellow # Ensure playback is stopped before starting new track try { Invoke-SpotifyApi -Method PUT -Path "/me/player/pause" | Out-Null Start-Sleep -Milliseconds 300 } catch { } # Play 3-second snippet for artist guess try { $body = @{ uris = @($trackUri) position_ms = $positionMs } Invoke-SpotifyApi -Method PUT -Path "/me/player/play" -Body $body | Out-Null } catch { # Retry once after a brief wait Start-Sleep -Milliseconds 500 try { Invoke-SpotifyApi -Method PUT -Path "/me/player/play" -Body $body | Out-Null } catch { Write-Host " Could not play track. Make sure a Spotify device is active." -ForegroundColor Red return 0 } } Start-Sleep -Seconds 3 # Pause for artist guess try { Invoke-SpotifyApi -Method PUT -Path "/me/player/pause" | Out-Null } catch { } # Phase 1: Artist multiple choice $artistResult = Get-ShuffledOptions -Correct $artistName -DecoyPool $DecoyArtists Write-Host "" Write-Host " Who is the artist?" -ForegroundColor White for ($i = 0; $i -lt $artistResult.Options.Count; $i++) { Write-Host " $($i + 1). $($artistResult.Options[$i])" -ForegroundColor Gray } $artistChoice = Read-Host " >" if ([string]::IsNullOrWhiteSpace($artistChoice)) { Write-Host " No answer! It was: $artistName - $trackName" -ForegroundColor Red Write-Host "" return 0 } $choiceNum = 0 if (-not [int]::TryParse($artistChoice.Trim(), [ref]$choiceNum) -or $choiceNum -lt 1 -or $choiceNum -gt $artistResult.Options.Count) { Write-Host " Invalid choice! It was: $artistName - $trackName" -ForegroundColor Red Write-Host "" return 0 } if ($choiceNum -ne $artistResult.CorrectIndex) { Write-Host " Wrong! It was: $artistName - $trackName" -ForegroundColor Red Write-Host " 0 points this round." -ForegroundColor Gray Write-Host "" return 0 } # Correct artist! Write-Host " Correct! +10 points" -ForegroundColor Green $totalPoints = 10 # Phase 2: Resume from where we paused, play 5 more seconds, then ask for song title Write-Host "" Write-Host " Listening to more..." -ForegroundColor Yellow try { $resumeBody = @{ uris = @($trackUri) position_ms = $positionMs + 3000 } Invoke-SpotifyApi -Method PUT -Path "/me/player/play" -Body $resumeBody | Out-Null } catch { } Start-Sleep -Seconds 5 try { Invoke-SpotifyApi -Method PUT -Path "/me/player/pause" | Out-Null } catch { } # Song title multiple choice $songResult = Get-ShuffledOptions -Correct $trackName -DecoyPool $DecoyTracks Write-Host "" Write-Host " What song is it?" -ForegroundColor White for ($i = 0; $i -lt $songResult.Options.Count; $i++) { Write-Host " $($i + 1). $($songResult.Options[$i])" -ForegroundColor Gray } $songChoice = Read-Host " >" if ([string]::IsNullOrWhiteSpace($songChoice)) { Write-Host " No answer! It was: $trackName" -ForegroundColor Red Write-Host " $totalPoints points this round." -ForegroundColor Gray Write-Host "" return $totalPoints } $songNum = 0 if (-not [int]::TryParse($songChoice.Trim(), [ref]$songNum) -or $songNum -lt 1 -or $songNum -gt $songResult.Options.Count) { Write-Host " Invalid choice! It was: $trackName" -ForegroundColor Red Write-Host " $totalPoints points this round." -ForegroundColor Gray Write-Host "" return $totalPoints } if ($songNum -eq $songResult.CorrectIndex) { $totalPoints += 10 Write-Host " Correct! +10 points" -ForegroundColor Green } else { Write-Host " Wrong! It was: $trackName" -ForegroundColor Red } Write-Host " Total: $totalPoints/20 for this round!" -ForegroundColor Cyan Write-Host "" return $totalPoints } function Start-MusicQuiz { [CmdletBinding()] param( [Parameter(Position = 0)] [int]$Rounds = 5 ) # Validate parameters if ($Rounds -lt 1) { $Rounds = 1 } if ($Rounds -gt 20) { $Rounds = 20 } Write-Host "" Write-Host " MUSIC QUIZ" -ForegroundColor Cyan Write-Host " ==========" -ForegroundColor Cyan Write-Host " Multiple choice quiz from your liked tracks!" -ForegroundColor White Write-Host " Rounds: $Rounds | Artist (3s) + Song (5s)" -ForegroundColor Gray Write-Host "" # Save current playback state to restore later $previousState = $null try { $previousState = Invoke-SpotifyApi -Method GET -Path "/me/player/currently-playing" } catch { # No active playback, that's fine } # Get total liked songs count Write-Host " Fetching your liked songs..." -ForegroundColor Gray try { $likedInfo = Invoke-SpotifyApi -Method GET -Path "/me/tracks" -Query @{ limit = 1; offset = 0 } $totalLiked = $likedInfo.total } catch { Write-Host " Could not fetch liked songs. Check your connection." -ForegroundColor Red return } # Need extra tracks for decoys $totalToFetch = $Rounds + 12 if ($totalLiked -lt $totalToFetch) { $totalToFetch = [Math]::Min($totalLiked, $Rounds + $totalLiked) } if ($totalLiked -lt $Rounds) { Write-Host " You only have $totalLiked liked songs. Need at least $Rounds for the quiz." -ForegroundColor Red return } if ($totalLiked -lt 4) { Write-Host " You need at least 4 liked songs for multiple choice. You have $totalLiked." -ForegroundColor Red return } Write-Host " Found $totalLiked liked songs. Picking tracks..." -ForegroundColor Gray # Pick random tracks (unique offsets) - fetch extra for decoys $usedOffsets = @() $allTracks = @() for ($i = 0; $i -lt $totalToFetch; $i++) { $maxAttempts = 50 $attempt = 0 do { $offset = Get-Random -Minimum 0 -Maximum $totalLiked $attempt++ } while ($usedOffsets -contains $offset -and $attempt -lt $maxAttempts) $usedOffsets += $offset try { $result = Invoke-SpotifyApi -Method GET -Path "/me/tracks" -Query @{ limit = 1; offset = $offset } if ($result.items -and $result.items.Count -gt 0) { $allTracks += $result.items[0] } } catch { # Skip failed fetches } } if ($allTracks.Count -lt 4) { Write-Host " Could not fetch enough tracks for the quiz." -ForegroundColor Red return } # Split into quiz tracks and build decoy pools from ALL tracks $quizTracks = @($allTracks | Select-Object -First $Rounds) $actualRounds = $quizTracks.Count # Build decoy pools from all fetched tracks (including quiz tracks) $decoyArtists = @($allTracks | ForEach-Object { ($_.track.artists | ForEach-Object { $_.name }) -join ", " } | Select-Object -Unique) $decoyTrackNames = @($allTracks | ForEach-Object { $_.track.name } | Select-Object -Unique) Write-Host "" Write-Host " Ready! $actualRounds rounds. Listen carefully!" -ForegroundColor Green Write-Host " Press Enter to start..." -ForegroundColor Gray Read-Host | Out-Null # Run quiz rounds $totalScore = 0 $maxScore = $actualRounds * 20 for ($i = 0; $i -lt $actualRounds; $i++) { # Brief pause between rounds to let Spotify API settle if ($i -gt 0) { Start-Sleep -Milliseconds 500 } $points = Start-QuizRound -Track $quizTracks[$i] -RoundNumber ($i + 1) -TotalRounds $actualRounds -DecoyArtists $decoyArtists -DecoyTracks $decoyTrackNames $totalScore += $points } # Show results Write-Host "" Write-Host " ========================" -ForegroundColor Cyan Write-Host " QUIZ COMPLETE!" -ForegroundColor Cyan Write-Host " Score: $totalScore / $maxScore points" -ForegroundColor White $percentage = if ($maxScore -gt 0) { [Math]::Round(($totalScore / $maxScore) * 100) } else { 0 } if ($percentage -ge 80) { Write-Host " Amazing! You really know your music!" -ForegroundColor Green } elseif ($percentage -ge 50) { Write-Host " Not bad! Keep listening!" -ForegroundColor Yellow } else { Write-Host " Better luck next time!" -ForegroundColor Red } # Check highscore $existing = Get-QuizHighscore -Rounds $actualRounds if ($null -eq $existing -or $totalScore -gt $existing.highScore) { Write-Host " NEW HIGHSCORE!" -ForegroundColor Magenta Save-QuizHighscore -Score $totalScore -Rounds $actualRounds } elseif ($null -ne $existing) { Write-Host " Highscore: $($existing.highScore) pts ($($existing.date))" -ForegroundColor Gray } Write-Host " ========================" -ForegroundColor Cyan Write-Host "" # Offer to restore previous playback if ($null -ne $previousState -and $null -ne $previousState.item) { $restoreChoice = Read-Host " Resume previous track? ($($previousState.item.name)) [Y/n]" if ($restoreChoice -ne 'n' -and $restoreChoice -ne 'N') { try { $body = @{ uris = @($previousState.item.uri) position_ms = [int]$previousState.progress_ms } Invoke-SpotifyApi -Method PUT -Path "/me/player/play" -Body $body | Out-Null if (-not $previousState.is_playing) { Start-Sleep -Milliseconds 300 Invoke-SpotifyApi -Method PUT -Path "/me/player/pause" | Out-Null } Write-Host " Playback restored." -ForegroundColor Green } catch { Write-Host " Could not restore playback." -ForegroundColor Yellow } } } } # Export and alias New-Alias -Name 'quiz' -Value 'Start-MusicQuiz' -Force Export-ModuleMember -Function 'Start-MusicQuiz', 'Get-ShuffledOptions' -Alias 'quiz' |