# J621Greeting.ps1 - show a random J621 library image as the shell greeting. # # PowerShell port of extras/fish_greeting/fish_greeting.fish (the J621 REST # rewrite): asks GET /api/random/?fastfetch=1&rating=X for a random # png/jpg/gif, downloads the signed url and runs fastfetch with the image as # its logo. Based on the original script from # J621-Django/extras/fish_greeting system/fish_greeting.fish. # # Requires: fastfetch and curl.exe (ships with Windows 10 1803+). # # Rating modes (same file name as the fish version): # %APPDATA%\j621Greeting\greeting_mode 0 = NSFW (explicit) # 1 = SFW (safe) # 2 = Questionable (default) # # Configuration: %APPDATA%\j621Greeting\config.ps1 (sets environment # variables; see config.example.ps1): # J621_BASE API origin (default https://j621.rainbow-herring.ts.net) # J621_TOKEN API / scoped greeting token, sent as "Authorization: Token ..." # J621_WEB web origin used in the printed link (defaults to J621_BASE) # J621_FASTFETCH_CONFIG fastfetch config name (default "jake") # J621_MODE_FILE mode file override # J621_LOGO_TYPE force a fastfetch logo type (sixel, kitty-direct, ...) # J621_ALWAYS_FETCH=1 fetch even in terminals without image support # J621_LOGO_WIDTH logo width in columns (default 35) #Requires -Version 5.1 function Get-J621FileKind { param([string]$Path) try { $stream = [IO.File]::OpenRead($Path) try { $buffer = New-Object byte[] 8 $read = $stream.Read($buffer, 0, 8) if ($read -ge 4 -and $buffer[0] -eq 0x89 -and $buffer[1] -eq 0x50 -and $buffer[2] -eq 0x4E -and $buffer[3] -eq 0x47) { return 'png' } if ($read -ge 2 -and $buffer[0] -eq 0xFF -and $buffer[1] -eq 0xD8) { return 'jpeg' } if ($read -ge 3 -and $buffer[0] -eq 0x47 -and $buffer[1] -eq 0x49 -and $buffer[2] -eq 0x46) { return 'gif' } return 'unknown' } finally { $stream.Dispose() } } catch { return 'unknown' } } function Format-J621Accent { param([string]$Text) return "$([char]27)[4;2;38;2;138;0;222;49m$Text$([char]27)[0m" } function Format-J621Error { param([string]$Text) return "$([char]27)[31m$Text$([char]27)[0m" } function Get-J621FastfetchArgs { $configName = if ($env:J621_FASTFETCH_CONFIG) { $env:J621_FASTFETCH_CONFIG } else { 'jake' } $candidates = @( (Join-Path $env:APPDATA "fastfetch\$configName.jsonc"), (Join-Path $env:APPDATA "fastfetch\$configName\config.jsonc") ) foreach ($candidate in $candidates) { if (Test-Path -LiteralPath $candidate) { return @('--config', $configName) } } return @() } function Show-J621Greeting { param([int]$Depth = 0) # --- Configuration --- $configFile = Join-Path $env:APPDATA 'j621Greeting\config.ps1' if (Test-Path -LiteralPath $configFile) { . $configFile } $base = if ($env:J621_BASE) { $env:J621_BASE.TrimEnd('/') } else { 'https://j621.rainbow-herring.ts.net' } $webBase = if ($env:J621_WEB) { $env:J621_WEB.TrimEnd('/') } else { $base } $token = $env:J621_TOKEN $logoWidth = if ($env:J621_LOGO_WIDTH) { $env:J621_LOGO_WIDTH } else { '35' } $modeFile = if ($env:J621_MODE_FILE) { $env:J621_MODE_FILE } else { Join-Path $env:APPDATA 'j621Greeting\greeting_mode' } # --- Setup logging --- $logDir = Join-Path $env:APPDATA 'j621Logos' $logFile = Join-Path $logDir 'logs.log' if (-not (Test-Path -LiteralPath $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } $stamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') # --- Mode selection --- $mode = '2' if (Test-Path -LiteralPath $modeFile) { $raw = Get-Content -LiteralPath $modeFile -Raw -ErrorAction SilentlyContinue if ($null -ne $raw) { $mode = $raw.Trim() } } switch ($mode) { '0' { $rating = 'e'; $modeName = 'NSFW' } '1' { $rating = 's'; $modeName = 'SFW' } '2' { $rating = 'q'; $modeName = 'Questionable' } default { Write-Output 'Unknown mode, using default NSFW'; $rating = 'e'; $modeName = 'NSFW' } } # --- Pick the image protocol for this terminal --- $logoType = $env:J621_LOGO_TYPE if (-not $logoType) { if ($env:WT_SESSION) { $logoType = 'sixel' } elseif ($env:WEZTERM_PANE) { $logoType = 'kitty-direct' } } $fetchImage = [bool]$logoType -or ($env:J621_ALWAYS_FETCH -eq '1') $ffArgs = @(Get-J621FastfetchArgs) if (-not $fetchImage) { # Terminal without image support (e.g. VS Code): plain fastfetch. & fastfetch @ffArgs return } # --- Ask the API for a random image (JSON with a signed URL) --- $apiUrl = "$base/api/random/?fastfetch=1&rating=$rating" $curlArgs = @('-s', '-L', '--max-time', '20') if ($token) { $curlArgs += @('-H', "Authorization: Token $token") } $json = & curl.exe @curlArgs $apiUrl 2>$null | Out-String $data = $null try { $data = $json | ConvertFrom-Json } catch { $data = $null } $imageUrl = $null; $jId = $null; $md5 = $null; $filename = $null; $detail = $null; $ext = 'img' if ($data) { if ($data.url) { $imageUrl = [string]$data.url } if ($data.j_id) { $jId = [string]$data.j_id } if ($data.md5) { $md5 = [string]$data.md5 } if ($data.filename) { $filename = [string]$data.filename } if ($data.detail) { $detail = [string]$data.detail } if ($data.extension) { $ext = [string]$data.extension } } if ($ext -notmatch '^[A-Za-z0-9]+$') { $ext = 'img' } if (-not $imageUrl) { & fastfetch @ffArgs if ($detail) { Write-Host (Format-J621Accent "No logo ($detail)") } else { Write-Host (Format-J621Accent 'No logo (API error)') } Add-Content -LiteralPath $logFile -Value "$stamp No logo for rating=${rating}: $detail" return } # --- Download the signed image --- $tempFile = Join-Path ([IO.Path]::GetTempPath()) ('j621_logo_' + [guid]::NewGuid().ToString('N') + '.' + $ext) $downloadSuccess = $false & curl.exe -s -L --max-time 60 -o $tempFile $imageUrl 2>$null if ($LASTEXITCODE -eq 0 -and (Test-Path -LiteralPath $tempFile) -and (Get-Item -LiteralPath $tempFile).Length -gt 0) { $downloadSuccess = $true } if (-not $downloadSuccess) { & fastfetch @ffArgs Write-Host (Format-J621Accent 'No logo (download failed)') Add-Content -LiteralPath $logFile -Value "$stamp Download failed for $jId" Remove-Item -LiteralPath $tempFile -ErrorAction SilentlyContinue return } # --- Accept only PNG, JPEG, GIF; anything else causes one more roll --- $kind = Get-J621FileKind -Path $tempFile if ($kind -eq 'unknown') { if ($Depth -lt 3) { Show-J621Greeting -Depth ($Depth + 1) } else { Write-Host (Format-J621Error 'Max recursion depth reached, skipping unsupported file type: unknown') } if ($Depth -eq 0) { Write-Host (Format-J621Error ("Forked (Got unsupported type: unknown - $filename)")) if ($md5) { Write-Output "Link: $webBase/view/$md5" } } Remove-Item -LiteralPath $tempFile -ErrorAction SilentlyContinue return } # --- Display result --- if ($logoType) { & fastfetch @ffArgs --logo-type $logoType --logo $tempFile --logo-width $logoWidth } else { & fastfetch @ffArgs } Add-Content -LiteralPath $logFile -Value "$stamp Downloaded: $jId $filename [$modeName]" if ($filename) { Write-Host (Format-J621Accent "Logo from API: $filename ($modeName, $jId)") Write-Output "Link: $webBase/detail/$jId" } else { Write-Host (Format-J621Accent "Logo from API ($modeName)") } # --- Keep only the last 1000 lines of the log --- $lines = @(Get-Content -LiteralPath $logFile -Tail 1000) Set-Content -LiteralPath $logFile -Value $lines # --- Cleanup --- Remove-Item -LiteralPath $tempFile -ErrorAction SilentlyContinue }