functions/helpers.ps1

#these are private helper functions

Function _newFacetLink {
    # https://docs.bsky.app/docs/advanced-guides/post-richtext
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory, HelpMessage = 'The Bluesky message with the links')]
        [string]$Message,
        [Parameter(Mandatory, HelpMessage = 'The text of the link')]
        [string]$Text,
        [Parameter(Mandatory, HelpMessage = 'The URI of the link')]
        [string]$Uri
    )

    Write-Verbose "[$((Get-Date).TimeOfDay) PRIVATE] Creating a new facet link for $uri [$Text] in '$Message'"
    if ($text -match "\[|\]|\(\)") {
        Write-Verbose "[Helper] Regex Escaping the text"
        $text = [regex]::Escape($text)
    }
    #the comparison test is case-sensitive
    if (([regex]$Text).IsMatch($Message)) {
        #properties of the facet object are also case-sensitive
        $m = ([regex]$Text).match($Message)
        [PSCustomObject]@{
            index    = [ordered]@{
                byteStart = $m.index
                byteEnd   = ($m.value.length) + ($m.index)
            }
            features = @(
                [PSCustomObject]@{
                    '$type' = 'app.bsky.richtext.facet#link'
                    uri     = $Uri
                }
            )
        }
    }
    else {
        Write-Warning "The text $Text was not found in the message $Message."
    }
}

Function _convertDidToAt {
    [cmdletbinding()]
    Param(
        [parameter(Mandatory, HelpMessage = 'The DID to convert')]
        [ValidatePattern('^did:plc:')]
        [string]$did
    )

    #did:plc:qrllvid7s54k4hnwtqxwetrf
    $at = $did.replace("did:", "at://")
    $at
}

Function _convertAT {
    [cmdletbinding()]
    Param(
        [parameter(Mandatory, HelpMessage = 'The AT string to convert')]
        [ValidatePattern('^at://')]
        [string]$at
    )

    #at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.feed.post/3l7e5jvorof2t
    $split = $at -split '/' | where { $_ -match '\w' }
    #this part might need to change in the future depending on the type of link
    $publicUri = 'https://bsky.app/profile/'
    $publicUri += '{0}/post/{1}' -f $split[1], $split[-1]
    $publicUri
}

function _getPostText {
    [cmdletbinding()]
    param (
        [string]$AT,
        [hashtable]$Headers
    )
    if ($AT) {
        $url = "$script:PDSHost/xrpc/app.bsky.feed.getPosts?uris=$at"
        $r = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
        $r.posts.record.text
    }
}

Function _newSessionObject {
<#
Convert the API response into a structured and type object
 
did : did:plc:ohgsqpfsbocaaxusxqlgfvd7
didDoc : @{@context=System.Object[]; id=did:plc:ohgsqpfsbocaaxusxqlgfvd7; alsoKnownAs=System.Object[];
                  verificationMethod=System.Object[]; service=System.Object[]}
handle : jdhitsolutions.com
email : jhicks@jdhitsolutions.com
emailConfirmed : True
emailAuthFactor : False
accessJwt : eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.eyJzY29wZSI...
refreshJwt : eyJ0eXAiOiJyZWZyZXNoK2p3dCIsImFsZyI6IkVTMjU2SyJ9.eyJz...
active : True
Date : 10/29/2024 9:56:40 AM
Age : 01:09:58.6486840
#>


    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            HelpMessage = 'The Bluesky session object',
            ValueFromPipeline
        )]
        [object]$InputObject
    )
    Begin {
        #not used
    }
    Process {
        [PSCustomObject]@{
            PSTypeName = 'PSBlueskySession'
            Handle     = $InputObject.handle
            Email      = $InputObject.email
            Active     = $InputObject.active
            AccessJwt  = $InputObject.accessJwt
            RefreshJwt = $InputObject.refreshJwt
            DiD        = $InputObject.did
            DidDoc     = $InputObject.didDoc
            Date       = $InputObject.Date
        }
    } #process
    End {
        #11 Nov 2024 Move these definitions to the root module
       <#
        Update-TypeData -TypeName 'PSBlueskySession' -MemberType AliasProperty -MemberName UserName -Value handle -Force
        Update-TypeData -TypeName 'PSBlueskySession' -MemberType AliasProperty -MemberName AccessToken -Value AccessJwt -Force
        Update-TypeData -TypeName 'PSBlueskySession' -MemberType AliasProperty -MemberName RefreshToken -Value RefreshJwt -Force
        Update-TypeData -TypeName 'PSBlueskySession' -MemberType ScriptProperty -MemberName Age -Value { (Get-Date) - $this.Date } -Force
        Update-TypeData -TypeName 'PSBlueskySession' -MemberType ScriptMethod -MemberName Refresh -Value {Update-BskySession -RefreshToken $this.RefreshJwt} -Force
        #>

    }
}

