public/Get-XAccessToken.ps1

function Get-XAccessToken {

[CmdletBinding()]
param (
    [Parameter(Mandatory=$true)][string]$ClientId,
    [Parameter(Mandatory=$true)][string]$ClientSecret,
    [string]$RedirectUri = "http://127.0.0.1",
    [string[]]$Scopes = @("tweet.read", "tweet.write", "users.read", "offline.access")
)

# Generate PKCE code verifier and challenge
$codeVerifier = -join ((65..90) + (97..122) | Get-Random -Count 128 | % {[char]$_})
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$codeChallenge = [Convert]::ToBase64String($sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($codeVerifier))) -replace '=','' -replace '/','_' -replace '\+','-'

# Build authorization URL
$authUrl = "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=$ClientId&redirect_uri=$([Uri]::EscapeDataString($RedirectUri))&scope=$([Uri]::EscapeDataString($Scopes -join ' '))&state=state&code_challenge=$codeChallenge&code_challenge_method=S256"
Write-Output "Opening authorization URL: $authUrl"

# Open browser
Start-Process $authUrl

# Start HTTP listener
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($RedirectUri + "/")
try {
    $listener.Start()
    Write-Output "Waiting for authorization on $RedirectUri..."
} catch {
    Write-error "Failed to start listener: $_"
    return
}

try {
    $context = $listener.GetContext()
    $response = $context.Response
    $code = $context.Request.QueryString["code"]
    $state = $context.Request.QueryString["state"]
    $xerror = $context.Request.QueryString["xerror"]
    Write-Output "Received redirect: Code=$code, State=$state, xerror=$xerror"

    $html = "<html><body>Authorization complete. You can close this tab. xerror: $xerror</body></html>"
    $buffer = [Text.Encoding]::UTF8.GetBytes($html)
    $response.ContentLength64 = $buffer.Length
    $response.OutputStream.Write($buffer, 0, $buffer.Length)
    $response.Close()
} catch {
    Write-error "xerror processing redirect: $_"
} finally {
    $listener.Stop()
}

if ($xerror -or -not $code) {
    Write-error "Authorization failed. Code: $code, xerror: $xerror. Check browser URL for details."
    return
}

# Exchange code for access token
$tokenBody = @{
    code = $code
    grant_type = "authorization_code"
    client_id = $ClientId
    redirect_uri = $RedirectUri
    code_verifier = $codeVerifier
} | ConvertTo-Json
$headers = @{ "Content-Type" = "application/json" }
$tokenResponse = Invoke-RestMethod -Uri "https://api.twitter.com/2/oauth2/token" -Method Post -Headers $headers -Body $tokenBody

Write-Output "Access Token: $($tokenResponse.access_token)"
Write-Output "Refresh Token: $($tokenResponse.refresh_token)"
Write-Output "Expires In: $($tokenResponse.expires_in) seconds"
$tokenResponse | ConvertTo-Json | Out-File -FilePath "x_tokens.json"
}