Types/OpenPackage.Publisher/com.atproto.repo.uploadBlob.ps1
|
<# .SYNOPSIS Publish Blobs to At Protocol .DESCRIPTION Publishes content blobs to At Protocol. .LINK https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/repo/uploadBlob.json #> [CmdletBinding(PositionalBinding=$false,SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( "PSAvoidUsingPlainTextForPassword", "", Justification=" SecureStrings are not actually more secure. Use -Credential to avoid potential information disclosure in Windows event logs. " )] param( # Handle or other identifier supported by the server for the authenticating user. [string] $Identifier, # The app password or account password. [string] $AppPassword, # A credential used to connect. # The username will be treated as the `-Identifier`. # The password will be treated as the `-AppPassword` [Management.Automation.PSCredential] [Alias('PSCredential')] $Credential, # The personal data server used for the connection. [Alias('PersonalDataServer')] [string] $PDS = "https://bsky.social/", # A content type map. # This maps extensions and URIs to a content type. [Collections.IDictionary] $TypeMap = $( ([PSCustomObject]@{PSTypeName='OpenPackage.ContentTypeMap'}).TypeMap ), # Any input to publish. # This can be: # * A package part # * A `[IO.FileInfo]` object. # * A `[Collections.IDictionary]` containing .Content and .ContentType [Parameter(ValueFromPipeline)] [Alias('Package')] [PSObject[]] $InputObject ) # Collect all piped input $allInput = @($input) # If that did not work, collect all non-piped `-InputObject` if (-not $allInput) { $allInput += $InputObject } # If there is no input if (-not $allInput) { # error out. Write-Error "No input to publish" return } # Declare a small function to upload our blobs function uploadBlob { param( # The content to upload. # Can be bytes, files, or package parts. [Parameter(Mandatory)] [PSObject] $Content, # The content type. [string] $ContentType ) # Get our content as bytes [byte[]]$contentBytes = if ($content -is [byte[]] -or $content -as [byte[]] ) { # If it is already bytes, or castable to bytes $content # use the content directly } # If if is a file elseif ($content -is [IO.FileInfo]) { # read all of the file bytes [IO.File]::ReadAllBytes($content.FullName) # and attempt to detect the correct content type if (-not $ContentType) { $contentType = if ($TypeMap.($content.Extension)) { $TypeMap.($content.Extension) } else { # defaulting to image/jpeg if missing 'image/jpeg' } } } # If the content is a stream and we can read it elseif ( $content -is [IO.Stream] -and $content.CanRead ) { # seek to the start of the stream $null = $content.Seek('0', 'Begin') # copy to a memory stream $memoryStream = [IO.MemoryStream]::new() $content.CopyTo($memoryStream) # get the bytes $memoryStream.ToArray() # and close up. $memoryStream.Close() $memoryStream.Dispose() } # If the content is a package part elseif ($content -is [IO.Packaging.PackagePart]) { # use the content type of the package part if (-not $ContentType) { $contentType = $content.ContentType } # Copy the content to a memory stream $memoryStream = [IO.MemoryStream]::new() $contentStream = $content.GetStream('Open', 'Read') $contentStream.CopyTo($memoryStream) # get the bytes $memoryStream.ToArray() # and close up. $memoryStream.Close() $memoryStream.Dispose() $contentStream.Close() $contentStream.Dispose() } # If we could not get content as bytes if (-not $contentBytes) { # error out Write-Error "Could not get content as bytes" return } # Declare the namespace identifier and http method $NamespaceID = 'com.atproto.repo.uploadBlob' $httpMethod = 'POST' # And construct an upload url using our pds. $uploadUrl = "$( # If the PDS was already https if ($pds -like 'https://*') { # just trim trailing slashes from https urls. $pds -replace '/$' } else { # Otherwise, prefix anything else by https:// "https://$pds" -replace '/$' })/xrpc/$NamespaceID" # Prepare our invoke parameters $invokeSplat = [Ordered]@{ Uri = $uploadUrl Body = $contentBytes Method = $httpMethod ContentType = $ContentType } # If -WhatIf was passed, if ($WhatIfPreference) { return $invokeSplat # output our invoke parameters } # Otherwise, add the authentication header $invokeSplat.Headers = [Ordered]@{Authorization="Bearer $($atConnection.accessJwt)"} # and call the endpoint. Invoke-RestMethod @invokeSplat } # Reset any potential connection. $atConnection = $null # and then see if we have enough data to connect. if (-not $atConnection -and ( ($Identifier -and $appPassword) -or ($Credential) )) { # If we do, prepare a splat $connectionSplat = [Ordered]@{} if ($identifier -and $AppPassword) { $connectionSplat.Identifier = $Identifier $connectionSplat.AppPassword = $AppPassword } else { $connectionSplat.Credential = $Credential } # and connect. $atConnection = Publish-OpenPackage -Publisher com.atproto.server.createSession -Option $connectionSplat } #region Upload Blobs $InputNumber = 0 :nextInput foreach ($in in $allInput) { if ($in -is [Collections.IDictionary]) { if (-not ( ($in.Content -is [byte[]]) -or ($in.Content -as [byte[]]) -or ($in.Content -is [IO.Stream] -and $in.Content.CanRead) -or ($in.Content -is [IO.FileInfo]) -or ($in.Content -is [IO.Packaging.PackagePart]) )) { Write-Warning "Input # $InputNumber must contain Content" continue } if ((-not $in.ContentType) -and ( $in.Content -isnot [IO.FileInfo] -and $in.Content -isnot [IO.Packaging.PackagePart] )) { Write-Warning "Input # $InputNumber must contain ContentType" continue } $inCopy = [Ordered]@{} $inCopy.Content = $in.Content $inCopy.ContentType = $in.ContentType uploadBlob @inCopy continue } elseif ($in -is [IO.FileInfo] -or $in -is [IO.Packaging.PackagePart] ) { uploadBlob -Content $in continue } $InputNumber++ } #endregion Upload Blobs |