Private/Show-ConfigEditorMenu.ps1
|
#Requires -Version 5.1 function Show-ConfigEditorMenu { <# .SYNOPSIS Interactive menu for changing one configuration field. .DESCRIPTION Offers only the fields bot.py reads, taken from Get-ConfigFieldMap. Two earlier entries, output format and bot activity, are gone: bot.py never read them, so changing them did nothing. The menu does not clear the console. It used to call Clear-Host on every iteration, which wipes whatever the user had on screen - including the installer output they may still need. .PARAMETER ConfigPath Path to config.json. .PARAMETER FFmpegPath ffmpeg used to enumerate audio devices. Falls back to detection. .OUTPUTS Hashtable with Field and Value once a change is confirmed, or $null when the user quits. #> param( [string]$ConfigPath, [string]$FFmpegPath ) if (-not (Test-Path $ConfigPath)) { # Write-Host, not Write-Error: this is an expected, handled condition # that the caller already reports. Writing to the error stream made # the outcome depend on the host's $ErrorActionPreference - the same # call passed locally and failed on the CI runner. Write-Host " config.json not found: $ConfigPath" -ForegroundColor Red return $null } while ($true) { try { $config = Get-Content $ConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { Write-Error "config.json is not valid JSON: $($_.Exception.Message)" return $null } Write-Host "" Write-Host "BATCRelayBot Configuration" -ForegroundColor Cyan Write-Host "" Write-Host " 1. Bot token [REDACTED]" -ForegroundColor Gray Write-Host " 2. Server ID $(Format-ConfigValue $config.guild_id)" -ForegroundColor Gray Write-Host " 3. Audio device $(Format-ConfigValue $config.audio_device_name)" -ForegroundColor Gray Write-Host "" Write-Host " q. Quit" -ForegroundColor Gray Write-Host "" # Read-Host returns "" for a bare Enter and $null only when there is no # input to be had. The difference matters now that the caller loops: # "" re-prompts, $null would otherwise re-prompt forever on a host that # cannot answer - and .Trim() on it threw before it got that far. $raw = try { Read-Host "Select field to edit (1-3) or q" } catch { $null } if ($null -eq $raw) { Write-Host " (no input available - leaving the editor)" -ForegroundColor Yellow return $null } $selection = "$raw".Trim() $result = $null switch ($selection) { "1" { $result = Get-DiscordToken } "2" { $result = Read-ConfigSnowflake -Field "Guild" -CurrentValue $config.guild_id } "3" { $result = Read-ConfigAudioDevice -FFmpegPath $FFmpegPath -CurrentValue $config.audio_device_name } # Neither branch may claim nothing was changed: the caller returns # here after every change, so by the time q is pressed there may # have been several. Only the caller knows. "q" { return $null } "Q" { return $null } default { Write-Host " Please choose 1-3, or q to quit." -ForegroundColor Yellow continue } } if (-not $result -or -not $result.Valid) { if ($result -and $result.Message) { Write-Host " $($result.Message)" -ForegroundColor Yellow } continue } # Confirm before returning: this is the last point at which the change # can be abandoned without touching the file. Write-Host "" $confirm = Read-Host " Apply this change? (y/N)" if ($confirm -notmatch '^(y|yes|j|ja)$') { Write-Host " Discarded." -ForegroundColor Yellow continue } return @{ Field = $result.Field; Value = $result.Value } } } function Format-ConfigValue { <# .SYNOPSIS Renders a config value for display, naming the empty case explicitly. #> param($Value) if ($null -eq $Value -or [string]::IsNullOrWhiteSpace([string]$Value)) { return "(not set)" } return [string]$Value } function Read-ConfigSnowflake { <# .SYNOPSIS Prompts for a Discord ID and validates it before returning. #> [OutputType([hashtable])] param( [Parameter(Mandatory = $true)][string]$Field, $CurrentValue ) $definition = Get-ConfigFieldDefinition -Field $Field Write-Host "" Write-Host $definition.Label -ForegroundColor Cyan Write-Host " Current: $(Format-ConfigValue $CurrentValue)" -ForegroundColor Gray Write-Host " $($definition.Hint)" -ForegroundColor Gray Write-Host "" $entered = Read-Host " New $($definition.Label) (Enter to cancel)" if ([string]::IsNullOrWhiteSpace($entered)) { return @{ Value = $null; Valid = $false; Message = "Cancelled - $($definition.Label) unchanged" } } $check = Test-ConfigValue -Field $Field -Value $entered.Trim() if (-not $check.Valid) { return @{ Value = $null; Valid = $false; Message = $check.Message } } return @{ Value = $entered.Trim(); Valid = $true; Message = $check.Message; Field = $Field } } function Read-ConfigAudioDevice { <# .SYNOPSIS Picks a new audio device from the same filtered list the installer uses. .DESCRIPTION Reuses Select-AudioDevice so the editor cannot offer a device the installer would reject, and so the name is always spelled exactly as ffmpeg reports it. This is the field most likely to need correcting after an install, because it is the one that decides whether anything is heard. #> [OutputType([hashtable])] param( [string]$FFmpegPath, $CurrentValue ) Write-Host "" Write-Host "Audio device" -ForegroundColor Cyan Write-Host " Current: $(Format-ConfigValue $CurrentValue)" -ForegroundColor Gray Write-Host "" if (-not $FFmpegPath) { $detected = Find-FFmpeg if ($detected.Found) { $FFmpegPath = $detected.Path } } $device = Select-AudioDevice -FFmpegPath $FFmpegPath if (-not $device) { return @{ Value = $null; Valid = $false; Message = "Cancelled - audio device unchanged" } } return @{ Value = $device; Valid = $true; Message = "Audio device selected"; Field = "AudioDevice" } } Export-ModuleMember -Function @( 'Show-ConfigEditorMenu', 'Format-ConfigValue', 'Read-ConfigSnowflake', 'Read-ConfigAudioDevice' ) |