Function _CreateSession {
    #there is an API limit of 300 per day for this endpoint
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory, HelpMessage = 'A PSCredential with your Bluesky username and password')]
        [PSCredential]$Credential
    )

    #Create a logon session
    $headers = @{
        'Content-Type' = 'application/json'
    }

    $LogonURL = "$PDSHOST/xrpc/com.atproto.server.createSession"
    $body = @{
        identifier = $Credential.UserName
        password   = $Credential.GetNetworkCredential().Password
    } | ConvertTo-Json
    Write-Verbose "[$((Get-Date).TimeOfDay) PRIVATE] Creating a Bluesky logon session for $($Credential.UserName)"

    $splat = @{
        Uri         = $LogonURL
        Method      = 'Post'
        Headers     = $headers
        Body        = $Body
        ErrorAction = 'Stop'
    }
    Try {
        # 11 Nov 2024 -create a synchronized hashtable and use a background runspace
        # to update the session every 15 minutes

        $r = Invoke-RestMethod @splat
        $script:BSkySession = [hashtable]::Synchronized(@{
            Handle     = $r.handle
            Email      = $r.email
            Active     = $r.active
            AccessJwt  = $r.accessJwt
            RefreshJwt = $r.refreshJwt
            DiD        = $r.did
            DidDoc     = $r.didDoc
            Date       = Get-Date
        })

        $script:accessJwt = $script:BSkySession.accessJwt
        $script:refreshJwt = $script:BSkySession.refreshJwt

        $script:BSkySession | _newSessionObject

        #create a runspace to update the session every 15 minutes
        $newRunspace =[RunspaceFactory]::CreateRunspace()
        $newRunspace.ApartmentState = "STA"
        $newRunspace.ThreadOptions = "ReuseThread"
        $newRunspace.Open()
        $newRunspace.SessionStateProxy.SetVariable("BSkySession",$script:BSkySession)

        $script:PSCmd = [PowerShell]::Create().AddScript({
            $PDSHOST = 'https://bsky.social'
            $i=0
            #!!!!!!! MY DEBUG CODE
            # $debugFile = "d:\temp\debug.log"
            Do {
                $ts = New-TimeSpan -Start $BSkySession.Date -End (Get-Date)
                if ($ts.TotalMinutes -ge 15) {
                    $i++
                    #refresh
                    $headers = @{
                        Authorization  = "Bearer $($BSkySession.refreshJwt)"
                        'Content-Type' = 'application/json'
                    }
                    $RefreshUrl = "$PDSHost/xrpc/com.atproto.server.refreshSession"
                    Try {
                        $splat = @{
                            Uri         = $RefreshUrl
                            Method      = 'Post'
                            Headers     = $headers
                            ErrorAction = 'Stop'
                            ErrorVariable = 'e'
                        }
                        $r = Invoke-RestMethod @splat
                        #!!!!!!! DEBUG CODE
                         # "[$((Get-Date).TimeOfDay)] Refreshing session" | Out-File -FilePath $debugFile -Append
                         # $r | Out-File -FilePath $debugFile -Append
                        #!!!!!!! DEBUG CODE
                        $BSkySession.accessJwt = $r.accessJwt
                        $BSkySession.refreshJwt = $r.refreshJwt
                        $BSkySession.Active = $r.active
                        $BSkySession.Date = Get-Date
                        $BsKySession["RunspaceOperation"] = $i
                    } #try
                    Catch {
                        #!!!!!!! DEBUG CODE
                       #"[$(Get-Date)] Failed to authenticate or refresh the session. $($_.Exception.Message)" | out-file $debugFile -Append
                        #$e.message | out-file $debugFile -Append
                    } #catch
                }
                Start-Sleep -Seconds 60
            } While ($True)
            Exit
        })

        $script:PSCmd.runspace = $newRunspace
        [Void]($psCmd.BeginInvoke())

        #$script:BSkySession = Invoke-RestMethod @splat | _newSessionObject
        #$script:accessJwt = $script:BSkySession.accessJwt
        #$script:refreshJwt = $script:BSkySession.refreshJwt

    } #try
    Catch {
        throw $_
    }
    if ($script:accessJwt) {
        $script:accessJwt
    }
    else {
        Write-Warning 'Failed to authenticate.'
    }
}

# 4 Nov 2024 -moved this to a public function, Update-BskySession
Function X_RefreshSession {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory, HelpMessage = 'The refresh token')]
        [string]$RefreshToken
    )

    #Refresh a Bluesky session
    Write-Verbose "[$((Get-Date).TimeOfDay)] Refreshing a Bluesky logon session for $($script:BSkySession.handle)"
    $headers = @{
        Authorization  = "Bearer $RefreshToken"
        'Content-Type' = 'application/json'
    }
    $RefreshUrl = "$PDSHost/xrpc/com.atproto.server.refreshSession"
    Try {
        $script:BSkySession = Invoke-RestMethod -Uri $RefreshUrl -Method Post -Headers $headers -errorAction Stop | _newSessionObject

        $script:accessJwt = $script:BSkySession.accessJwt
        $script:refreshJwt = $script:BSkySession.refreshJwt
        #return the session
        $script:BSkySession
    } #try
    Catch {
        Write-Warning "Failed to authenticate or refresh the session. $($_.Exception.Message)"
    }
}