From 68bc48fc46b4d4c5b2395e741cedb28a91e7a998 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Sat, 22 Jun 2019 19:18:55 -0600 Subject: [PATCH] WIP: Update Send-SlackAPI to support cursor-based pagination --- PSSlack/Public/Get-SlackUser.ps1 | 54 +++++-------- PSSlack/Public/Send-SlackAPI.ps1 | 127 +++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 72 deletions(-) diff --git a/PSSlack/Public/Get-SlackUser.ps1 b/PSSlack/Public/Get-SlackUser.ps1 index 0cf0a79..038b1b2 100644 --- a/PSSlack/Public/Get-SlackUser.ps1 +++ b/PSSlack/Public/Get-SlackUser.ps1 @@ -37,7 +37,6 @@ .FUNCTIONALITY Slack #> - [cmdletbinding(DefaultParameterSetName = 'Content')] param ( [string]$Token = $Script:PSSlack.Token, @@ -47,11 +46,11 @@ [switch]$ExcludeBots, [switch]$Raw ) + begin { $body = @{} - if($Presence) - { + if ($Presence) { $body.add('presence', 1) } @@ -59,38 +58,31 @@ Token = $Token Method = 'users.list' } - if($body.keys.count -gt 0) - { + if ($body.keys.count -gt 0) { $params.add('body', $Body) } - $RawUsers = Send-SlackApi @params + $RawUsers = Send-SlackApi @params -EnablePagination - $HasWildCard = $False - foreach($Item in $Name) - { - if($Item -match '\*') - { + $HasWildCard = $false + foreach ($Item in $Name) { + if ($Item -match '\*') { $HasWildCard = $true break } } - if($Billing) - { - $BillingInfo = Send-SlackApi -Token $Token -Method team.billableInfo + if ($Billing) { + $BillingInfo = Send-SlackApi -Token $Token -Method team.billableInfo -EnablePagination $UserIDs = $BillingInfo.billable_info.psobject.properties.name - foreach($User in $RawUsers.members) - { + foreach ($User in $RawUsers.members) { $UserId = $User.Id - if($UserIDs -contains $UserId) - { + if ($UserIDs -contains $UserId) { Add-Member -InputObject $User -MemberType NoteProperty -Name BillingActive -Value $BillingInfo.billable_info.$UserId.billing_active -Force } } } - if($Name -and -not $HasWildCard) - { + if ($Name -and -not $HasWildCard) { # torn between independent queries, or filtering users.list # submit a PR if this isn't performant enough or doesn't make sense. $Users = $RawUsers.members | @@ -102,30 +94,26 @@ # allow like operator on each channel requested in the param, avoid dupes $UserHash = [ordered]@{} - foreach($SlackUser in $AllUsers) - { - foreach($Username in $Name) - { - if($SlackUser.Name -like $Username -and -not $UserHash.Contains($SlackUser.id)) - { + foreach ($SlackUser in $AllUsers) { + foreach ($Username in $Name) { + if ($SlackUser.Name -like $Username -and -not $UserHash.Contains($SlackUser.id)) { $UserHash.Add($SlackUser.Id, $SlackUser) } } } + $Users = $UserHash.Values } - else # nothing specified - { + else { + # nothing specified $Users = $RawUsers.members } - if($Raw) - { + if ($Raw) { $RawUsers } - else - { + else { Parse-SlackUser -InputObject $Users } } -} \ No newline at end of file +} diff --git a/PSSlack/Public/Send-SlackAPI.ps1 b/PSSlack/Public/Send-SlackAPI.ps1 index e94e8c1..deeca35 100755 --- a/PSSlack/Public/Send-SlackAPI.ps1 +++ b/PSSlack/Public/Send-SlackAPI.ps1 @@ -44,6 +44,7 @@ function Send-SlackApi [ValidateNotNullOrEmpty()] [hashtable]$Body = @{ }, + [Parameter()] [ValidateNotNullOrEmpty()] [ValidateScript({ if (-not $_ -and -not $Script:PSSlack.Token) @@ -57,67 +58,115 @@ function Send-SlackApi })] [string]$Token = $Script:PSSlack.Token, + [Parameter()] [string]$Proxy = $Script:PSSlack.Proxy, - [switch]$ForceVerbose = $Script:PSSlack.ForceVerbose + [Parameter()] + [switch]$ForceVerbose = $Script:PSSlack.ForceVerbose, + + # If specified, enables cursor-based pagination of results. See https://api.slack.com/docs/pagination + # for the list of Slack REST API methods that support cursor-based pagination. + [Parameter(ParameterSetName="Pagination")] + [switch] + $EnablePagination, + + # Specifies the page size when using cursor-based pagination. Slack recommends a range of 100-200. + # The default is 200. Note: Slack limits the max to 1000 on certain APIs. That limit may vary over + # time and over different APIs. + [Parameter(ParameterSetName="Pagination")] + [ValidateRange(1,1000)] + [int] + $PageSize = 200, + + # MaxNumberPages can be used to limit the number of pages returned from Slack. The default value is + # 0 which represents no limit i.e. all results are returned. + [Parameter(ParameterSetName="Pagination")] + [ValidateRange(0, [int]::MaxValue)] + [int] + $MaxNumberPages = 0 ) + $Params = @{ Uri = "https://slack.com/api/$Method" ErrorAction = 'Stop' } - if($Proxy) { + if ($Proxy) { $Params['Proxy'] = $Proxy } - if(-not $ForceVerbose) { - $Params.Add('Verbose', $False) + if (-not $ForceVerbose) { + $Params.Add('Verbose', $false) } - if($ForceVerbose) { + if ($ForceVerbose) { $Params.Add('Verbose', $true) } - $Body.token = $Token - try { - $Response = $null - $Response = Invoke-RestMethod @Params -Body $Body + if ($EnablePagination) { + $Params['Uri'] += "?limit=$PageSize" } - catch { - # (HTTP 429 is "Too Many Requests") - if ($_.Exception.Response.StatusCode -eq 429) { - - # Get the time before we can try again. - if( $_.Exception.Response.Headers -and $_.Exception.Response.Headers.Contains('Retry-After') ) { - $RetryPeriod = $_.Exception.Response.Headers.GetValues('Retry-After') - if($RetryPeriod -is [string[]]) { - $RetryPeriod = [int]$RetryPeriod[0] + + $Body.token = $Token + $pageCount = 0 + $hasMoreData = $true + $paginationUriBase = $Params.Uri + + do { + try { + $Response = $null + $Response = Invoke-RestMethod @Params -Body $Body + } + catch { + # (HTTP 429 is "Too Many Requests") + if ($_.Exception.Response.StatusCode -eq 429) { + + # Get the time before we can try again. + if ($_.Exception.Response.Headers -and $_.Exception.Response.Headers.Contains('Retry-After') ) { + $RetryPeriod = $_.Exception.Response.Headers.GetValues('Retry-After') + if ($RetryPeriod -is [string[]]) { + $RetryPeriod = [int]$RetryPeriod[0] + } } + else { + $RetryPeriod = 2 + } + + Write-Verbose "Sleeping [$RetryPeriod] seconds due to Slack 429 response" + Start-Sleep -Seconds $RetryPeriod + continue + } + elseif ($null -ne $_.ErrorDetails.Message) { + # Convert the error-message to an object. (Invoke-RestMethod will not return data by-default if a 4xx/5xx status code is generated.) + $_.ErrorDetails.Message | ConvertFrom-Json | Parse-SlackError -Exception $_.Exception -ErrorAction Stop } else { - $RetryPeriod = 2 + Write-Error -Exception $_.Exception -Message "Slack API call failed: $_" } - Write-Verbose "Sleeping [$RetryPeriod] seconds due to Slack 429 response" - Start-Sleep -Seconds $RetryPeriod - Send-SlackApi @PSBoundParameters - } - elseif ($_.ErrorDetails.Message -ne $null) { - # Convert the error-message to an object. (Invoke-RestMethod will not return data by-default if a 4xx/5xx status code is generated.) - $_.ErrorDetails.Message | ConvertFrom-Json | Parse-SlackError -Exception $_.Exception -ErrorAction Stop + # Check to see if we have confirmation that our API call failed. + # (Responses with exception-generating status codes are handled in the "catch" block above - this one is for errors that don't generate exceptions) + if ($null -ne $Response -and $Response.ok -eq $false) { + $Response | Parse-SlackError + break + } + elseif ($Response) { + Write-Output $Response + if ($EnablePagination) { + $pageCount++ + + $nextCursor = $Response.response_metadata.next_cursor + if ($nextCursor) { + $encodedNextCursor = [System.Web.HttpUtility]::UrlEncode($nextCursor) + $Params['Uri'] = "${paginationUriBase}&cursor=$encodedNextCursor" + } + else { + $hasMoreData = $false + } + } } else { - Write-Error -Exception $_.Exception -Message "Slack API call failed: $_" + Write-Verbose "Something went wrong. `$Response is `$null" + break } } - - # Check to see if we have confirmation that our API call failed. - # (Responses with exception-generating status codes are handled in the "catch" block above - this one is for errors that don't generate exceptions) - if ($Response -ne $null -and $Response.ok -eq $False) { - $Response | Parse-SlackError - } - elseif($Response) { - Write-Output $Response - } - else { - Write-Verbose "Something went wrong. `$Response is `$null" - } + while ($EnablePagination -and $hasMoreData -and ($MaxNumberPages -and ($pageCount -lt $MaxNumberPages))) }