functions/New-PSBlueSkyPost.ps1

# https://docs.bsky.app/docs/api/com-atproto-repo-create-record

Function New-BskyPost {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([System.String])]
    [Alias('skeet')]
    param(
        [parameter(
            Position = 0,
            Mandatory,
            HelpMessage = 'The text of the post',
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Message,
        [parameter(HelpMessage = 'The path to the image file.', ValueFromPipelineByPropertyName)]
        [ValidatePattern('.*\.(jpg|jpeg|png|gif)$')]
        [string]$ImagePath,
        [Parameter(HelpMessage = 'You should include ALT text for the image.', ValueFromPipelineByPropertyName)]
        [Alias('Alt')]
        [string]$ImageAlt
    )

    Begin {
        $PSDefaultParameterValues['_verbose:Command'] = $MyInvocation.MyCommand
        $PSDefaultParameterValues['_verbose:block'] = 'Begin'
        _verbose -message $strings.Starting

        if ($MyInvocation.CommandOrigin -eq 'Runspace') {
            #Hide this metadata when the command is called from another command
            _verbose -message ($strings.PSVersion -f $PSVersionTable.PSVersion)
            _verbose -message ($strings.UsingHost -f $host.Name)
            _verbose -message ($strings.UsingOS -f $PSVersionTable.OS)
            _verbose -message ($strings.UsingModule -f $ModuleVersion)
        }

        $apiUrl = "$PDSHOST/xrpc/com.atproto.repo.createRecord"
        if ($script:BSkySession.accessJwt) {
            $token = $script:BSkySession.accessJwt
            $did = $script:BskySession.did
            $headers = @{
                Authorization  = "Bearer $token"
                'Content-Type' = 'application/json'
            }
            Write-Information $script:BSkySession -Tags raw
        }
        else {
            Write-Warning $strings.NoSession
        }
    } #begin
    Process {
        $PSDefaultParameterValues['_verbose:block'] = 'Process'
        If ($headers) {
            _verbose $strings.PostingMessage

            $record = [ordered]@{
                '$type'   = 'app.bsky.feed.post'
                text      = $Message
                createdAt = (Get-Date -Format 'o')
            }

            #now create the facets
            $facets = @()

            #test for @mentions first and update the text
            #Added 12 Nov 2024 Issue #14
            [regex]$rxMention = '(?<name>@[\w+-]*(\.[\w+-]+)+)'
            if ($rxMention.IsMatch($record.text)) {
                $matches = $rxMention.Matches($record.text)
                $matches | ForEach-Object {
                    #17 Nov 2024 Create a mention facet
                    #
                    <#
                    $url = "https://bsky.app/profile/{0}" -f $_.Groups['name'].Value
                    $replace = "[$($_.value)]($url)"
                    Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Replacing mention $($_.Value) with $replace"
                    $record.text = $record.text -replace $_.Value, $replace
                    #>

                    $text = $_.Groups['name'].Value
                    _verbose -message ($strings.ResolveDID -f $text)
                    $mentionDid = (Get-BskyProfile $text.SubString(1)).did
                    $mention = _newFacetLink -Text $text -did $mentionDid -Message $record.text -FacetType mention
                    $facets += $mention
                }
            }

            #test for tags
            [regex]$rxTag = '(?<tag>#[\w+-]+)'
            if ($rxTag.IsMatch($record.text)) {
                $matches = $rxTag.Matches($record.text)
                $matches | ForEach-Object {
                    $tag = $_.Groups['tag'].Value
                    $tagName = $tag.SubString(1)
                    $tagFacet = _newFacetLink -Text $tag -Message $record.text -FacetType tag -Tag $tagName
                    $facets += $tagFacet
                }
            } #if tags

            #test for Markdown style links
            #create a facet if found
            [regex]$pattern = '(?<text>(?<=\[)[^\]]+(?=\]))\]\((?<uri>http(s)?:\/\/\S+(?=\)))'
            #"(?<text>(?<=\[).*(?=\]))\]\((?<uri>http(s)?:\/\/\S+(?=\)))"
            if ($pattern.IsMatch($record.text)) {
                _verbose $string.ProcessMD
                $matches = $pattern.Matches($record.text)
                #strip off the [ ] from text and the url from the message
                foreach ($match in $matches) {
                    $text = $match.Groups['text'].Value
                    $uri = $match.Groups['uri'].Value
                    #revise the text to be displayed
                    $record.text = ($record.text).replace("[$text]", $text).replace("($uri)", '')
                }

                foreach ($match in $matches) {
                    $text = $match.Groups['text'].Value
                    $uri = $match.Groups['uri'].Value
                    $link = _newFacetLink -Text $text -Uri $uri -Message $record.text -FacetType link
                    $facets += $link
                }
            }
            elseif (([regex]$pattern = 'http(s)?:\/\/\S+').IsMatch($Message)) {
                #a regex pattern to detect https or http links
                #1 Nov 2024 - made the regex more specific

                _verbose -message $string.ProcessUrl
                $matches = $pattern.Matches($Message)
                #$facets = @()
                foreach ($match in $matches) {
                    $link = _newFacetLink -Text $match.Value -Uri $match.Value -Message $Message
                    $facets += $link
                }
                #$record.Add('langs', @('en'))
            }
            _verbose -message $strings.AddFacets
            $record.Add('facets', $facets)

            if ($ImagePath) {
                if (-not $ImageAlt) {
                    Throw $strings.MissingAlt
                }
                $image = Add-BskyImage -ImagePath $ImagePath -ImageAlt $ImageAlt
                Write-Information -MessageData $image -Tags raw
                if ($WhatIfPreference) {
                    #don't do anything
                }
                elseif ($image) {
                    $embed = @{
                        '$type' = 'app.bsky.embed.images'
                        images  = @(
                            @{
                                alt   = $ImageAlt
                                image = @{
                                    '$type'  = 'blob'
                                    ref      = @{'$link' = $image.link }
                                    mimeType = $image.mimeType
                                    size     = $image.size
                                }
                            }
                        )
                    }
                    $record.Add('embed', $embed)
                }
                else {
                    Throw ($strings.FailUpload -f $ImagePath, $_.Exception.Message)
                }
            }

            Write-Information -MessageData $record -Tags record
            #15 Nov 2024 Use the accounts DiD to post and not the user's handle
            $body = @{
                repo       = $did
                collection = 'app.bsky.feed.post'
                record     = $record
            } | ConvertTo-Json -Depth 7

            Write-Information -MessageData $body -Tags raw

            if ($PSCmdlet.ShouldProcess($Message, 'Post to Bluesky')) {
                $response = Invoke-RestMethod -Uri $apiUrl -Method Post -Headers $headers -Body $body
                _convertAT -at $response.uri
                Write-Information -MessageData $response -Tags raw
            }
        }
    } #process
    End {
        $PSDefaultParameterValues['_verbose:Command'] = $MyInvocation.MyCommand
        $PSDefaultParameterValues['_verbose:block'] = 'End'
        _verbose $strings.Ending
    } #end
}