From a860003fe11bd1b9f92c55155890a540b52ce6c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 22:33:47 +0000 Subject: [PATCH] chore(deps): bump github.com/slack-go/slack Bumps the go-modules-updates group with 1 update in the / directory: [github.com/slack-go/slack](https://github.com/slack-go/slack). Updates `github.com/slack-go/slack` from 0.20.0 to 0.23.1 - [Release notes](https://github.com/slack-go/slack/releases) - [Changelog](https://github.com/slack-go/slack/blob/master/CHANGELOG.md) - [Commits](https://github.com/slack-go/slack/compare/v0.20.0...v0.23.1) --- updated-dependencies: - dependency-name: github.com/slack-go/slack dependency-version: 0.23.1 dependency-type: indirect dependency-group: go-modules-updates ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/slack-go/slack/CHANGELOG.md | 383 +++++++++++++++++- vendor/github.com/slack-go/slack/admin.go | 12 +- .../github.com/slack-go/slack/admin_teams.go | 153 +++++++ vendor/github.com/slack-go/slack/apps.go | 2 +- vendor/github.com/slack-go/slack/assistant.go | 163 ++++++-- vendor/github.com/slack-go/slack/audit.go | 2 +- vendor/github.com/slack-go/slack/block.go | 3 + .../github.com/slack-go/slack/block_alert.go | 70 ++++ .../github.com/slack-go/slack/block_card.go | 90 ++++ .../slack-go/slack/block_carousel.go | 42 ++ .../github.com/slack-go/slack/block_conv.go | 33 +- .../slack-go/slack/block_element.go | 26 +- .../github.com/slack-go/slack/block_header.go | 4 +- .../github.com/slack-go/slack/block_json.go | 110 +++++ .../github.com/slack-go/slack/block_object.go | 1 - .../slack-go/slack/block_rich_text.go | 21 +- .../slack-go/slack/block_unknown.go | 22 +- vendor/github.com/slack-go/slack/channels.go | 2 +- vendor/github.com/slack-go/slack/chat.go | 82 ++-- .../slack-go/slack/chat_stream_chunks.go | 95 +++++ .../github.com/slack-go/slack/conversation.go | 15 +- vendor/github.com/slack-go/slack/dialog.go | 3 +- vendor/github.com/slack-go/slack/dnd.go | 36 +- vendor/github.com/slack-go/slack/files.go | 1 + .../slack-go/slack/function_execute.go | 6 +- vendor/github.com/slack-go/slack/im.go | 21 - vendor/github.com/slack-go/slack/info.go | 27 +- vendor/github.com/slack-go/slack/manifests.go | 6 +- vendor/github.com/slack-go/slack/misc.go | 88 +++- vendor/github.com/slack-go/slack/mise.toml | 1 + vendor/github.com/slack-go/slack/oauth.go | 188 +++++++-- vendor/github.com/slack-go/slack/reactions.go | 36 +- vendor/github.com/slack-go/slack/security.go | 8 +- vendor/github.com/slack-go/slack/slack.go | 72 +++- .../github.com/slack-go/slack/socket_mode.go | 6 +- vendor/github.com/slack-go/slack/stars.go | 60 +-- vendor/github.com/slack-go/slack/team.go | 36 +- .../github.com/slack-go/slack/usergroups.go | 14 + vendor/github.com/slack-go/slack/users.go | 76 +++- vendor/github.com/slack-go/slack/views.go | 63 ++- vendor/github.com/slack-go/slack/webhooks.go | 4 +- .../slack-go/slack/websocket_groups.go | 3 - .../slack-go/slack/websocket_managed_conn.go | 15 +- .../slack-go/slack/websocket_misc.go | 142 ++++++- .../slack-go/slack/workflows_featured.go | 143 +++++++ .../slack-go/slack/workflows_triggers.go | 8 +- vendor/modules.txt | 2 +- 49 files changed, 2089 insertions(+), 313 deletions(-) create mode 100644 vendor/github.com/slack-go/slack/admin_teams.go create mode 100644 vendor/github.com/slack-go/slack/block_alert.go create mode 100644 vendor/github.com/slack-go/slack/block_card.go create mode 100644 vendor/github.com/slack-go/slack/block_carousel.go create mode 100644 vendor/github.com/slack-go/slack/block_json.go create mode 100644 vendor/github.com/slack-go/slack/chat_stream_chunks.go delete mode 100644 vendor/github.com/slack-go/slack/im.go create mode 100644 vendor/github.com/slack-go/slack/workflows_featured.go diff --git a/go.mod b/go.mod index f1f06c34..b79d90ff 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/slack-go/slack v0.20.0 // indirect + github.com/slack-go/slack v0.23.1 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/tools v0.43.0 // indirect diff --git a/go.sum b/go.sum index cc11941f..61cd6455 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/slack-go/slack v0.20.0 h1:gbDdbee8+Z2o+DWx05Spq3GzbrLLleiRwHUKs+hZLSU= -github.com/slack-go/slack v0.20.0/go.mod h1:K81UmCivcYd/5Jmz8vLBfuyoZ3B4rQC2GHVXHteXiAE= +github.com/slack-go/slack v0.23.1 h1:ZS5B96wxxYQRwvJ3/vJFtqtUZi3tXhsZCyT44Nv7M80= +github.com/slack-go/slack v0.23.1/go.mod h1:H0yR/YBuRJ39RkE+JpV/d/oEsbanzTRowR82bCN0cEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/vendor/github.com/slack-go/slack/CHANGELOG.md b/vendor/github.com/slack-go/slack/CHANGELOG.md index 5419408b..2f9f821e 100644 --- a/vendor/github.com/slack-go/slack/CHANGELOG.md +++ b/vendor/github.com/slack-go/slack/CHANGELOG.md @@ -7,6 +7,370 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.23.1] - 2026-05-10 + +### Fixed + +- `NewSecretsVerifier` now rejects empty signing secrets to avoid accepting forged request + signatures when applications are misconfigured. + +## [0.23.0] - 2026-04-22 + +### Added + +- **Block Kit: `CardBlock` and `CarouselBlock`** — Support for two of the new + agent-UI blocks announced in the + [April 16 Slack changelog](https://docs.slack.dev/changelog/2026/04/16/block-kit-new-blocks). + `CardBlock` is constructed via `NewCardBlock` with a functional-options + pattern and fluent `With*` builders (`WithTitle`, `WithSubtitle`, `WithBody`, + `WithIcon`, `WithHeroImage`, `WithActions`). `CarouselBlock` is constructed + via `NewCarouselBlock` with a variadic `*CardBlock` list plus `WithBlockID` + and `AddCard` helpers. Both blocks wire into `Blocks.UnmarshalJSON` for + round-trip fidelity, and reuse existing `ImageBlockElement` / + `ButtonBlockElement` / `BlockElements` types rather than introducing new + composition objects. +- **Block Kit: `AlertBlock`** — Support for the third of the new agent-UI + blocks from the + [April 16 Slack changelog](https://docs.slack.dev/changelog/2026/04/16/block-kit-new-blocks). + `AlertBlock` is constructed via `NewAlertBlock` with a `*TextBlockObject` + body and a functional-options pattern. Severity is set via + `AlertBlockOptionLevel` (`AlertLevelDefault`, `AlertLevelInfo`, + `AlertLevelWarning`, `AlertLevelError`, `AlertLevelSuccess`) and the block + ID via `AlertBlockOptionBlockID`. Wires into `Blocks.UnmarshalJSON` for + round-trip fidelity. Must be delivered via the streaming chunks API — + `chat.postMessage` rejects it as an unsupported block type. +- **Streaming-message chunks API** — `chat.startStream` / `chat.appendStream` / + `chat.stopStream` now accept a `chunks` parameter. Added `MsgOptionChunks` + along with a `StreamChunk` interface and four chunk types: + `MarkdownTextChunk`, `TaskUpdateChunk`, `PlanUpdateChunk`, and `BlocksChunk` + (each with a `New*Chunk` constructor). This is the supported transport for + streaming Block Kit content and the new agent-UI blocks in particular + (which `chat.postMessage` rejects as `Unsupported block type`). +- **`MsgOptionTaskDisplayMode`** — New option for `chat.startStream` controlling + whether task chunks render as a sequential timeline or a grouped plan. + Accepts `TaskDisplayModeTimeline` or `TaskDisplayModePlan`. +- Added `Username`, `IconURL`, and `IconEmoji` fields to + `AssistantThreadsSetStatusParameters`, forwarded by + `SetAssistantThreadsStatusContext`, matching the new optional parameters on + [`assistant.threads.setStatus`](https://docs.slack.dev/reference/methods/assistant.threads.setStatus) + for customising the status-update presentation. +- Exposed `SocketmodeHandler.DispatchEvent` (previously the unexported + `dispatcher`), enabling integration tests to exercise registered handlers + without a live WebSocket connection. The unexported `dispatcher` is kept as + a thin wrapper for backwards compatibility. Closes #1549. + +## [0.22.0] - 2026-04-12 + +### Added + +- Added missing parameters to `assistant.search.context` (`Sort`, `SortDir`, `Before`, + `After`, `Highlight`, `IncludeContextMessages`, `IncludeDeletedUsers`, + `IncludeMessageBlocks`, `IncludeArchivedChannels`, `DisableSemanticSearch`, `Modifiers`, + `TermClauses`) and new response types (`AssistantSearchContextFile`, + `AssistantSearchContextChannel`, `AssistantSearchContextMessageContext`) to match the + full Real-Time Search API surface. +- Added `Underline`, `Highlight`, `ClientHighlight`, and `Unlink` fields to + `RichTextSectionTextStyle`. Added `Style` field to `RichTextSectionUserGroupElement`. +- Added `BotOptional` and `UserOptional` fields to `OAuthScopes` for app manifests. +- Added PKCE support for OAuth: `OAuthOptionCodeVerifier` option for + `GetOAuthV2Response`, `GenerateCodeVerifier()` and `GenerateCodeChallenge()` + helper functions (RFC 7636). `client_secret` is now conditionally omitted when + empty in both `GetOAuthV2ResponseContext` and `RefreshOAuthV2TokenContext`. + +### Fixed + +- `ChannelTypes` and `ContentTypes` now send comma-separated values instead of repeated + form keys, matching the convention used by every other method in the library. +- In `socketmode` malformed JSON messages no longer force an unnecessary reconnect. + Instead the error is emitted and the connection continues as normal. + +## [0.21.1] - 2026-04-08 + +### Added + +- **`slackevents.ChannelType*` constants and `MessageEvent` helpers** — Added + `ChannelTypeChannel`, `ChannelTypeGroup`, `ChannelTypeIM`, `ChannelTypeMPIM` constants + and `IsChannel()`, `IsGroup()`, `IsIM()`, `IsMpIM()` methods on `MessageEvent` so + callers no longer need to compare raw strings. + +### Fixed + +- **Duplicate attachment/block serialization in `MsgOptionAttachments` / `MsgOptionBlocks`** — + Attachments and blocks were serialized twice: once into typed struct fields (for the JSON + response-URL path) and again into `url.Values` (for the form POST path). Serialization for + the form path now happens inside `formSender.BuildRequestContext`, so each sender owns its + own marshalling. This fixes the long-standing FIXME and eliminates redundant `json.Marshal` + calls in the option functions. ([#1547]) + + > [!NOTE] + > `UnsafeApplyMsgOptions` returns `config.values` directly. After this change, + > `attachments` and `blocks` keys are no longer present in those values since + > marshalling is deferred to send time. This function is documented as unsupported. + +## [0.21.0] - 2026-04-05 + +### Deprecated + +- **`slackevents.ParseActionEvent`** — Cannot parse `block_actions` payloads (returns + unmarshalling error). Use `slack.InteractionCallback` with `json.Unmarshal` instead, + or `slack.InteractionCallbackParse` for HTTP requests. `InteractionCallback` handles + all interaction types. ([#596]) +- **`slackevents.MessageAction`**, **`MessageActionEntity`**, **`MessageActionResponse`** — + Associated types that only support legacy `interactive_message` payloads. + +### Removed + +- **`IM` struct** — Removed the `IM` struct (and unused internal types `imChannel`, + `imResponseFull`). The `IsUserDeleted` field has been moved to `Conversation`, where it + is populated for IM-type conversations. Code using `IM` should switch to `Conversation`. + + > [!NOTE] + > In practice no user should be affected — `IM` was never returned by any public API + > method in this library, so there was no way to obtain one outside of manual construction. + +- **`Info.GetBotByID`, `GetUserByID`, `GetChannelByID`, `GetGroupByID`, `GetIMByID`** — + These methods were deprecated and returned `nil` unconditionally. They have been removed. + + > [!WARNING] + > **Breaking change.** If you are calling any of these methods, remove those calls — they + > were already no-ops. + +### Added + +- **`admin.teams.settings.*` API support** — `AdminTeamsSettingsInfo`, + `AdminTeamsSettingsSetDefaultChannels`, `AdminTeamsSettingsSetDescription`, + `AdminTeamsSettingsSetDiscoverability`, `AdminTeamsSettingsSetIcon`, and + `AdminTeamsSettingsSetName`. Includes `TeamDiscoverability` enum with `Open`, + `InviteOnly`, `Closed`, and `Unlisted` variants. ([#960]) +- **`OAuthOptionAPIURL` for package-level OAuth functions** — All package-level OAuth + functions (`GetOAuthV2Response`, `GetOpenIDConnectToken`, `RefreshOAuthV2Token`, etc.) + now accept variadic `OAuthOption` arguments. Use `OAuthOptionAPIURL(url)` to override + the default Slack API URL, enabling integration tests against a local HTTP server. + Existing callers are unaffected. ([#744]) +- **`GetOpenIDConnectUserInfo` / `GetOpenIDConnectUserInfoContext`** — Returns identity + information about the user associated with the token via `openid.connect.userInfo`. + Complements the existing `GetOpenIDConnectToken` method. ([#967]) +- **HTTP response headers** — Slack API response headers (e.g. `X-OAuth-Scopes`, + `X-Accepted-OAuth-Scopes`, `X-Ratelimit-*`) are now accessible. `AuthTestResponse` + exposes a `Header` field directly. For all other methods, use + `OptionOnResponseHeaders(func(method string, headers http.Header))` to register a + callback that fires after every API call. ([#1076]) +- **`DNDOptionTeamID`** — `GetDNDInfo` and `GetDNDTeamInfo` now accept optional + `ParamOption` arguments. Use `DNDOptionTeamID("T...")` to pass `team_id`, which is + required after workspace migration (Slack returns `missing_argument` without it). + ([#1157]) +- **`UpdateUserGroupMembersList` / `UpdateUserGroupMembersListContext`** — Convenience + wrappers around `UpdateUserGroupMembers` that accept `[]string` instead of a + comma-separated string, enabling clean chaining with `GetUserGroupMembers`. ([#1172]) +- **`SetUserProfile` / `SetUserProfileContext`** — Set multiple user profile fields in a + single API call by passing a `*UserProfile` struct to `users.profile.set`. Complements + the existing single-field methods (`SetUserRealName`, `SetUserCustomStatus`, etc.). + ([#1158]) +- **API warning callbacks** — Slack API responses may include a `warnings` field with + deprecation notices or usage hints. Use `OptionWarnings(func(warnings []string))` to + register a callback that receives these warnings. ([#1540]) +- **RTM support for `user_status_changed`, `user_huddle_changed`, `user_profile_changed` + events** — these events are now mapped in `EventMapping` with dedicated structs + (`UserStatusChangedEvent`, `UserHuddleChangedEvent`, `UserProfileChangedEvent`). + Previously they triggered `UnmarshallingErrorEvent`. ([#1541]) +- **RTM support for `sh_room_join`, `sh_room_leave`, `sh_room_update`, `channel_updated` + events** — Slack Call/Huddle room events and channel property updates are now mapped with + dedicated structs (`SHRoomJoinEvent`, `SHRoomLeaveEvent`, `SHRoomUpdateEvent`, + `ChannelUpdatedEvent`). ([#858]) +- **`CacheTS` and `EventTS` fields on `UserChangeEvent`** — these fields were sent by Slack + but silently dropped during unmarshalling. +- **`workflows.featured` API support** — add, list, remove, and set featured workflows on + channels via `WorkflowsFeaturedAdd`, `WorkflowsFeaturedList`, `WorkflowsFeaturedRemove`, + and `WorkflowsFeaturedSet` +- **`IsConnectorBot` and `IsWorkflowBot` in `User`** — boolean flags for connector and + workflow bot users +- **`GuestInvitedBy` in `UserProfile`** — user ID of whoever invited a guest user +- **`Blocks` field on `MessageEvent`** — block data from webhook payloads is now directly + accessible via `event.Blocks` instead of only through `event.Message.Blocks`. ([#1257]) +- **`Username` field on `User`** — Slack's interaction payloads (block_actions, shortcuts) + include a `username` field in the user object that was previously dropped during + unmarshalling. ([#1218]) +- **`Blocks`, `Attachments`, `Files`, `Upload` fields on `AppMentionEvent`** — these fields + are sent by Slack in `app_mention` event payloads but were silently dropped. ([#961]) +- **`HandleShortcut`, `HandleViewSubmission`, `HandleViewClosed` in socketmode handler** — + Level 3 handlers that dispatch `shortcut`/`message_action`, `view_submission`, and + `view_closed` interactions by `CallbackID`, matching the pattern of + `HandleInteractionBlockAction` and `HandleSlashCommand`. ([#1161]) +- **`BlockFromJSON` / `MustBlockFromJSON`** — Create blocks from raw JSON strings, enabling + direct use of output from Slack's [Block Kit Builder](https://app.slack.com/block-kit-builder) + or quick adoption of new block types before the library adds typed support. The original + JSON is preserved through marshalling. ([#1497]) + +### Documentation + +- **`ViewSubmissionResponse` constructors** — `NewClearViewSubmissionResponse`, + `NewUpdateViewSubmissionResponse`, `NewPushViewSubmissionResponse`, and + `NewErrorsViewSubmissionResponse` now have doc comments explaining the HTTP response + pattern (write JSON and return promptly) and the Socket Mode pattern (pass as Ack + payload). `NewErrorsViewSubmissionResponse` additionally documents that map keys must + be `BlockID`s of `InputBlock` elements. ([#726], [#1013]) +- **Socket Mode examples** — `examples/socketmode/` and `examples/socketmode_handler/` now + have doc comments explaining the two-token requirement: app-level token (`xapp-`) for the + WebSocket connection and bot token (`xoxb-`) for API calls. ([#941]) + +### Fixed + +- **`UnknownBlock` round-trip data loss** — Unrecognized block types (e.g. new Slack block + types not yet supported by this library) now preserve their full JSON through + unmarshal/marshal cycles. Previously only `type` and `block_id` were retained, silently + discarding all other fields. + +### Changed + +- Adjusted some `admin` errors that started with uppercase to be lowercase per go + conventions. + + > [!WARNING] + > **Breaking change.** If you are matching the error content in your code, this is a + > BREAKING CHANGE. +- **`WebhookMessage.UnfurlLinks` and `UnfurlMedia` are now `*bool`** — Previously these + were `bool` with `omitempty`, which meant `false` was silently stripped from the JSON + payload. Users could not explicitly disable link or media unfurling via webhooks. The + fields are now `*bool` so that `nil` (omit), `false`, and `true` all serialize correctly. + ([#1231]) + + > [!WARNING] + > **Breaking change.** Code that sets these fields directly must be updated: + > + > ```go + > // Before + > msg := slack.WebhookMessage{UnfurlLinks: true} + > + > // After — use a helper or a variable + > t := true + > msg := slack.WebhookMessage{UnfurlLinks: &t} + > ``` + > + > Leaving the fields unset (`nil`) preserves the previous default behavior — Slack's + > server-side defaults apply (`unfurl_links=false`, `unfurl_media=true`). + +- **`User.Has2FA` is now `*bool`** — When using a bot token, Slack's `users.list` API omits + `has_2fa` entirely. With a plain `bool`, this was indistinguishable from explicitly `false`. + Now `nil` means absent/unknown, `false` means explicitly disabled, `true` means enabled. + ([#1121]) + + > [!WARNING] + > **Breaking change.** Code that reads `Has2FA` must handle the pointer: + > + > ```go + > // Before + > if user.Has2FA { ... } + > + > // After + > if user.Has2FA != nil && *user.Has2FA { ... } + > ``` + +- **`ListReactions` now uses cursor-based pagination** — `ListReactionsParameters` replaces + `Count`/`Page` with `Cursor`/`Limit`, and `ListReactions`/`ListReactionsContext` now return + `([]ReactedItem, string, error)` where the string is the next cursor, instead of + `([]ReactedItem, *Paging, error)`. ([#825]) + + > [!WARNING] + > **Breaking change.** Both the parameters and return signature have changed: + > + > ```go + > // Before + > params := slack.NewListReactionsParameters() + > params.Count = 100 + > params.Page = 2 + > items, paging, err := api.ListReactions(params) + > + > // After + > params := slack.NewListReactionsParameters() + > params.Limit = 100 + > items, nextCursor, err := api.ListReactions(params) + > // Use nextCursor for the next page: params.Cursor = nextCursor + > ``` + +- **`ListStars`/`GetStarred` now use cursor-based pagination** — `StarsParameters` replaces + `Count`/`Page` with `Cursor`/`Limit` (and adds `TeamID`), and `ListStars`/`ListStarsContext`/ + `GetStarred`/`GetStarredContext` now return `string` (next cursor) instead of `*Paging`. + Slack's `stars.list` API no longer returns `paging` data — only `response_metadata.next_cursor`. + + > [!WARNING] + > **Breaking change.** Both the parameters and return signature have changed: + > + > ```go + > // Before + > params := slack.NewStarsParameters() + > params.Count = 100 + > params.Page = 2 + > items, paging, err := api.ListStars(params) + > + > // After + > params := slack.NewStarsParameters() + > params.Limit = 100 + > items, nextCursor, err := api.ListStars(params) + > // Use nextCursor for the next page: params.Cursor = nextCursor + > ``` + +- **`GetAccessLogs` now uses cursor-based pagination** — `AccessLogParameters` replaces + `Count`/`Page` with `Cursor`/`Limit` (and adds `Before`), and `GetAccessLogs`/ + `GetAccessLogsContext` now return `string` (next cursor) instead of `*Paging`. + Slack's `team.accessLogs` API warns `use_cursor_pagination_instead` when using the old + parameters. + + > [!WARNING] + > **Breaking change.** Both the parameters and return signature have changed: + > + > ```go + > // Before + > params := slack.NewAccessLogParameters() + > params.Count = 100 + > params.Page = 2 + > logins, paging, err := api.GetAccessLogs(params) + > + > // After + > params := slack.NewAccessLogParameters() + > params.Limit = 100 + > logins, nextCursor, err := api.GetAccessLogs(params) + > // Use nextCursor for the next page: params.Cursor = nextCursor + > ``` + +### Fixed + +- **Socket Mode: large Ack payloads no longer silently fail** — Two issues caused `Ack()` + payloads to be silently dropped by Slack. First, gorilla/websocket's default 4KB write + buffer fragmented messages into WebSocket continuation frames that Slack does not + reassemble. The library now uses a 32KB write buffer. Second, Slack silently drops + Socket Mode responses at or above 20KB — `Ack()`, `Send()`, and `SendCtx()` now return + an error when the serialized response reaches this limit. ([#1196]) + + > [!WARNING] + > **Breaking change.** `Ack()` and `Send()` now return `error`. Existing call sites that + > don't capture the return value continue to compile without changes. + +- **`MsgOptionBlocks()` with no arguments now sends `blocks=[]`** — Previously, calling + `MsgOptionBlocks()` with no arguments or a nil spread was a silent no-op, making it + impossible to clear blocks from a message via `chat.update`. The Slack API requires an + explicit `blocks=[]` to reliably remove blocks. ([#1214]) + + > [!WARNING] + > **Breaking change.** `MsgOptionBlocks()` with no arguments now sends `blocks=[]` instead + > of being a no-op. If you were relying on this to be a no-op, remove the option entirely: + > + > ```go + > // Before — this was a no-op, now it sends blocks=[] + > api.PostMessage(ch, slack.MsgOptionBlocks(), slack.MsgOptionText("text", false)) + > + > // After — omit MsgOptionBlocks entirely to not set blocks + > api.PostMessage(ch, slack.MsgOptionText("text", false)) + > ``` + +- **`WorkflowButtonBlockElement` missing from `UnmarshalJSON`** — `workflow_button` blocks + now unmarshal correctly through `BlockElements`, `InputBlock`, and `Accessory` paths. + Also adds missing `multi_*_select` and `file_input` cases to `BlockElements.UnmarshalJSON`, + and fixes `toBlockElement` for `RichTextInputElement` and `WorkflowButtonElement`. ([#1539]) +- **`NewBlockHeader` nil pointer dereference** — passing a nil text object no longer panics. ([#1236]) +- **`ValidateUniqueBlockID` rejects empty block IDs** — multiple input blocks with no + explicit `block_id` set (empty string) were incorrectly flagged as duplicates, causing + `OpenView` to fail. ([#1184]) + ## [0.20.0] - 2026-03-21 > [!WARNING] @@ -154,6 +518,8 @@ Previous release. See [GitHub releases](https://github.com/slack-go/slack/releas for details. [#897]: https://github.com/slack-go/slack/issues/897 +[#1236]: https://github.com/slack-go/slack/issues/1236 +[#1257]: https://github.com/slack-go/slack/issues/1257 [#1144]: https://github.com/slack-go/slack/issues/1144 [#1329]: https://github.com/slack-go/slack/issues/1329 [#1343]: https://github.com/slack-go/slack/issues/1343 @@ -171,6 +537,7 @@ for details. [#1490]: https://github.com/slack-go/slack/pull/1490 [#1491]: https://github.com/slack-go/slack/issues/1491 [#1495]: https://github.com/slack-go/slack/pull/1495 +[#1497]: https://github.com/slack-go/slack/pull/1497 [#1499]: https://github.com/slack-go/slack/pull/1499 [#1504]: https://github.com/slack-go/slack/pull/1504 [#1506]: https://github.com/slack-go/slack/pull/1506 @@ -191,8 +558,20 @@ for details. [#1533]: https://github.com/slack-go/slack/pull/1533 [#1534]: https://github.com/slack-go/slack/pull/1534 [#1536]: https://github.com/slack-go/slack/pull/1536 - -[Unreleased]: https://github.com/slack-go/slack/compare/v0.20.0...HEAD +[#596]: https://github.com/slack-go/slack/issues/596 +[#1541]: https://github.com/slack-go/slack/issues/1541 +[#1172]: https://github.com/slack-go/slack/issues/1172 +[#1076]: https://github.com/slack-go/slack/issues/1076 +[#1157]: https://github.com/slack-go/slack/issues/1157 +[#1196]: https://github.com/slack-go/slack/issues/1196 +[#1547]: https://github.com/slack-go/slack/pull/1547 + +[Unreleased]: https://github.com/slack-go/slack/compare/v0.23.1...HEAD +[0.23.1]: https://github.com/slack-go/slack/compare/v0.23.0...v0.23.1 +[0.23.0]: https://github.com/slack-go/slack/compare/v0.22.0...v0.23.0 +[0.22.0]: https://github.com/slack-go/slack/compare/v0.21.1...0.22.0 +[0.21.1]: https://github.com/slack-go/slack/compare/v0.21.0...v0.21.1 +[0.21.0]: https://github.com/slack-go/slack/compare/v0.20.0...v0.21.0 [0.20.0]: https://github.com/slack-go/slack/compare/v0.19.0...v0.20.0 [0.19.0]: https://github.com/slack-go/slack/compare/v0.18.0...v0.19.0 [0.18.0]: https://github.com/slack-go/slack/compare/v0.18.0-rc2...v0.18.0 diff --git a/vendor/github.com/slack-go/slack/admin.go b/vendor/github.com/slack-go/slack/admin.go index d51426b5..1b0d2178 100644 --- a/vendor/github.com/slack-go/slack/admin.go +++ b/vendor/github.com/slack-go/slack/admin.go @@ -59,7 +59,7 @@ func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, fi err := api.adminRequest(ctx, "invite", teamName, values) if err != nil { - return fmt.Errorf("Failed to invite single-channel guest: %s", err) + return fmt.Errorf("failed to invite single-channel guest: %s", err) } return nil @@ -86,7 +86,7 @@ func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channe err := api.adminRequest(ctx, "invite", teamName, values) if err != nil { - return fmt.Errorf("Failed to restricted account: %s", err) + return fmt.Errorf("failed to restricted account: %s", err) } return nil @@ -110,7 +110,7 @@ func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName, err := api.adminRequest(ctx, "invite", teamName, values) if err != nil { - return fmt.Errorf("Failed to invite to team: %s", err) + return fmt.Errorf("failed to invite to team: %s", err) } return nil @@ -132,7 +132,7 @@ func (api *Client) SetRegularContext(ctx context.Context, teamName, user string) err := api.adminRequest(ctx, "setRegular", teamName, values) if err != nil { - return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err) + return fmt.Errorf("failed to change the user (%s) to a regular user: %s", user, err) } return nil @@ -154,7 +154,7 @@ func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, use err := api.adminRequest(ctx, "sendSSOBind", teamName, values) if err != nil { - return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err) + return fmt.Errorf("failed to send SSO binding email for user (%s): %s", user, err) } return nil @@ -177,7 +177,7 @@ func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid, err := api.adminRequest(ctx, "setUltraRestricted", teamName, values) if err != nil { - return fmt.Errorf("Failed to ultra-restrict account: %s", err) + return fmt.Errorf("failed to ultra-restrict account: %s", err) } return nil diff --git a/vendor/github.com/slack-go/slack/admin_teams.go b/vendor/github.com/slack-go/slack/admin_teams.go new file mode 100644 index 00000000..17ae3df3 --- /dev/null +++ b/vendor/github.com/slack-go/slack/admin_teams.go @@ -0,0 +1,153 @@ +package slack + +import ( + "context" + "net/url" + "strings" +) + +// AdminTeamSettings contains workspace settings returned by admin.teams.settings.info. +type AdminTeamSettings struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Domain string `json:"domain"` + EmailDomain string `json:"email_domain"` + AvatarBaseURL string `json:"avatar_base_url"` + IsVerified bool `json:"is_verified"` + Icon TeamSettingsIcon `json:"icon"` + EnterpriseID string `json:"enterprise_id"` + EnterpriseName string `json:"enterprise_name"` + EnterpriseDomain string `json:"enterprise_domain"` + DefaultChannels []string `json:"default_channels"` +} + +// TeamSettingsIcon contains team icon URLs and a default flag. +type TeamSettingsIcon struct { + ImageDefault bool `json:"image_default"` + Image34 string `json:"image_34"` + Image44 string `json:"image_44"` + Image68 string `json:"image_68"` + Image88 string `json:"image_88"` + Image102 string `json:"image_102"` + Image132 string `json:"image_132"` + Image230 string `json:"image_230"` +} + +// TeamDiscoverability represents the discoverability setting for a workspace. +type TeamDiscoverability string + +const ( + TeamDiscoverabilityOpen TeamDiscoverability = "open" + TeamDiscoverabilityInviteOnly TeamDiscoverability = "invite_only" + TeamDiscoverabilityClosed TeamDiscoverability = "closed" + TeamDiscoverabilityUnlisted TeamDiscoverability = "unlisted" +) + +type adminTeamSettingsInfoResponse struct { + Team AdminTeamSettings `json:"team"` + SlackResponse +} + +// AdminTeamsSettingsInfo returns workspace settings. +// Slack API docs: https://docs.slack.dev/reference/methods/admin.teams.settings.info +func (api *Client) AdminTeamsSettingsInfo(ctx context.Context, teamID string) (*AdminTeamSettings, error) { + values := url.Values{ + "token": {api.token}, + "team_id": {teamID}, + } + + response := &adminTeamSettingsInfoResponse{} + err := api.postMethod(ctx, "admin.teams.settings.info", values, response) + if err != nil { + return nil, err + } + + return &response.Team, response.Err() +} + +// AdminTeamsSettingsSetDefaultChannels sets the default channels for a workspace. +// Slack API docs: https://docs.slack.dev/reference/methods/admin.teams.settings.setDefaultChannels +func (api *Client) AdminTeamsSettingsSetDefaultChannels(ctx context.Context, teamID string, channelIDs ...string) error { + values := url.Values{ + "token": {api.token}, + "team_id": {teamID}, + "channel_ids": {strings.Join(channelIDs, ",")}, + } + + response := &SlackResponse{} + err := api.postMethod(ctx, "admin.teams.settings.setDefaultChannels", values, response) + if err != nil { + return err + } + return response.Err() +} + +// AdminTeamsSettingsSetDescription sets the description for a workspace. +// Slack API docs: https://docs.slack.dev/reference/methods/admin.teams.settings.setDescription +func (api *Client) AdminTeamsSettingsSetDescription(ctx context.Context, teamID, description string) error { + values := url.Values{ + "token": {api.token}, + "team_id": {teamID}, + "description": {description}, + } + + response := &SlackResponse{} + err := api.postMethod(ctx, "admin.teams.settings.setDescription", values, response) + if err != nil { + return err + } + return response.Err() +} + +// AdminTeamsSettingsSetDiscoverability sets the discoverability for a workspace. +// The discoverability parameter must be one of: open, invite_only, closed, or unlisted. +// Slack API docs: https://docs.slack.dev/reference/methods/admin.teams.settings.setDiscoverability +func (api *Client) AdminTeamsSettingsSetDiscoverability(ctx context.Context, teamID string, discoverability TeamDiscoverability) error { + values := url.Values{ + "token": {api.token}, + "team_id": {teamID}, + "discoverability": {string(discoverability)}, + } + + response := &SlackResponse{} + err := api.postMethod(ctx, "admin.teams.settings.setDiscoverability", values, response) + if err != nil { + return err + } + return response.Err() +} + +// AdminTeamsSettingsSetIcon sets the icon for a workspace. +// Slack API docs: https://docs.slack.dev/reference/methods/admin.teams.settings.setIcon +func (api *Client) AdminTeamsSettingsSetIcon(ctx context.Context, teamID, imageURL string) error { + values := url.Values{ + "token": {api.token}, + "team_id": {teamID}, + "image_url": {imageURL}, + } + + response := &SlackResponse{} + err := api.postMethod(ctx, "admin.teams.settings.setIcon", values, response) + if err != nil { + return err + } + return response.Err() +} + +// AdminTeamsSettingsSetName sets the name for a workspace. +// Slack API docs: https://docs.slack.dev/reference/methods/admin.teams.settings.setName +func (api *Client) AdminTeamsSettingsSetName(ctx context.Context, teamID, name string) error { + values := url.Values{ + "token": {api.token}, + "team_id": {teamID}, + "name": {name}, + } + + response := &SlackResponse{} + err := api.postMethod(ctx, "admin.teams.settings.setName", values, response) + if err != nil { + return err + } + return response.Err() +} diff --git a/vendor/github.com/slack-go/slack/apps.go b/vendor/github.com/slack-go/slack/apps.go index 7322b15b..c75569fb 100644 --- a/vendor/github.com/slack-go/slack/apps.go +++ b/vendor/github.com/slack-go/slack/apps.go @@ -35,7 +35,7 @@ func (api *Client) ListEventAuthorizationsContext(ctx context.Context, eventCont "event_context": eventContext, }) - err := postJSON(ctx, api.httpclient, api.endpoint+"apps.event.authorizations.list", api.appLevelToken, request, &resp, api) + err := api.postJSONMethod(ctx, "apps.event.authorizations.list", api.appLevelToken, request, &resp) if err != nil { return nil, err diff --git a/vendor/github.com/slack-go/slack/assistant.go b/vendor/github.com/slack-go/slack/assistant.go index d95e68a5..fb82cb64 100644 --- a/vendor/github.com/slack-go/slack/assistant.go +++ b/vendor/github.com/slack-go/slack/assistant.go @@ -14,6 +14,9 @@ type AssistantThreadsSetStatusParameters struct { Status string `json:"status"` ThreadTS string `json:"thread_ts"` LoadingMessages []string `json:"loading_messages,omitempty"` + Username string `json:"username,omitempty"` + IconURL string `json:"icon_url,omitempty"` + IconEmoji string `json:"icon_emoji,omitempty"` } // AssistantThreadSetTitleParameters are the parameters for AssistantThreadSetTitle @@ -39,30 +42,82 @@ type AssistantThreadsPrompt struct { // AssistantSearchContextParameters are the parameters for AssistantSearchContext type AssistantSearchContextParameters struct { - Query string `json:"query"` - ActionToken string `json:"action_token,omitempty"` - ChannelTypes []string `json:"channel_types,omitempty"` - ContentTypes []string `json:"content_types,omitempty"` - ContextChannelID string `json:"context_channel_id,omitempty"` - Cursor string `json:"cursor,omitempty"` - IncludeBots bool `json:"include_bots,omitempty"` - Limit int `json:"limit,omitempty"` + Query string `json:"query"` + ActionToken string `json:"action_token,omitempty"` + ChannelTypes []string `json:"channel_types,omitempty"` + ContentTypes []string `json:"content_types,omitempty"` + ContextChannelID string `json:"context_channel_id,omitempty"` + Cursor string `json:"cursor,omitempty"` + IncludeBots bool `json:"include_bots,omitempty"` + Limit int `json:"limit,omitempty"` + IncludeDeletedUsers bool `json:"include_deleted_users,omitempty"` + Before int64 `json:"before,omitempty"` + After int64 `json:"after,omitempty"` + IncludeContextMessages bool `json:"include_context_messages,omitempty"` + Sort string `json:"sort,omitempty"` + SortDir string `json:"sort_dir,omitempty"` + IncludeMessageBlocks bool `json:"include_message_blocks,omitempty"` + Highlight bool `json:"highlight,omitempty"` + TermClauses []string `json:"term_clauses,omitempty"` + Modifiers string `json:"modifiers,omitempty"` + IncludeArchivedChannels bool `json:"include_archived_channels,omitempty"` + DisableSemanticSearch bool `json:"disable_semantic_search,omitempty"` } // AssistantSearchContextMessage represents a search result message type AssistantSearchContextMessage struct { - AuthorUserID string `json:"author_user_id"` - TeamID string `json:"team_id"` - ChannelID string `json:"channel_id"` - MessageTS string `json:"message_ts"` - Content string `json:"content"` - IsAuthorBot bool `json:"is_author_bot"` - Permalink string `json:"permalink"` + AuthorUserID string `json:"author_user_id"` + AuthorName string `json:"author_name,omitempty"` + TeamID string `json:"team_id"` + ChannelID string `json:"channel_id"` + ChannelName string `json:"channel_name,omitempty"` + MessageTS string `json:"message_ts"` + Content string `json:"content"` + IsAuthorBot bool `json:"is_author_bot"` + Permalink string `json:"permalink"` + Blocks Blocks `json:"blocks,omitempty"` + ContextMessages *AssistantSearchContextMessageContext `json:"context_messages,omitempty"` +} + +// AssistantSearchContextMessageContext contains context messages surrounding a search result +type AssistantSearchContextMessageContext struct { + Before []AssistantSearchContextMessage `json:"before"` + After []AssistantSearchContextMessage `json:"after"` +} + +// AssistantSearchContextFile represents a search result file +type AssistantSearchContextFile struct { + UploaderUserID string `json:"uploader_user_id"` + AuthorUserID string `json:"author_user_id"` + AuthorName string `json:"author_name"` + TeamID string `json:"team_id"` + FileID string `json:"file_id"` + DateCreated int64 `json:"date_created"` + DateUpdated int64 `json:"date_updated"` + Title string `json:"title"` + FileType string `json:"file_type"` + Permalink string `json:"permalink"` + Content string `json:"content"` +} + +// AssistantSearchContextChannel represents a search result channel +type AssistantSearchContextChannel struct { + TeamID string `json:"team_id"` + CreatorUserID string `json:"creator_user_id"` + CreatorName string `json:"creator_name"` + DateCreated int64 `json:"date_created"` + DateUpdated int64 `json:"date_updated"` + Name string `json:"name"` + Topic string `json:"topic"` + Purpose string `json:"purpose"` + Permalink string `json:"permalink"` } // AssistantSearchContextResults contains the search results type AssistantSearchContextResults struct { - Messages []AssistantSearchContextMessage `json:"messages"` + Messages []AssistantSearchContextMessage `json:"messages,omitempty"` + Files []AssistantSearchContextFile `json:"files,omitempty"` + Channels []AssistantSearchContextChannel `json:"channels,omitempty"` } // AssistantSearchContextResponse is the response from assistant.search.context @@ -126,13 +181,17 @@ func (api *Client) SetAssistantThreadsSuggestedPromptsContext(ctx context.Contex return response.Err() } -// SetAssistantThreadStatus sets the status of a thread +// SetAssistantThreadsStatus sets the status of a thread. +// This method accepts either the chat:write or assistant:write scope. +// Note: the assistant:write scope is being deprecated in favor of chat:write. // @see https://api.slack.com/methods/assistant.threads.setStatus func (api *Client) SetAssistantThreadsStatus(params AssistantThreadsSetStatusParameters) (err error) { return api.SetAssistantThreadsStatusContext(context.Background(), params) } -// SetAssistantThreadStatusContext sets the status of a thread with a custom context +// SetAssistantThreadsStatusContext sets the status of a thread with a custom context. +// This method accepts either the chat:write or assistant:write scope. +// Note: the assistant:write scope is being deprecated in favor of chat:write. // @see https://api.slack.com/methods/assistant.threads.setStatus func (api *Client) SetAssistantThreadsStatusContext(ctx context.Context, params AssistantThreadsSetStatusParameters) (err error) { @@ -153,6 +212,18 @@ func (api *Client) SetAssistantThreadsStatusContext(ctx context.Context, params values.Add("loading_messages", strings.Join(params.LoadingMessages, ",")) } + if params.Username != "" { + values.Add("username", params.Username) + } + + if params.IconURL != "" { + values.Add("icon_url", params.IconURL) + } + + if params.IconEmoji != "" { + values.Add("icon_emoji", params.IconEmoji) + } + response := struct { SlackResponse }{} @@ -224,15 +295,11 @@ func (api *Client) SearchAssistantContextContext(ctx context.Context, params Ass } if len(params.ChannelTypes) > 0 { - for _, channelType := range params.ChannelTypes { - values.Add("channel_types", channelType) - } + values.Add("channel_types", strings.Join(params.ChannelTypes, ",")) } if len(params.ContentTypes) > 0 { - for _, contentType := range params.ContentTypes { - values.Add("content_types", contentType) - } + values.Add("content_types", strings.Join(params.ContentTypes, ",")) } if params.ContextChannelID != "" { @@ -251,6 +318,54 @@ func (api *Client) SearchAssistantContextContext(ctx context.Context, params Ass values.Add("limit", strconv.Itoa(params.Limit)) } + if params.IncludeDeletedUsers { + values.Add("include_deleted_users", "true") + } + + if params.Before > 0 { + values.Add("before", strconv.FormatInt(params.Before, 10)) + } + + if params.After > 0 { + values.Add("after", strconv.FormatInt(params.After, 10)) + } + + if params.IncludeContextMessages { + values.Add("include_context_messages", "true") + } + + if params.Sort != "" { + values.Add("sort", params.Sort) + } + + if params.SortDir != "" { + values.Add("sort_dir", params.SortDir) + } + + if params.IncludeMessageBlocks { + values.Add("include_message_blocks", "true") + } + + if params.Highlight { + values.Add("highlight", "true") + } + + if len(params.TermClauses) > 0 { + values.Add("term_clauses", strings.Join(params.TermClauses, ",")) + } + + if params.Modifiers != "" { + values.Add("modifiers", params.Modifiers) + } + + if params.IncludeArchivedChannels { + values.Add("include_archived_channels", "true") + } + + if params.DisableSemanticSearch { + values.Add("disable_semantic_search", "true") + } + response := &AssistantSearchContextResponse{} err := api.postMethod(ctx, "assistant.search.context", values, response) diff --git a/vendor/github.com/slack-go/slack/audit.go b/vendor/github.com/slack-go/slack/audit.go index bb9ae6dc..135a68d7 100644 --- a/vendor/github.com/slack-go/slack/audit.go +++ b/vendor/github.com/slack-go/slack/audit.go @@ -108,7 +108,7 @@ type AuditLogParameters struct { func (api *Client) auditLogsRequest(ctx context.Context, path string, values url.Values) (*AuditLogResponse, error) { response := &AuditLogResponse{} // The Audit Logs API uses a different base URL (api.slack.com instead of slack.com/api) - err := getResource(ctx, api.httpclient, api.auditEndpoint+path, api.token, values, response, api) + _, err := getResource(ctx, api.httpclient, api.auditEndpoint+path, api.token, values, response, api) if err != nil { return nil, err } diff --git a/vendor/github.com/slack-go/slack/block.go b/vendor/github.com/slack-go/slack/block.go index 8963c7af..cc5d80f2 100644 --- a/vendor/github.com/slack-go/slack/block.go +++ b/vendor/github.com/slack-go/slack/block.go @@ -21,6 +21,9 @@ const ( MBTTable MessageBlockType = "table" MBTTaskCard MessageBlockType = "task_card" MBTPlan MessageBlockType = "plan" + MBTAlert MessageBlockType = "alert" + MBTCard MessageBlockType = "card" + MBTCarousel MessageBlockType = "carousel" ) // Block defines an interface all block types should implement diff --git a/vendor/github.com/slack-go/slack/block_alert.go b/vendor/github.com/slack-go/slack/block_alert.go new file mode 100644 index 00000000..ddb151b4 --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_alert.go @@ -0,0 +1,70 @@ +package slack + +// AlertLevel defines the severity for an AlertBlock. +type AlertLevel string + +const ( + AlertLevelDefault AlertLevel = "default" + AlertLevelInfo AlertLevel = "info" + AlertLevelWarning AlertLevel = "warning" + AlertLevelError AlertLevel = "error" + AlertLevelSuccess AlertLevel = "success" +) + +// AlertBlock defines a block of type alert used to surface a notification +// message with an optional severity level. +// +// Surface: modal only. Slack rejects alert blocks sent via chat.postMessage +// or the streaming APIs — use OpenView / UpdateView / PushView with a +// ModalViewRequest whose Blocks include the alert. +// +// More Information: https://docs.slack.dev/reference/block-kit/blocks/alert-block/ +type AlertBlock struct { + Type MessageBlockType `json:"type"` + Text *TextBlockObject `json:"text"` + Level AlertLevel `json:"level,omitempty"` + BlockID string `json:"block_id,omitempty"` +} + +// BlockType returns the type of the block +func (s AlertBlock) BlockType() MessageBlockType { + return s.Type +} + +// ID returns the ID of the block +func (s AlertBlock) ID() string { + return s.BlockID +} + +// AlertBlockOption allows configuration of options for a new alert block +type AlertBlockOption func(*AlertBlock) + +// AlertBlockOptionLevel sets the severity level for the alert block +func AlertBlockOptionLevel(level AlertLevel) AlertBlockOption { + return func(block *AlertBlock) { + block.Level = level + } +} + +// AlertBlockOptionBlockID sets the block ID for the alert block +func AlertBlockOptionBlockID(blockID string) AlertBlockOption { + return func(block *AlertBlock) { + block.BlockID = blockID + } +} + +// NewAlertBlock returns a new instance of an alert block +func NewAlertBlock(text *TextBlockObject, options ...AlertBlockOption) *AlertBlock { + block := AlertBlock{ + Type: MBTAlert, + Text: text, + } + + for _, option := range options { + if option != nil { + option(&block) + } + } + + return &block +} diff --git a/vendor/github.com/slack-go/slack/block_card.go b/vendor/github.com/slack-go/slack/block_card.go new file mode 100644 index 00000000..ccee1742 --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_card.go @@ -0,0 +1,90 @@ +package slack + +// CardBlock defines a block of type card used to display a rich, self-contained +// piece of content with an optional hero image, icon, title, subtitle, body, +// and action buttons. Cards can stand alone or be grouped inside a +// CarouselBlock. +// +// More Information: https://docs.slack.dev/reference/block-kit/blocks/card-block/ +type CardBlock struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` + HeroImage *ImageBlockElement `json:"hero_image,omitempty"` + Icon *ImageBlockElement `json:"icon,omitempty"` + Title *TextBlockObject `json:"title,omitempty"` + Subtitle *TextBlockObject `json:"subtitle,omitempty"` + Body *TextBlockObject `json:"body,omitempty"` + Actions *BlockElements `json:"actions,omitempty"` +} + +// BlockType returns the type of the block +func (s CardBlock) BlockType() MessageBlockType { + return s.Type +} + +// ID returns the ID of the block +func (s CardBlock) ID() string { + return s.BlockID +} + +// CardBlockOption allows configuration of options for a new card block +type CardBlockOption func(*CardBlock) + +// CardBlockOptionBlockID sets the block ID for the card block +func CardBlockOptionBlockID(blockID string) CardBlockOption { + return func(block *CardBlock) { + block.BlockID = blockID + } +} + +// NewCardBlock returns a new instance of a card block. Use the chainable +// With* methods or provide options to populate its fields. +func NewCardBlock(options ...CardBlockOption) *CardBlock { + block := CardBlock{ + Type: MBTCard, + } + + for _, option := range options { + if option != nil { + option(&block) + } + } + + return &block +} + +// WithTitle sets the title text for the CardBlock +func (s *CardBlock) WithTitle(title *TextBlockObject) *CardBlock { + s.Title = title + return s +} + +// WithSubtitle sets the subtitle text for the CardBlock +func (s *CardBlock) WithSubtitle(subtitle *TextBlockObject) *CardBlock { + s.Subtitle = subtitle + return s +} + +// WithBody sets the body text for the CardBlock +func (s *CardBlock) WithBody(body *TextBlockObject) *CardBlock { + s.Body = body + return s +} + +// WithIcon sets the icon image for the CardBlock +func (s *CardBlock) WithIcon(icon *ImageBlockElement) *CardBlock { + s.Icon = icon + return s +} + +// WithHeroImage sets the hero image for the CardBlock +func (s *CardBlock) WithHeroImage(heroImage *ImageBlockElement) *CardBlock { + s.HeroImage = heroImage + return s +} + +// WithActions sets the action buttons displayed at the bottom of the card +func (s *CardBlock) WithActions(elements ...BlockElement) *CardBlock { + s.Actions = &BlockElements{ElementSet: elements} + return s +} diff --git a/vendor/github.com/slack-go/slack/block_carousel.go b/vendor/github.com/slack-go/slack/block_carousel.go new file mode 100644 index 00000000..5d407db9 --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_carousel.go @@ -0,0 +1,42 @@ +package slack + +// CarouselBlock defines a block of type carousel that displays a scrollable +// list of cards. A carousel must contain between 1 and 10 cards. +// +// More Information: https://docs.slack.dev/reference/block-kit/blocks/carousel-block/ +type CarouselBlock struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` + Elements []*CardBlock `json:"elements"` +} + +// BlockType returns the type of the block +func (s CarouselBlock) BlockType() MessageBlockType { + return s.Type +} + +// ID returns the ID of the block +func (s CarouselBlock) ID() string { + return s.BlockID +} + +// NewCarouselBlock returns a new instance of a carousel block containing the +// given cards. +func NewCarouselBlock(cards ...*CardBlock) *CarouselBlock { + return &CarouselBlock{ + Type: MBTCarousel, + Elements: cards, + } +} + +// WithBlockID sets the block ID for the CarouselBlock +func (s *CarouselBlock) WithBlockID(blockID string) *CarouselBlock { + s.BlockID = blockID + return s +} + +// AddCard appends a card to the carousel +func (s *CarouselBlock) AddCard(card *CardBlock) *CarouselBlock { + s.Elements = append(s.Elements, card) + return s +} diff --git a/vendor/github.com/slack-go/slack/block_conv.go b/vendor/github.com/slack-go/slack/block_conv.go index cba651fc..b83ad831 100644 --- a/vendor/github.com/slack-go/slack/block_conv.go +++ b/vendor/github.com/slack-go/slack/block_conv.go @@ -81,10 +81,21 @@ func (b *Blocks) UnmarshalJSON(data []byte) error { block = &TableBlock{} case "task_card": block = &TaskCardBlock{} + case "alert": + block = &AlertBlock{} case "plan": block = &PlanBlock{} + case "card": + block = &CardBlock{} + case "carousel": + block = &CarouselBlock{} default: - block = &UnknownBlock{} + b := &UnknownBlock{raw: r} + if err = json.Unmarshal(r, b); err != nil { + return err + } + blocks.BlockSet = append(blocks.BlockSet, b) + continue } err = json.Unmarshal(r, block) @@ -153,6 +164,8 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error { e = &FeedbackButtonsBlockElement{} case "icon_button": e = &IconButtonBlockElement{} + case "workflow_button": + e = &WorkflowButtonBlockElement{} default: return fmt.Errorf("unsupported block element type %v", s.TypeVal) } @@ -231,12 +244,18 @@ func (b *BlockElements) UnmarshalJSON(data []byte) error { blockElement = &RadioButtonsBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": blockElement = &SelectBlockElement{} + case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select": + blockElement = &MultiSelectBlockElement{} case "number_input": blockElement = &NumberInputBlockElement{} + case "file_input": + blockElement = &FileInputBlockElement{} case "feedback_buttons": blockElement = &FeedbackButtonsBlockElement{} case "icon_button": blockElement = &IconButtonBlockElement{} + case "workflow_button": + blockElement = &WorkflowButtonBlockElement{} default: return fmt.Errorf("unsupported block element type %v", blockElementType) } @@ -357,6 +376,12 @@ func (a *Accessory) UnmarshalJSON(data []byte) error { return err } a.CheckboxGroupsBlockElement = element.(*CheckboxGroupsBlockElement) + case "workflow_button": + element, err := unmarshalBlockElement(r, &WorkflowButtonBlockElement{}) + if err != nil { + return err + } + a.WorkflowButtonElement = element.(*WorkflowButtonBlockElement) default: element, err := unmarshalBlockElement(r, &UnknownBlockElement{}) if err != nil { @@ -407,6 +432,12 @@ func toBlockElement(element *Accessory) BlockElement { if element.MultiSelectElement != nil { return element.MultiSelectElement } + if element.RichTextInputElement != nil { + return element.RichTextInputElement + } + if element.WorkflowButtonElement != nil { + return element.WorkflowButtonElement + } return nil } diff --git a/vendor/github.com/slack-go/slack/block_element.go b/vendor/github.com/slack-go/slack/block_element.go index b271f132..12849340 100644 --- a/vendor/github.com/slack-go/slack/block_element.go +++ b/vendor/github.com/slack-go/slack/block_element.go @@ -67,31 +67,31 @@ type Accessory struct { // NewAccessory returns a new Accessory for a given block element func NewAccessory(element BlockElement) *Accessory { - switch element.(type) { + switch element := element.(type) { case *ImageBlockElement: - return &Accessory{ImageElement: element.(*ImageBlockElement)} + return &Accessory{ImageElement: element} case *ButtonBlockElement: - return &Accessory{ButtonElement: element.(*ButtonBlockElement)} + return &Accessory{ButtonElement: element} case *OverflowBlockElement: - return &Accessory{OverflowElement: element.(*OverflowBlockElement)} + return &Accessory{OverflowElement: element} case *DatePickerBlockElement: - return &Accessory{DatePickerElement: element.(*DatePickerBlockElement)} + return &Accessory{DatePickerElement: element} case *TimePickerBlockElement: - return &Accessory{TimePickerElement: element.(*TimePickerBlockElement)} + return &Accessory{TimePickerElement: element} case *PlainTextInputBlockElement: - return &Accessory{PlainTextInputElement: element.(*PlainTextInputBlockElement)} + return &Accessory{PlainTextInputElement: element} case *RichTextInputBlockElement: - return &Accessory{RichTextInputElement: element.(*RichTextInputBlockElement)} + return &Accessory{RichTextInputElement: element} case *RadioButtonsBlockElement: - return &Accessory{RadioButtonsElement: element.(*RadioButtonsBlockElement)} + return &Accessory{RadioButtonsElement: element} case *SelectBlockElement: - return &Accessory{SelectElement: element.(*SelectBlockElement)} + return &Accessory{SelectElement: element} case *MultiSelectBlockElement: - return &Accessory{MultiSelectElement: element.(*MultiSelectBlockElement)} + return &Accessory{MultiSelectElement: element} case *CheckboxGroupsBlockElement: - return &Accessory{CheckboxGroupsBlockElement: element.(*CheckboxGroupsBlockElement)} + return &Accessory{CheckboxGroupsBlockElement: element} case *WorkflowButtonBlockElement: - return &Accessory{WorkflowButtonElement: element.(*WorkflowButtonBlockElement)} + return &Accessory{WorkflowButtonElement: element} default: return &Accessory{UnknownElement: element.(*UnknownBlockElement)} } diff --git a/vendor/github.com/slack-go/slack/block_header.go b/vendor/github.com/slack-go/slack/block_header.go index 3afb4c95..4277057f 100644 --- a/vendor/github.com/slack-go/slack/block_header.go +++ b/vendor/github.com/slack-go/slack/block_header.go @@ -36,7 +36,9 @@ func NewHeaderBlock(textObj *TextBlockObject, options ...HeaderBlockOption) *Hea } for _, option := range options { - option(&block) + if option != nil { + option(&block) + } } return &block diff --git a/vendor/github.com/slack-go/slack/block_json.go b/vendor/github.com/slack-go/slack/block_json.go new file mode 100644 index 00000000..43798f51 --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_json.go @@ -0,0 +1,110 @@ +package slack + +import ( + "encoding/json" + "fmt" +) + +// RawJSONBlock represents a block created from raw JSON that preserves +// the original JSON structure. This is useful for testing new Slack block types +// before the library has full support, or for using blocks copied from Block Kit Builder. +// +// The block stores the original JSON and outputs it unchanged during marshalling, +// ensuring no data is lost through the unmarshal/marshal cycle. +type RawJSONBlock struct { + Type MessageBlockType `json:"-"` + BlockID string `json:"-"` + raw json.RawMessage +} + +// BlockType returns the type of the block +func (r RawJSONBlock) BlockType() MessageBlockType { + return r.Type +} + +// ID returns the block_id of the block +func (r RawJSONBlock) ID() string { + return r.BlockID +} + +// MarshalJSON outputs the original JSON unchanged +func (r RawJSONBlock) MarshalJSON() ([]byte, error) { + return r.raw, nil +} + +// BlockFromJSON creates a RawJSONBlock from a JSON string that preserves +// the original JSON. This is useful for quickly testing blocks from Slack's +// Block Kit Builder or for incorporating new block types before the library +// has full support. +// +// The JSON can be either a single block object or an array of blocks. +// If an array is provided, only the first block is returned. +// +// The returned block stores the original JSON and outputs it unchanged during +// marshalling, ensuring no data is lost. +// +// Returns an error if the JSON is invalid, empty, or missing required fields. +// +// Example: +// +// block, err := slack.BlockFromJSON(`{"type": "section", "text": {"type": "mrkdwn", "text": "Hello"}}`) +// if err != nil { +// return err +// } +// blocks = append(blocks, block) +func BlockFromJSON(jsonStr string) (Block, error) { + var rawJSON json.RawMessage + var isArray bool + + // Try to unmarshal as an array first + var arrayTest []json.RawMessage + if err := json.Unmarshal([]byte(jsonStr), &arrayTest); err == nil && len(arrayTest) > 0 { + rawJSON = arrayTest[0] + isArray = true + } else { + // Try as a single block object + if err := json.Unmarshal([]byte(jsonStr), &rawJSON); err != nil { + return nil, fmt.Errorf("failed to unmarshal block JSON: %w", err) + } + isArray = false + } + + if !isArray && len(rawJSON) == 0 { + return nil, fmt.Errorf("no blocks found in JSON") + } + + // Extract minimal fields for Block interface + var minimal struct { + Type string `json:"type"` + BlockID string `json:"block_id"` + } + if err := json.Unmarshal(rawJSON, &minimal); err != nil { + return nil, fmt.Errorf("failed to extract block type: %w", err) + } + + if minimal.Type == "" { + return nil, fmt.Errorf("block missing required 'type' field") + } + + return RawJSONBlock{ + Type: MessageBlockType(minimal.Type), + BlockID: minimal.BlockID, + raw: rawJSON, + }, nil +} + +// MustBlockFromJSON creates a Block from a JSON string and panics if there's an error. +// This is primarily intended for use in tests or examples where the JSON is known to be valid. +// For production code, use BlockFromJSON which returns an error instead. +// +// Example: +// +// block := slack.MustBlockFromJSON(`{"type": "divider"}`) +// msg := slack.NewBlockMessage(block) +func MustBlockFromJSON(jsonStr string) Block { + block, err := BlockFromJSON(jsonStr) + if err != nil { + panic(fmt.Sprintf("MustBlockFromJSON: %v", err)) + } + return block +} diff --git a/vendor/github.com/slack-go/slack/block_object.go b/vendor/github.com/slack-go/slack/block_object.go index fd73b6c4..c1b64cc4 100644 --- a/vendor/github.com/slack-go/slack/block_object.go +++ b/vendor/github.com/slack-go/slack/block_object.go @@ -11,7 +11,6 @@ import ( // BlockObject defines an interface that all block object types should // implement. -// @TODO: Is this interface needed? // blockObject object types const ( diff --git a/vendor/github.com/slack-go/slack/block_rich_text.go b/vendor/github.com/slack-go/slack/block_rich_text.go index f0c1eb22..3b0ead2e 100644 --- a/vendor/github.com/slack-go/slack/block_rich_text.go +++ b/vendor/github.com/slack-go/slack/block_rich_text.go @@ -103,6 +103,10 @@ func (u RichTextUnknown) RichTextElementType() RichTextElementType { return u.Type } +func (u RichTextUnknown) MarshalJSON() ([]byte, error) { + return []byte(u.Raw), nil +} + type RichTextListElementType string const ( @@ -294,10 +298,14 @@ type RichTextSectionElement interface { } type RichTextSectionTextStyle struct { - Bold bool `json:"bold,omitempty"` - Italic bool `json:"italic,omitempty"` - Strike bool `json:"strike,omitempty"` - Code bool `json:"code,omitempty"` + Bold bool `json:"bold,omitempty"` + Italic bool `json:"italic,omitempty"` + Strike bool `json:"strike,omitempty"` + Code bool `json:"code,omitempty"` + Underline bool `json:"underline,omitempty"` + Highlight bool `json:"highlight,omitempty"` + ClientHighlight bool `json:"client_highlight,omitempty"` + Unlink bool `json:"unlink,omitempty"` } type RichTextSectionTextElement struct { @@ -416,6 +424,7 @@ func NewRichTextSectionTeamElement(teamID string, style *RichTextSectionTextStyl type RichTextSectionUserGroupElement struct { Type RichTextSectionElementType `json:"type"` UsergroupID string `json:"usergroup_id"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` } func (r RichTextSectionUserGroupElement) RichTextSectionElementType() RichTextSectionElementType { @@ -492,6 +501,10 @@ func (r RichTextSectionUnknownElement) RichTextSectionElementType() RichTextSect return r.Type } +func (r RichTextSectionUnknownElement) MarshalJSON() ([]byte, error) { + return []byte(r.Raw), nil +} + // RichTextQuote represents rich_text_quote element type. type RichTextQuote struct { Type RichTextElementType `json:"type"` diff --git a/vendor/github.com/slack-go/slack/block_unknown.go b/vendor/github.com/slack-go/slack/block_unknown.go index 7a49a2c8..71b2a90c 100644 --- a/vendor/github.com/slack-go/slack/block_unknown.go +++ b/vendor/github.com/slack-go/slack/block_unknown.go @@ -1,10 +1,19 @@ package slack -// UnknownBlock represents a block type that is not yet known. This block type exists to prevent Slack from introducing -// new and unknown block types that break this library. +import "encoding/json" + +// UnknownBlock represents a block type that is not yet known. This block type +// exists to prevent Slack from introducing new and unknown block types that +// break this library. It preserves the raw JSON so that unrecognized blocks +// survive round-trip marshaling. +// +// If you encounter an UnknownBlock for a block type that Slack documents, +// please open an issue at https://github.com/slack-go/slack/issues so we can +// add first-class support for it. type UnknownBlock struct { Type MessageBlockType `json:"type"` BlockID string `json:"block_id,omitempty"` + raw json.RawMessage } // BlockType returns the type of the block @@ -16,3 +25,12 @@ func (b UnknownBlock) BlockType() MessageBlockType { func (s UnknownBlock) ID() string { return s.BlockID } + +// MarshalJSON returns the original raw JSON if available, preserving all fields +func (b UnknownBlock) MarshalJSON() ([]byte, error) { + if b.raw != nil { + return b.raw, nil + } + type alias UnknownBlock + return json.Marshal(alias(b)) +} diff --git a/vendor/github.com/slack-go/slack/channels.go b/vendor/github.com/slack-go/slack/channels.go index 88d567bf..d01ce823 100644 --- a/vendor/github.com/slack-go/slack/channels.go +++ b/vendor/github.com/slack-go/slack/channels.go @@ -28,7 +28,7 @@ type Channel struct { func (api *Client) channelRequest(ctx context.Context, path string, values url.Values) (*channelResponseFull, error) { response := &channelResponseFull{} - err := postForm(ctx, api.httpclient, api.endpoint+path, values, response, api) + _, err := postForm(ctx, api.httpclient, api.endpoint+path, values, response, api) if err != nil { return nil, err } diff --git a/vendor/github.com/slack-go/slack/chat.go b/vendor/github.com/slack-go/slack/chat.go index f1832d9a..a58b4e80 100644 --- a/vendor/github.com/slack-go/slack/chat.go +++ b/vendor/github.com/slack-go/slack/chat.go @@ -264,7 +264,7 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt api.Debugf("Sending request: %s", redactToken(reqBody)) } - if err = doPost(api.httpclient, req, parser(&response), api); err != nil { + if _, err = doPost(api.httpclient, req, parser(&response), api); err != nil { return "", "", "", err } @@ -375,13 +375,15 @@ func (t sendConfig) BuildRequestContext(ctx context.Context, token, channelID st deleteOriginal: t.deleteOriginal, }.BuildRequestContext(ctx) default: - return formSender{endpoint: t.endpoint, values: t.values}.BuildRequestContext(ctx) + return formSender{endpoint: t.endpoint, values: t.values, attachments: t.attachments, blocks: t.blocks}.BuildRequestContext(ctx) } } type formSender struct { - endpoint string - values url.Values + endpoint string + values url.Values + attachments []Attachment + blocks Blocks } func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) { @@ -389,6 +391,22 @@ func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) respo } func (t formSender) BuildRequestContext(ctx context.Context) (*http.Request, func(*chatResponseFull) responseParser, error) { + if t.attachments != nil { + attachmentBytes, err := json.Marshal(t.attachments) + if err != nil { + return nil, nil, err + } + t.values.Set("attachments", string(attachmentBytes)) + } + + if t.blocks.BlockSet != nil { + blockBytes, err := json.Marshal(t.blocks.BlockSet) + if err != nil { + return nil, nil, err + } + t.values.Set("blocks", string(blockBytes)) + } + req, err := formReq(ctx, t.endpoint, t.values) return req, func(resp *chatResponseFull) responseParser { return newJSONParser(resp) @@ -626,6 +644,7 @@ func MsgOptionDeleteOriginal(responseURL string) MsgOption { // MsgOptionAsUser whether or not to send the message as the user. func MsgOptionAsUser(b bool) MsgOption { return func(config *sendConfig) error { + //lint:ignore S1002 - we want to explicitly check against the constant if b != DEFAULT_MESSAGE_ASUSER { config.values.Set("as_user", "true") } @@ -670,33 +689,24 @@ func MsgOptionAttachments(attachments ...Attachment) MsgOption { config.attachments = attachments - // FIXME: We are setting the attachments on the message twice: above for - // the json version, and below for the html version. The marshalled bytes - // we put into config.values below don't work directly in the Msg version. - - attachmentBytes, err := json.Marshal(attachments) - if err == nil { - config.values.Set("attachments", string(attachmentBytes)) - } - - return err + return nil } } -// MsgOptionBlocks sets blocks for the message +// MsgOptionBlocks sets blocks for the message. +// Calling with no arguments or an empty slice sends "blocks=[]" to clear blocks. +// To skip setting blocks entirely, do not include this option. func MsgOptionBlocks(blocks ...Block) MsgOption { return func(config *sendConfig) error { - if blocks == nil { - return nil + if len(blocks) == 0 { + // Explicitly set to empty slice (not nil) so the sender + // knows to marshal "[]" and clear blocks on the message. + config.blocks.BlockSet = []Block{} + } else { + config.blocks.BlockSet = append(config.blocks.BlockSet, blocks...) } - config.blocks.BlockSet = append(config.blocks.BlockSet, blocks...) - - blocks, err := json.Marshal(blocks) - if err == nil { - config.values.Set("blocks", string(blocks)) - } - return err + return nil } } @@ -902,6 +912,24 @@ func MsgOptionMarkdownText(text string) MsgOption { } } +// TaskDisplayMode controls how task_card / task_update chunks render in a +// streamed message. Used with chat.startStream. +type TaskDisplayMode string + +const ( + TaskDisplayModeTimeline TaskDisplayMode = "timeline" + TaskDisplayModePlan TaskDisplayMode = "plan" +) + +// MsgOptionTaskDisplayMode sets task_display_mode on chat.startStream, +// controlling whether tasks render as a sequential timeline or a grouped plan. +func MsgOptionTaskDisplayMode(mode TaskDisplayMode) MsgOption { + return func(config *sendConfig) error { + config.values.Set("task_display_mode", string(mode)) + return nil + } +} + // UnsafeMsgOptionEndpoint deliver the message to the specified endpoint. // NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this Option // will be supported by the library, it is subject to change without notice that @@ -936,15 +964,19 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption { config.values.Set("link_names", "1") } + //lint:ignore S1002 - we want to explicitly check against the constant if params.UnfurlLinks != DEFAULT_MESSAGE_UNFURL_LINKS { config.values.Set("unfurl_links", "true") } // I want to send a message with explicit `as_user` `true` and `unfurl_links` `false` in request. // Because setting `as_user` to `true` will change the default value for `unfurl_links` to `true` on Slack API side. + //lint:ignore S1002 - we want to explicitly check against the constants if params.AsUser != DEFAULT_MESSAGE_ASUSER && params.UnfurlLinks == DEFAULT_MESSAGE_UNFURL_LINKS { config.values.Set("unfurl_links", "false") } + + //lint:ignore S1002 - we want to explicitly check against the constant if params.UnfurlMedia != DEFAULT_MESSAGE_UNFURL_MEDIA { config.values.Set("unfurl_media", "false") } @@ -954,6 +986,7 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption { if params.IconEmoji != DEFAULT_MESSAGE_ICON_EMOJI { config.values.Set("icon_emoji", params.IconEmoji) } + //lint:ignore S1002 - we want to explicitly check against the constant if params.Markdown != DEFAULT_MESSAGE_MARKDOWN { config.values.Set("mrkdwn", "false") } @@ -961,6 +994,7 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption { if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP { config.values.Set("thread_ts", params.ThreadTimestamp) } + //lint:ignore S1002 - we want to explicitly check against the constant if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST { config.values.Set("reply_broadcast", "true") } diff --git a/vendor/github.com/slack-go/slack/chat_stream_chunks.go b/vendor/github.com/slack-go/slack/chat_stream_chunks.go new file mode 100644 index 00000000..fbdab3be --- /dev/null +++ b/vendor/github.com/slack-go/slack/chat_stream_chunks.go @@ -0,0 +1,95 @@ +package slack + +import ( + "encoding/json" +) + +// StreamChunkType identifies a chunk in the chat.startStream / chat.appendStream +// / chat.stopStream streaming-message protocol. +// +// More information: https://docs.slack.dev/reference/methods/chat.appendStream/ +type StreamChunkType string + +const ( + StreamChunkMarkdownText StreamChunkType = "markdown_text" + StreamChunkTaskUpdate StreamChunkType = "task_update" + StreamChunkPlanUpdate StreamChunkType = "plan_update" + StreamChunkBlocks StreamChunkType = "blocks" +) + +// StreamChunk represents a single chunk in the streaming-message chunks array. +type StreamChunk interface { + ChunkType() StreamChunkType +} + +// MarkdownTextChunk streams markdown-formatted text. +type MarkdownTextChunk struct { + Type StreamChunkType `json:"type"` + Text string `json:"text"` +} + +func (c MarkdownTextChunk) ChunkType() StreamChunkType { return c.Type } + +// NewMarkdownTextChunk returns a markdown_text chunk. +func NewMarkdownTextChunk(text string) MarkdownTextChunk { + return MarkdownTextChunk{Type: StreamChunkMarkdownText, Text: text} +} + +// TaskUpdateChunk streams a task status update that renders as a task card. +type TaskUpdateChunk struct { + Type StreamChunkType `json:"type"` + ID string `json:"id"` + Title string `json:"title"` + Status TaskCardStatus `json:"status,omitempty"` + Details string `json:"details,omitempty"` + Output string `json:"output,omitempty"` + Sources []TaskCardSource `json:"sources,omitempty"` +} + +func (c TaskUpdateChunk) ChunkType() StreamChunkType { return c.Type } + +// NewTaskUpdateChunk returns a task_update chunk with the given id and title. +func NewTaskUpdateChunk(id, title string) TaskUpdateChunk { + return TaskUpdateChunk{Type: StreamChunkTaskUpdate, ID: id, Title: title} +} + +// PlanUpdateChunk streams an update to the current plan's title. +type PlanUpdateChunk struct { + Type StreamChunkType `json:"type"` + Title string `json:"title"` +} + +func (c PlanUpdateChunk) ChunkType() StreamChunkType { return c.Type } + +// NewPlanUpdateChunk returns a plan_update chunk. +func NewPlanUpdateChunk(title string) PlanUpdateChunk { + return PlanUpdateChunk{Type: StreamChunkPlanUpdate, Title: title} +} + +// BlocksChunk streams a group of Block Kit blocks. Up to 50 blocks per chunk. +type BlocksChunk struct { + Type StreamChunkType `json:"type"` + Blocks []Block `json:"blocks"` +} + +func (c BlocksChunk) ChunkType() StreamChunkType { return c.Type } + +// NewBlocksChunk returns a blocks chunk containing the given blocks. +func NewBlocksChunk(blocks ...Block) BlocksChunk { + return BlocksChunk{Type: StreamChunkBlocks, Blocks: blocks} +} + +// MsgOptionChunks sets the `chunks` parameter for the streaming chat methods +// (chat.startStream / chat.appendStream / chat.stopStream). It is the +// transport for Block Kit agent-UI blocks (Alert, Card, Carousel, etc.) which +// chat.postMessage rejects as "Unsupported block type". +func MsgOptionChunks(chunks ...StreamChunk) MsgOption { + return func(config *sendConfig) error { + encoded, err := json.Marshal(chunks) + if err != nil { + return err + } + config.values.Set("chunks", string(encoded)) + return nil + } +} diff --git a/vendor/github.com/slack-go/slack/conversation.go b/vendor/github.com/slack-go/slack/conversation.go index 2f8e7f42..a0ce707b 100644 --- a/vendor/github.com/slack-go/slack/conversation.go +++ b/vendor/github.com/slack-go/slack/conversation.go @@ -29,6 +29,7 @@ type Conversation struct { IsPrivate bool `json:"is_private"` IsReadOnly bool `json:"is_read_only"` IsMpIM bool `json:"is_mpim"` + IsUserDeleted bool `json:"is_user_deleted"` Unlinked int `json:"unlinked"` NameNormalized string `json:"name_normalized"` NumMembers int `json:"num_members"` @@ -353,12 +354,14 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel return response.Channel, response.Err() } -// The following functions are for inviting users to a channel but setting the `force` -// parameter to true. We have added this so that we don't break the existing API. -// -// IMPORTANT: If we ever get here for _another_ parameter, we should consider refactoring -// this to be more flexible. -// +/********************************************************************************** +The following functions are for inviting users to a channel but setting the `force` +parameter to true. We have added this so that we don't break the existing API. + +IMPORTANT: If we ever get here for _another_ parameter, we should consider refactoring +this to be more flexible. +*/ + // ForceInviteUsersToConversation invites users to a channel but sets the `force` // parameter to true. // diff --git a/vendor/github.com/slack-go/slack/dialog.go b/vendor/github.com/slack-go/slack/dialog.go index f94113f4..4c507fcd 100644 --- a/vendor/github.com/slack-go/slack/dialog.go +++ b/vendor/github.com/slack-go/slack/dialog.go @@ -106,8 +106,7 @@ func (api *Client) OpenDialogContext(ctx context.Context, triggerID string, dial } response := &DialogOpenResponse{} - endpoint := api.endpoint + "dialog.open" - if err := postJSON(ctx, api.httpclient, endpoint, api.token, encoded, response, api); err != nil { + if err := api.postJSONMethod(ctx, "dialog.open", api.token, encoded, response); err != nil { return err } diff --git a/vendor/github.com/slack-go/slack/dnd.go b/vendor/github.com/slack-go/slack/dnd.go index 81eaf502..4f6b35a5 100644 --- a/vendor/github.com/slack-go/slack/dnd.go +++ b/vendor/github.com/slack-go/slack/dnd.go @@ -7,6 +7,14 @@ import ( "strings" ) +// DNDOptionTeamID sets the team_id parameter for DND methods. Required after +// workspace migration when the API returns missing_argument: team_id. +func DNDOptionTeamID(teamID string) ParamOption { + return func(v *url.Values) { + v.Set("team_id", teamID) + } +} + type SnoozeDebug struct { SnoozeEndDate string `json:"snooze_end_date"` } @@ -52,7 +60,7 @@ func (api *Client) EndDND() error { } // EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context. -// Slack API docs: https://api.slack.com/methods/dnd.endDnd +// Slack API docs: https://docs.slack.dev/reference/methods/dnd.endDnd func (api *Client) EndDNDContext(ctx context.Context) error { values := url.Values{ "token": {api.token}, @@ -74,7 +82,7 @@ func (api *Client) EndSnooze() (*DNDStatus, error) { } // EndSnoozeContext ends the current user's snooze mode with a custom context. -// Slack API docs: https://api.slack.com/methods/dnd.endSnooze +// Slack API docs: https://docs.slack.dev/reference/methods/dnd.endSnooze func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) { values := url.Values{ "token": {api.token}, @@ -89,19 +97,22 @@ func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) { // GetDNDInfo provides information about a user's current Do Not Disturb settings. // For more information see the GetDNDInfoContext documentation. -func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) { - return api.GetDNDInfoContext(context.Background(), user) +func (api *Client) GetDNDInfo(user *string, options ...ParamOption) (*DNDStatus, error) { + return api.GetDNDInfoContext(context.Background(), user, options...) } // GetDNDInfoContext provides information about a user's current Do Not Disturb settings with a custom context. -// Slack API docs: https://api.slack.com/methods/dnd.info -func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDStatus, error) { +// Slack API docs: https://docs.slack.dev/reference/methods/dnd.info/ +func (api *Client) GetDNDInfoContext(ctx context.Context, user *string, options ...ParamOption) (*DNDStatus, error) { values := url.Values{ "token": {api.token}, } if user != nil { values.Set("user", *user) } + for _, opt := range options { + opt(&values) + } response, err := api.dndRequest(ctx, "dnd.info", values) if err != nil { @@ -112,17 +123,20 @@ func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDSta // GetDNDTeamInfo provides information about a user's current Do Not Disturb settings. // For more information see the GetDNDTeamInfoContext documentation. -func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) { - return api.GetDNDTeamInfoContext(context.Background(), users) +func (api *Client) GetDNDTeamInfo(users []string, options ...ParamOption) (map[string]DNDStatus, error) { + return api.GetDNDTeamInfoContext(context.Background(), users, options...) } // GetDNDTeamInfoContext provides information about a user's current Do Not Disturb settings with a custom context. -// Slack API docs: https://api.slack.com/methods/dnd.teamInfo -func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (map[string]DNDStatus, error) { +// Slack API docs: https://docs.slack.dev/reference/methods/dnd.teamInfo +func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string, options ...ParamOption) (map[string]DNDStatus, error) { values := url.Values{ "token": {api.token}, "users": {strings.Join(users, ",")}, } + for _, opt := range options { + opt(&values) + } response := &dndTeamInfoResponse{} if err := api.postMethod(ctx, "dnd.teamInfo", values, response); err != nil { @@ -145,7 +159,7 @@ func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) { // SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings. // If a snooze session is not already active for the user, invoking this method will // begin one for the specified duration. -// Slack API docs: https://api.slack.com/methods/dnd.setSnooze +// Slack API docs: https://docs.slack.dev/reference/methods/dnd.setSnooze func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) { values := url.Values{ "token": {api.token}, diff --git a/vendor/github.com/slack-go/slack/files.go b/vendor/github.com/slack-go/slack/files.go index 729ab4ff..974ba0a4 100644 --- a/vendor/github.com/slack-go/slack/files.go +++ b/vendor/github.com/slack-go/slack/files.go @@ -310,6 +310,7 @@ func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameter if params.Page != DEFAULT_FILES_PAGE { values.Add("page", strconv.Itoa(params.Page)) } + //lint:ignore S1002 - we want to explicitly check against the constant if params.ShowHidden != DEFAULT_FILES_SHOW_HIDDEN { values.Add("show_files_hidden_by_limit", strconv.FormatBool(params.ShowHidden)) } diff --git a/vendor/github.com/slack-go/slack/function_execute.go b/vendor/github.com/slack-go/slack/function_execute.go index 4ec8f9f4..97bc7e15 100644 --- a/vendor/github.com/slack-go/slack/function_execute.go +++ b/vendor/github.com/slack-go/slack/function_execute.go @@ -43,14 +43,13 @@ func (api *Client) FunctionCompleteSuccessContext(ctx context.Context, functionE option(r) } - endpoint := api.endpoint + "functions.completeSuccess" jsonData, err := json.Marshal(r) if err != nil { return err } response := &SlackResponse{} - if err := postJSON(ctx, api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + if err := api.postJSONMethod(ctx, "functions.completeSuccess", api.token, jsonData, response); err != nil { return err } @@ -74,14 +73,13 @@ func (api *Client) FunctionCompleteErrorContext(ctx context.Context, functionExe } r.Error = errorMessage - endpoint := api.endpoint + "functions.completeError" jsonData, err := json.Marshal(r) if err != nil { return err } response := &SlackResponse{} - if err := postJSON(ctx, api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + if err := api.postJSONMethod(ctx, "functions.completeError", api.token, jsonData, response); err != nil { return err } diff --git a/vendor/github.com/slack-go/slack/im.go b/vendor/github.com/slack-go/slack/im.go deleted file mode 100644 index 7c4bc257..00000000 --- a/vendor/github.com/slack-go/slack/im.go +++ /dev/null @@ -1,21 +0,0 @@ -package slack - -type imChannel struct { - ID string `json:"id"` -} - -type imResponseFull struct { - NoOp bool `json:"no_op"` - AlreadyClosed bool `json:"already_closed"` - AlreadyOpen bool `json:"already_open"` - Channel imChannel `json:"channel"` - IMs []IM `json:"ims"` - History - SlackResponse -} - -// IM contains information related to the Direct Message channel -type IM struct { - Conversation - IsUserDeleted bool `json:"is_user_deleted"` -} diff --git a/vendor/github.com/slack-go/slack/info.go b/vendor/github.com/slack-go/slack/info.go index a026ab49..d5276f75 100644 --- a/vendor/github.com/slack-go/slack/info.go +++ b/vendor/github.com/slack-go/slack/info.go @@ -431,7 +431,7 @@ type Team struct { Icons *Icons `json:"icon,omitempty"` } -// Icons XXX: needs further investigation +// Icons contains the image URLs for the team icons in various sizes type Icons struct { Image36 string `json:"image_36,omitempty"` Image48 string `json:"image_48,omitempty"` @@ -452,28 +452,3 @@ type infoResponseFull struct { Info SlackResponse } - -// GetBotByID is deprecated and returns nil -func (info Info) GetBotByID(botID string) *Bot { - return nil -} - -// GetUserByID is deprecated and returns nil -func (info Info) GetUserByID(userID string) *User { - return nil -} - -// GetChannelByID is deprecated and returns nil -func (info Info) GetChannelByID(channelID string) *Channel { - return nil -} - -// GetGroupByID is deprecated and returns nil -func (info Info) GetGroupByID(groupID string) *Group { - return nil -} - -// GetIMByID is deprecated and returns nil -func (info Info) GetIMByID(imID string) *IM { - return nil -} diff --git a/vendor/github.com/slack-go/slack/manifests.go b/vendor/github.com/slack-go/slack/manifests.go index ab6b1f2f..59f6a70b 100644 --- a/vendor/github.com/slack-go/slack/manifests.go +++ b/vendor/github.com/slack-go/slack/manifests.go @@ -269,8 +269,10 @@ type OAuthConfig struct { // OAuthScopes is a group of settings that describe permission scopes configuration type OAuthScopes struct { - Bot []string `json:"bot,omitempty" yaml:"bot,omitempty"` - User []string `json:"user,omitempty" yaml:"user,omitempty"` + Bot []string `json:"bot,omitempty" yaml:"bot,omitempty"` + User []string `json:"user,omitempty" yaml:"user,omitempty"` + BotOptional []string `json:"bot_optional,omitempty" yaml:"bot_optional,omitempty"` + UserOptional []string `json:"user_optional,omitempty" yaml:"user_optional,omitempty"` } // ManifestResponse is the response returned by the API for apps.manifest.x endpoints diff --git a/vendor/github.com/slack-go/slack/misc.go b/vendor/github.com/slack-go/slack/misc.go index dc7fb9fd..46060a72 100644 --- a/vendor/github.com/slack-go/slack/misc.go +++ b/vendor/github.com/slack-go/slack/misc.go @@ -114,16 +114,59 @@ func (e *SlackResponseErrors) UnmarshalJSON(data []byte) error { type SlackResponse struct { Ok bool `json:"ok"` Error string `json:"error"` + Warning string `json:"warning"` Errors []SlackResponseErrors `json:"errors,omitempty"` ResponseMetadata ResponseMetadata `json:"response_metadata"` } +// Warn returns warning information from the API response, or nil if there +// are no warnings. +func (t SlackResponse) Warn() *Warning { + if t.Warning == "" && len(t.ResponseMetadata.Warnings) == 0 { + return nil + } + return &Warning{ + Codes: strings.Split(t.Warning, ","), + Warnings: t.ResponseMetadata.Warnings, + } +} + +// warner is satisfied by any response type that can report warnings. +type warner interface { + Warn() *Warning +} + +// Warning provides warning information from the web API. +// https://docs.slack.dev/apis/web-api/#responses +type Warning struct { + Codes []string + Warnings []string +} + +// httpHeaderSetter is satisfied by response types that can store HTTP +// response headers. The response parser checks for this interface and +// injects headers before JSON decoding. +type httpHeaderSetter interface { + setHTTPResponseHeaders(http.Header) +} + +// responseHeaders is a mix-in for internal response types that need to +// capture HTTP response headers. Embedded in types like authTestResponseFull +// so the parser can store headers that are then propagated to the public +// response type (e.g. AuthTestResponse.Header). +type responseHeaders struct { + header http.Header +} + +func (r *responseHeaders) setHTTPResponseHeaders(h http.Header) { r.header = h } + // KickUserFromConversationSlackResponse is a variant of SlackResponse that can handle the case where // "errors" can be either an empty object {} or an array of errors. // This addresses issue #1446 where conversations.kick endpoint returns {"ok":true,"errors":{}} type KickUserFromConversationSlackResponse struct { Ok bool `json:"ok"` Error string `json:"error"` + Warning string `json:"warning"` Errors []SlackResponseErrors `json:"-"` ResponseMetadata ResponseMetadata `json:"response_metadata"` } @@ -164,6 +207,18 @@ func (s *KickUserFromConversationSlackResponse) UnmarshalJSON(data []byte) error return nil } +// Warn returns warning information from the API response, or nil if there +// are no warnings. +func (s KickUserFromConversationSlackResponse) Warn() *Warning { + if s.Warning == "" && len(s.ResponseMetadata.Warnings) == 0 { + return nil + } + return &Warning{ + Codes: strings.Split(s.Warning, ","), + Warnings: s.ResponseMetadata.Warnings, + } +} + // Err returns any API error present in the response. func (s KickUserFromConversationSlackResponse) Err() error { if s.Ok { @@ -362,26 +417,25 @@ func createFormFields(mw *multipart.Writer, values url.Values) error { return nil } -func doPost(client httpClient, req *http.Request, parser responseParser, d Debug) error { +func doPost(client httpClient, req *http.Request, parser responseParser, d Debug) (http.Header, error) { resp, err := client.Do(req) if err != nil { - return err + return nil, err } defer resp.Body.Close() - err = checkStatusCode(resp, d) - if err != nil { - return err + if err = checkStatusCode(resp, d); err != nil { + return nil, err } - return parser(resp) + return resp.Header, parser(resp) } // post JSON. -func postJSON(ctx context.Context, client httpClient, endpoint, token string, jsonBody []byte, intf any, d Debug) error { +func postJSON(ctx context.Context, client httpClient, endpoint, token string, jsonBody []byte, intf any, d Debug) (http.Header, error) { req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(jsonBody)) if err != nil { - return err + return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) @@ -393,11 +447,11 @@ func postJSON(ctx context.Context, client httpClient, endpoint, token string, js } // post a url encoded form. -func postForm(ctx context.Context, client httpClient, endpoint string, values url.Values, intf any, d Debug) error { +func postForm(ctx context.Context, client httpClient, endpoint string, values url.Values, intf any, d Debug) (http.Header, error) { body := values.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(body)) if err != nil { - return err + return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // allow retry client to re-send the request body on 429/5xx. @@ -407,10 +461,10 @@ func postForm(ctx context.Context, client httpClient, endpoint string, values ur return doPost(client, req, newJSONParser(intf), d) } -func getResource(ctx context.Context, client httpClient, endpoint, token string, values url.Values, intf any, d Debug) error { +func getResource(ctx context.Context, client httpClient, endpoint, token string, values url.Values, intf any, d Debug) (http.Header, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { - return err + return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) @@ -422,7 +476,8 @@ func getResource(ctx context.Context, client httpClient, endpoint, token string, func parseAdminResponse(ctx context.Context, client httpClient, method string, teamName string, values url.Values, intf any, d Debug) error { endpoint := fmt.Sprintf(WEBAPIURLFormat, teamName, method, time.Now().Unix()) - return postForm(ctx, client, endpoint, values, intf, d) + _, err := postForm(ctx, client, endpoint, values, intf, d) + return err } func logResponse(resp *http.Response, d Debug) error { @@ -470,6 +525,9 @@ func newJSONParser(dst any) responseParser { if dst == nil { return nil } + if hs, ok := dst.(httpHeaderSetter); ok { + hs.setHTTPResponseHeaders(resp.Header.Clone()) + } return json.NewDecoder(resp.Body).Decode(dst) } } @@ -507,6 +565,10 @@ func newContentTypeParser(dst any) responseParser { case "application/json": return newJSONParser(dst)(req) default: + // newTextParser doesn't use dst, so capture headers here. + if hs, ok := dst.(httpHeaderSetter); ok { + hs.setHTTPResponseHeaders(req.Header.Clone()) + } return newTextParser(dst)(req) } } diff --git a/vendor/github.com/slack-go/slack/mise.toml b/vendor/github.com/slack-go/slack/mise.toml index ce5d07a9..912c02c9 100644 --- a/vendor/github.com/slack-go/slack/mise.toml +++ b/vendor/github.com/slack-go/slack/mise.toml @@ -1,3 +1,4 @@ [tools] go = "1.25" golangci-lint = "2.10.1" +"go:honnef.co/go/tools/cmd/staticcheck" = "2026.1" diff --git a/vendor/github.com/slack-go/slack/oauth.go b/vendor/github.com/slack-go/slack/oauth.go index 0c77eca4..f98284d2 100644 --- a/vendor/github.com/slack-go/slack/oauth.go +++ b/vendor/github.com/slack-go/slack/oauth.go @@ -2,6 +2,9 @@ package slack import ( "context" + "crypto/rand" + "crypto/sha256" + "encoding/base64" "net/url" ) @@ -79,16 +82,47 @@ type OpenIDConnectResponse struct { SlackResponse } +type oauthConfig struct { + apiURL string + codeVerifier string +} + +// OAuthOption configures package-level OAuth functions. +type OAuthOption func(*oauthConfig) + +// OAuthOptionAPIURL overrides the default Slack API URL. Useful for testing. +func OAuthOptionAPIURL(url string) OAuthOption { + return func(c *oauthConfig) { c.apiURL = url } +} + +// OAuthOptionCodeVerifier sets the PKCE code_verifier for the OAuth token exchange. +// Use this when your authorization request included a code_challenge. +func OAuthOptionCodeVerifier(verifier string) OAuthOption { + return func(c *oauthConfig) { c.codeVerifier = verifier } +} + +func resolveOAuthConfig(opts []OAuthOption) oauthConfig { + c := oauthConfig{apiURL: APIURL} + for _, o := range opts { + o(&c) + } + return c +} + +func resolveOAuthAPIURL(opts []OAuthOption) string { + return resolveOAuthConfig(opts).apiURL +} + // GetOAuthToken retrieves an AccessToken. // For more details, see GetOAuthTokenContext documentation. -func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) { - return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, err error) { + return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...) } // GetOAuthTokenContext retrieves an AccessToken with a custom context. // For more details, see GetOAuthResponseContext documentation. -func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) { - response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI) +func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, err error) { + response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI, opts...) if err != nil { return "", "", err } @@ -97,14 +131,14 @@ func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clie // GetBotOAuthToken retrieves top-level and bot AccessToken - https://api.slack.com/legacy/oauth#bot_user_access_tokens // For more details, see GetBotOAuthTokenContext documentation. -func GetBotOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, bot OAuthResponseBot, err error) { - return GetBotOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +func GetBotOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, bot OAuthResponseBot, err error) { + return GetBotOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...) } // GetBotOAuthTokenContext retrieves top-level and bot AccessToken with a custom context. // For more details, see GetOAuthResponseContext documentation. -func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, bot OAuthResponseBot, err error) { - response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI) +func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, bot OAuthResponseBot, err error) { + response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI, opts...) if err != nil { return "", "", OAuthResponseBot{}, err } @@ -113,13 +147,13 @@ func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, c // GetOAuthResponse retrieves OAuth response. // For more details, see GetOAuthResponseContext documentation. -func GetOAuthResponse(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) { - return GetOAuthResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +func GetOAuthResponse(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthResponse, err error) { + return GetOAuthResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...) } // GetOAuthResponseContext retrieves OAuth response with custom context. // Slack API docs: https://api.slack.com/methods/oauth.access -func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) { +func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthResponse, err error) { values := url.Values{ "client_id": {clientID}, "client_secret": {clientSecret}, @@ -127,7 +161,7 @@ func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, c "redirect_uri": {redirectURI}, } response := &OAuthResponse{} - if err = postForm(ctx, client, APIURL+"oauth.access", values, response, discard{}); err != nil { + if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"oauth.access", values, response, discard{}); err != nil { return nil, err } return response, response.Err() @@ -135,21 +169,28 @@ func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, c // GetOAuthV2Response gets a V2 OAuth access token response. // For more details, see GetOAuthV2ResponseContext documentation. -func GetOAuthV2Response(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) { - return GetOAuthV2ResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +func GetOAuthV2Response(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthV2Response, err error) { + return GetOAuthV2ResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...) } // GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response. +// For PKCE flows, pass OAuthOptionCodeVerifier and an empty clientSecret. // Slack API docs: https://api.slack.com/methods/oauth.v2.access -func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) { +func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthV2Response, err error) { + cfg := resolveOAuthConfig(opts) values := url.Values{ - "client_id": {clientID}, - "client_secret": {clientSecret}, - "code": {code}, - "redirect_uri": {redirectURI}, + "client_id": {clientID}, + "code": {code}, + "redirect_uri": {redirectURI}, + } + if clientSecret != "" { + values.Set("client_secret", clientSecret) + } + if cfg.codeVerifier != "" { + values.Set("code_verifier", cfg.codeVerifier) } response := &OAuthV2Response{} - if err = postForm(ctx, client, APIURL+"oauth.v2.access", values, response, discard{}); err != nil { + if _, err = postForm(ctx, client, cfg.apiURL+"oauth.v2.access", values, response, discard{}); err != nil { return nil, err } return response, response.Err() @@ -157,21 +198,92 @@ func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, // RefreshOAuthV2Token with a context, gets a V2 OAuth access token response. // For more details, see RefreshOAuthV2TokenContext documentation. -func RefreshOAuthV2Token(client httpClient, clientID, clientSecret, refreshToken string) (resp *OAuthV2Response, err error) { - return RefreshOAuthV2TokenContext(context.Background(), client, clientID, clientSecret, refreshToken) +func RefreshOAuthV2Token(client httpClient, clientID, clientSecret, refreshToken string, opts ...OAuthOption) (resp *OAuthV2Response, err error) { + return RefreshOAuthV2TokenContext(context.Background(), client, clientID, clientSecret, refreshToken, opts...) } // RefreshOAuthV2TokenContext with a context, gets a V2 OAuth access token response. +// For PKCE public clients, pass an empty clientSecret. // Slack API docs: https://api.slack.com/methods/oauth.v2.access -func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID, clientSecret, refreshToken string) (resp *OAuthV2Response, err error) { +func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID, clientSecret, refreshToken string, opts ...OAuthOption) (resp *OAuthV2Response, err error) { values := url.Values{ "client_id": {clientID}, - "client_secret": {clientSecret}, "refresh_token": {refreshToken}, "grant_type": {"refresh_token"}, } + if clientSecret != "" { + values.Set("client_secret", clientSecret) + } response := &OAuthV2Response{} - if err = postForm(ctx, client, APIURL+"oauth.v2.access", values, response, discard{}); err != nil { + if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"oauth.v2.access", values, response, discard{}); err != nil { + return nil, err + } + return response, response.Err() +} + +// OpenIDConnectUserInfoResponse contains the response from openid.connect.userInfo. +// +// Some of the fields in the response to this method are preceded with https://slack.com/. +// These fields are Slack-specific, and they're from the perspective of Slack. +type OpenIDConnectUserInfoResponse struct { + Ok bool `json:"ok"` + + Sub string `json:"sub"` + + UserID string `json:"https://slack.com/user_id"` + TeamID string `json:"https://slack.com/team_id"` + + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + DateEmailVerified int64 `json:"date_email_verified"` + + Name string `json:"name"` + Picture string `json:"picture"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + Locale string `json:"locale"` + + TeamName string `json:"https://slack.com/team_name"` + TeamDomain string `json:"https://slack.com/team_domain"` + TeamImage34 string `json:"https://slack.com/team_image_34"` + TeamImage44 string `json:"https://slack.com/team_image_44"` + TeamImage68 string `json:"https://slack.com/team_image_68"` + TeamImage88 string `json:"https://slack.com/team_image_88"` + TeamImage102 string `json:"https://slack.com/team_image_102"` + TeamImage132 string `json:"https://slack.com/team_image_132"` + TeamImage230 string `json:"https://slack.com/team_image_230"` + + // `TeamImageDefault` indicates whether the image is a default one (true), or someone + // uploaded their own (false). + TeamImageDefault bool `json:"https://slack.com/team_image_default"` + + UserImage24 string `json:"https://slack.com/user_image_24"` + UserImage32 string `json:"https://slack.com/user_image_32"` + UserImage48 string `json:"https://slack.com/user_image_48"` + UserImage72 string `json:"https://slack.com/user_image_72"` + UserImage192 string `json:"https://slack.com/user_image_192"` + UserImage512 string `json:"https://slack.com/user_image_512"` + UserImage1024 string `json:"https://slack.com/user_image_1024"` + UserImageOriginal string `json:"https://slack.com/user_image_original"` + + SlackResponse +} + +// GetOpenIDConnectUserInfo returns the user info for the token. +// For more details, see GetOpenIDConnectUserInfoContext documentation. +func (api *Client) GetOpenIDConnectUserInfo() (*OpenIDConnectUserInfoResponse, error) { + return api.GetOpenIDConnectUserInfoContext(context.Background()) +} + +// GetOpenIDConnectUserInfoContext returns identity information about the user associated with the token. +// Slack API docs: https://docs.slack.dev/reference/methods/openid.connect.userInfo +func (api *Client) GetOpenIDConnectUserInfoContext(ctx context.Context) (*OpenIDConnectUserInfoResponse, error) { + values := url.Values{ + "token": {api.token}, + } + response := &OpenIDConnectUserInfoResponse{} + err := api.postMethod(ctx, "openid.connect.userInfo", values, response) + if err != nil { return nil, err } return response, response.Err() @@ -179,13 +291,13 @@ func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID // GetOpenIDConnectToken exchanges a temporary OAuth verifier code for an access token for Sign in with Slack. // For more details, see GetOpenIDConnectTokenContext documentation. -func GetOpenIDConnectToken(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OpenIDConnectResponse, err error) { - return GetOpenIDConnectTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +func GetOpenIDConnectToken(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OpenIDConnectResponse, err error) { + return GetOpenIDConnectTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...) } // GetOpenIDConnectTokenContext with a context, gets an access token for Sign in with Slack. // Slack API docs: https://api.slack.com/methods/openid.connect.token -func GetOpenIDConnectTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OpenIDConnectResponse, err error) { +func GetOpenIDConnectTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OpenIDConnectResponse, err error) { values := url.Values{ "client_id": {clientID}, "client_secret": {clientSecret}, @@ -193,8 +305,26 @@ func GetOpenIDConnectTokenContext(ctx context.Context, client httpClient, client "redirect_uri": {redirectURI}, } response := &OpenIDConnectResponse{} - if err = postForm(ctx, client, APIURL+"openid.connect.token", values, response, discard{}); err != nil { + if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"openid.connect.token", values, response, discard{}); err != nil { return nil, err } return response, response.Err() } + +// GenerateCodeVerifier creates a cryptographically random PKCE code verifier +// string suitable for use with OAuth 2.0 PKCE flows. The returned string is +// 43 characters of URL-safe base64 (no padding). +func GenerateCodeVerifier() (string, error) { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(b), nil +} + +// GenerateCodeChallenge creates a PKCE code challenge from a code verifier +// using the S256 method (SHA-256 hash, base64url-encoded without padding). +func GenerateCodeChallenge(verifier string) string { + h := sha256.Sum256([]byte(verifier)) + return base64.RawURLEncoding.EncodeToString(h[:]) +} diff --git a/vendor/github.com/slack-go/slack/reactions.go b/vendor/github.com/slack-go/slack/reactions.go index 743b22a1..18befa69 100644 --- a/vendor/github.com/slack-go/slack/reactions.go +++ b/vendor/github.com/slack-go/slack/reactions.go @@ -70,18 +70,16 @@ func (res getReactionsResponseFull) extractReactedItem() ReactedItem { } const ( - DEFAULT_REACTIONS_USER = "" - DEFAULT_REACTIONS_COUNT = 100 - DEFAULT_REACTIONS_PAGE = 1 - DEFAULT_REACTIONS_FULL = false + DEFAULT_REACTIONS_USER = "" + DEFAULT_REACTIONS_FULL = false ) // ListReactionsParameters is the inputs to find all reactions by a user. type ListReactionsParameters struct { User string TeamID string - Count int - Page int + Cursor string + Limit int Full bool } @@ -89,10 +87,8 @@ type ListReactionsParameters struct { // performed by a user. func NewListReactionsParameters() ListReactionsParameters { return ListReactionsParameters{ - User: DEFAULT_REACTIONS_USER, - Count: DEFAULT_REACTIONS_COUNT, - Page: DEFAULT_REACTIONS_PAGE, - Full: DEFAULT_REACTIONS_FULL, + User: DEFAULT_REACTIONS_USER, + Full: DEFAULT_REACTIONS_FULL, } } @@ -112,8 +108,8 @@ type listReactionsResponseFull struct { Reactions []ItemReaction } `json:"comment"` } - Paging `json:"paging"` SlackResponse + ResponseMetadata `json:"response_metadata"` } func (res listReactionsResponseFull) extractReactedItems() []ReactedItem { @@ -253,13 +249,13 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params // ListReactions returns information about the items a user reacted to. // For more details, see ListReactionsContext documentation. -func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) { +func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, string, error) { return api.ListReactionsContext(context.Background(), params) } // ListReactionsContext returns information about the items a user reacted to with a custom context. // Slack API docs: https://api.slack.com/methods/reactions.list -func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) { +func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, string, error) { values := url.Values{ "token": {api.token}, } @@ -269,11 +265,11 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction if params.TeamID != "" { values.Add("team_id", params.TeamID) } - if params.Count != DEFAULT_REACTIONS_COUNT { - values.Add("count", strconv.Itoa(params.Count)) + if params.Cursor != "" { + values.Add("cursor", params.Cursor) } - if params.Page != DEFAULT_REACTIONS_PAGE { - values.Add("page", strconv.Itoa(params.Page)) + if params.Limit != 0 { + values.Add("limit", strconv.Itoa(params.Limit)) } if params.Full { values.Add("full", strconv.FormatBool(params.Full)) @@ -282,12 +278,12 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction response := &listReactionsResponseFull{} err := api.postMethod(ctx, "reactions.list", values, response) if err != nil { - return nil, nil, err + return nil, "", err } if err := response.Err(); err != nil { - return nil, nil, err + return nil, "", err } - return response.extractReactedItems(), &response.Paging, nil + return response.extractReactedItems(), response.ResponseMetadata.Cursor, nil } diff --git a/vendor/github.com/slack-go/slack/security.go b/vendor/github.com/slack-go/slack/security.go index 45103529..8124c2c0 100644 --- a/vendor/github.com/slack-go/slack/security.go +++ b/vendor/github.com/slack-go/slack/security.go @@ -30,6 +30,10 @@ func unsafeSignatureVerifier(header http.Header, secret string) (_ SecretsVerifi bsignature []byte ) + if secret == "" { + return SecretsVerifier{}, ErrInvalidConfiguration + } + signature := header.Get(hSignature) stimestamp := header.Get(hTimestamp) @@ -42,7 +46,7 @@ func unsafeSignatureVerifier(header http.Header, secret string) (_ SecretsVerifi } hash := hmac.New(sha256.New, []byte(secret)) - if _, err = hash.Write([]byte(fmt.Sprintf("v0:%s:", stimestamp))); err != nil { + if _, err = fmt.Fprintf(hash, "v0:%s:", stimestamp); err != nil { return SecretsVerifier{}, err } @@ -95,7 +99,7 @@ func (v SecretsVerifier) Ensure() error { if v.d != nil && v.d.Debug() { v.d.Debugln(fmt.Sprintf("Expected signing signature: %s, but computed: %s", hex.EncodeToString(v.signature), hex.EncodeToString(computed))) } - return fmt.Errorf("Computed unexpected signature of: %s", hex.EncodeToString(computed)) + return fmt.Errorf("computed unexpected signature of: %s", hex.EncodeToString(computed)) } func abs64(n int64) int64 { diff --git a/vendor/github.com/slack-go/slack/slack.go b/vendor/github.com/slack-go/slack/slack.go index fbf255a1..45b9cd3b 100644 --- a/vendor/github.com/slack-go/slack/slack.go +++ b/vendor/github.com/slack-go/slack/slack.go @@ -46,13 +46,15 @@ type AuthTestResponse struct { TeamID string `json:"team_id"` UserID string `json:"user_id"` // EnterpriseID is only returned when an enterprise id present - EnterpriseID string `json:"enterprise_id,omitempty"` - BotID string `json:"bot_id"` + EnterpriseID string `json:"enterprise_id,omitempty"` + BotID string `json:"bot_id"` + Header http.Header `json:"-"` } type authTestResponseFull struct { SlackResponse AuthTestResponse + responseHeaders } type ParamOption func(*url.Values) @@ -68,6 +70,8 @@ type Client struct { debug bool log ilogger httpclient httpClient + onWarning func(path string, request any, w *Warning) + onResponseHeaders func(path string, headers http.Header) } // Option defines an option for a Client @@ -94,6 +98,34 @@ func OptionLog(l logger) func(*Client) { } } +// OptionOnWarning sets a callback invoked whenever an API response contains +// warnings. The callback receives the API method path (e.g. +// "conversations.join"), the request payload ([url.Values] for form-encoded +// requests or []byte for JSON requests), and a [Warning] with the warning +// codes and messages. +// +// Example: +// +// api := slack.New("YOUR_TOKEN", +// slack.OptionOnWarning(func(path string, request any, w *slack.Warning) { +// log.Printf("slack warnings for %s: codes=%v warnings=%v", path, w.Codes, w.Warnings) +// }), +// ) +func OptionOnWarning(fn func(path string, request any, w *Warning)) func(*Client) { + return func(c *Client) { + c.onWarning = fn + } +} + +// OptionOnResponseHeaders sets a callback invoked after every API request +// with the API method path and the HTTP response headers. This allows +// accessing headers like X-OAuth-Scopes and X-Ratelimit-* for any request. +func OptionOnResponseHeaders(fn func(path string, headers http.Header)) func(*Client) { + return func(c *Client) { + c.onResponseHeaders = fn + } +} + // OptionAPIURL set the url for the client. only useful for testing. func OptionAPIURL(u string) func(*Client) { return func(c *Client) { c.endpoint = u } @@ -183,6 +215,7 @@ func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestRespo return nil, err } + responseFull.AuthTestResponse.Header = responseFull.responseHeaders.header return &responseFull.AuthTestResponse, responseFull.Err() } @@ -207,10 +240,41 @@ func (api *Client) Debug() bool { // post to a slack web method. func (api *Client) postMethod(ctx context.Context, path string, values url.Values, intf any) error { - return postForm(ctx, api.httpclient, api.endpoint+path, values, intf, api) + headers, err := postForm(ctx, api.httpclient, api.endpoint+path, values, intf, api) + api.checkWarnings(intf, path, values) + api.fireResponseHeaders(path, headers) + return err } // get a slack web method. func (api *Client) getMethod(ctx context.Context, path string, token string, values url.Values, intf any) error { - return getResource(ctx, api.httpclient, api.endpoint+path, token, values, intf, api) + headers, err := getResource(ctx, api.httpclient, api.endpoint+path, token, values, intf, api) + api.checkWarnings(intf, path, values) + api.fireResponseHeaders(path, headers) + return err +} + +// postJSONMethod posts JSON to a slack web method. +func (api *Client) postJSONMethod(ctx context.Context, path string, token string, jsonBody []byte, intf any) error { + headers, err := postJSON(ctx, api.httpclient, api.endpoint+path, token, jsonBody, intf, api) + api.checkWarnings(intf, path, jsonBody) + api.fireResponseHeaders(path, headers) + return err +} + +func (api *Client) checkWarnings(intf any, path string, request any) { + if api.onWarning == nil { + return + } + if w, ok := intf.(warner); ok { + if warning := w.Warn(); warning != nil { + api.onWarning(path, request, warning) + } + } +} + +func (api *Client) fireResponseHeaders(path string, headers http.Header) { + if api.onResponseHeaders != nil && headers != nil { + api.onResponseHeaders(path, headers) + } } diff --git a/vendor/github.com/slack-go/slack/socket_mode.go b/vendor/github.com/slack-go/slack/socket_mode.go index ea9ea3b7..9498cdf7 100644 --- a/vendor/github.com/slack-go/slack/socket_mode.go +++ b/vendor/github.com/slack-go/slack/socket_mode.go @@ -22,7 +22,7 @@ type openResponseFull struct { // To have a fully managed Socket Mode connection, use `socketmode.New()`, and call `Run()` on it. func (api *Client) StartSocketModeContext(ctx context.Context) (info *SocketModeConnection, websocketURL string, err error) { response := &openResponseFull{} - err = postJSON(ctx, api.httpclient, api.endpoint+"apps.connections.open", api.appLevelToken, nil, response, api) + err = api.postJSONMethod(ctx, "apps.connections.open", api.appLevelToken, nil, response) if err != nil { return nil, "", err } @@ -36,7 +36,9 @@ func (api *Client) StartSocketModeContext(ctx context.Context) (info *SocketMode // time significantly shorter (360 seconds). if api.debug { u, _ := url.Parse(response.SocketModeConnection.URL) - u.Query().Add("debug_reconnects", "true") + q := u.Query() + q.Set("debug_reconnects", "true") + u.RawQuery = q.Encode() response.SocketModeConnection.URL = u.String() } return &response.SocketModeConnection, response.SocketModeConnection.URL, response.Err() diff --git a/vendor/github.com/slack-go/slack/stars.go b/vendor/github.com/slack-go/slack/stars.go index 51926854..0adb28c5 100644 --- a/vendor/github.com/slack-go/slack/stars.go +++ b/vendor/github.com/slack-go/slack/stars.go @@ -8,31 +8,28 @@ import ( ) const ( - DEFAULT_STARS_USER = "" - DEFAULT_STARS_COUNT = 100 - DEFAULT_STARS_PAGE = 1 + DEFAULT_STARS_USER = "" ) type StarsParameters struct { - User string - Count int - Page int + User string + Cursor string + Limit int + TeamID string } type StarredItem Item type listResponseFull struct { - Items []Item `json:"items"` - Paging `json:"paging"` + Items []Item `json:"items"` SlackResponse + ResponseMetadata `json:"response_metadata"` } // NewStarsParameters initialises StarsParameters with default values func NewStarsParameters() StarsParameters { return StarsParameters{ - User: DEFAULT_STARS_USER, - Count: DEFAULT_STARS_COUNT, - Page: DEFAULT_STARS_PAGE, + User: DEFAULT_STARS_USER, } } @@ -100,37 +97,40 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I // ListStars returns information about the stars a user added. // For more information see the ListStarsContext documentation. -func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) { +func (api *Client) ListStars(params StarsParameters) ([]Item, string, error) { return api.ListStarsContext(context.Background(), params) } // ListStarsContext returns information about the stars a user added with a custom context. // Slack API docs: https://api.slack.com/methods/stars.list -func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) { +func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, string, error) { values := url.Values{ "token": {api.token}, } if params.User != DEFAULT_STARS_USER { values.Add("user", params.User) } - if params.Count != DEFAULT_STARS_COUNT { - values.Add("count", strconv.Itoa(params.Count)) + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + if params.Limit != 0 { + values.Add("limit", strconv.Itoa(params.Limit)) } - if params.Page != DEFAULT_STARS_PAGE { - values.Add("page", strconv.Itoa(params.Page)) + if params.TeamID != "" { + values.Add("team_id", params.TeamID) } response := &listResponseFull{} err := api.postMethod(ctx, "stars.list", values, response) if err != nil { - return nil, nil, err + return nil, "", err } if err := response.Err(); err != nil { - return nil, nil, err + return nil, "", err } - return response.Items, &response.Paging, nil + return response.Items, response.ResponseMetadata.Cursor, nil } // GetStarred returns a list of StarredItem items. @@ -139,31 +139,31 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) // be looking at according to what is in the Type: // // for _, item := range items { -// switch c.Type { -// case "file_comment": -// log.Println(c.Comment) -// case "file": -// ... +// switch c.Type { +// case "file_comment": +// log.Println(c.Comment) +// case "file": +// ... // } // // This function still exists to maintain backwards compatibility. // I exposed it as returning []StarredItem, so it shall stay as StarredItem. -func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) { +func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, string, error) { return api.GetStarredContext(context.Background(), params) } // GetStarredContext returns a list of StarredItem items with a custom context // For more details see GetStarred -func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) { - items, paging, err := api.ListStarsContext(ctx, params) +func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, string, error) { + items, nextCursor, err := api.ListStarsContext(ctx, params) if err != nil { - return nil, nil, err + return nil, "", err } starredItems := make([]StarredItem, len(items)) for i, item := range items { starredItems[i] = StarredItem(item) } - return starredItems, paging, nil + return starredItems, nextCursor, nil } type listResponsePaginated struct { diff --git a/vendor/github.com/slack-go/slack/team.go b/vendor/github.com/slack-go/slack/team.go index 35b69927..55364a9b 100644 --- a/vendor/github.com/slack-go/slack/team.go +++ b/vendor/github.com/slack-go/slack/team.go @@ -6,11 +6,6 @@ import ( "strconv" ) -const ( - DEFAULT_LOGINS_COUNT = 100 - DEFAULT_LOGINS_PAGE = 1 -) - type TeamResponse struct { Team TeamInfo `json:"team"` SlackResponse @@ -46,8 +41,8 @@ type TeamProfileField struct { type LoginResponse struct { Logins []Login `json:"logins"` - Paging `json:"paging"` SlackResponse + ResponseMetadata `json:"response_metadata"` } type Login struct { @@ -75,16 +70,14 @@ type BillingActive struct { // AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request type AccessLogParameters struct { TeamID string - Count int - Page int + Cursor string + Limit int + Before int } // NewAccessLogParameters provides an instance of AccessLogParameters with all the sane default values set func NewAccessLogParameters() AccessLogParameters { - return AccessLogParameters{ - Count: DEFAULT_LOGINS_COUNT, - Page: DEFAULT_LOGINS_PAGE, - } + return AccessLogParameters{} } func (api *Client) teamRequest(ctx context.Context, path string, values url.Values) (*TeamResponse, error) { @@ -193,31 +186,34 @@ func (api *Client) GetTeamProfileContext(ctx context.Context, teamID ...string) // GetAccessLogs retrieves a page of logins according to the parameters given. // For more information see the GetAccessLogsContext documentation. -func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) { +func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, string, error) { return api.GetAccessLogsContext(context.Background(), params) } // GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context. // Slack API docs: https://api.slack.com/methods/team.accessLogs -func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) { +func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, string, error) { values := url.Values{ "token": {api.token}, } if params.TeamID != "" { values.Add("team_id", params.TeamID) } - if params.Count != DEFAULT_LOGINS_COUNT { - values.Add("count", strconv.Itoa(params.Count)) + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + if params.Limit != 0 { + values.Add("limit", strconv.Itoa(params.Limit)) } - if params.Page != DEFAULT_LOGINS_PAGE { - values.Add("page", strconv.Itoa(params.Page)) + if params.Before != 0 { + values.Add("before", strconv.Itoa(params.Before)) } response, err := api.accessLogsRequest(ctx, "team.accessLogs", values) if err != nil { - return nil, nil, err + return nil, "", err } - return response.Logins, &response.Paging, nil + return response.Logins, response.ResponseMetadata.Cursor, nil } type GetBillableInfoParams struct { diff --git a/vendor/github.com/slack-go/slack/usergroups.go b/vendor/github.com/slack-go/slack/usergroups.go index 616bbf15..b5c54545 100644 --- a/vendor/github.com/slack-go/slack/usergroups.go +++ b/vendor/github.com/slack-go/slack/usergroups.go @@ -568,3 +568,17 @@ func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup } return response.UserGroup, nil } + +// UpdateUserGroupMembersList updates the members of an existing user group, +// accepting a slice of user IDs. This is a convenience wrapper around +// UpdateUserGroupMembers for use with APIs that return []string (e.g. +// GetUserGroupMembers). +func (api *Client) UpdateUserGroupMembersList(userGroup string, members []string, options ...UpdateUserGroupMembersOption) (UserGroup, error) { + return api.UpdateUserGroupMembersContext(context.Background(), userGroup, strings.Join(members, ","), options...) +} + +// UpdateUserGroupMembersListContext updates the members of an existing user +// group with a custom context, accepting a slice of user IDs. +func (api *Client) UpdateUserGroupMembersListContext(ctx context.Context, userGroup string, members []string, options ...UpdateUserGroupMembersOption) (UserGroup, error) { + return api.UpdateUserGroupMembersContext(ctx, userGroup, strings.Join(members, ","), options...) +} diff --git a/vendor/github.com/slack-go/slack/users.go b/vendor/github.com/slack-go/slack/users.go index 7f167605..541baae7 100644 --- a/vendor/github.com/slack-go/slack/users.go +++ b/vendor/github.com/slack-go/slack/users.go @@ -49,6 +49,7 @@ type UserProfile struct { HuddleState string `json:"huddle_state,omitempty"` HuddleStateExpirationTS int `json:"huddle_state_expiration_ts,omitempty"` StartDate string `json:"start_date,omitempty"` + GuestInvitedBy string `json:"guest_invited_by,omitempty"` Team string `json:"team"` Fields UserProfileCustomFields `json:"fields,omitempty"` } @@ -80,7 +81,7 @@ func (fields *UserProfileCustomFields) UnmarshalJSON(b []byte) error { // MarshalJSON is the implementation of the json.Marshaler interface. func (fields UserProfileCustomFields) MarshalJSON() ([]byte, error) { if len(fields.fields) == 0 { - return []byte("[]"), nil + return []byte("{}"), nil } return json.Marshal(fields.fields) } @@ -122,6 +123,7 @@ type User struct { ID string `json:"id"` TeamID string `json:"team_id"` Name string `json:"name"` + Username string `json:"username,omitempty"` Deleted bool `json:"deleted"` Color string `json:"color"` RealName string `json:"real_name"` @@ -137,9 +139,11 @@ type User struct { IsUltraRestricted bool `json:"is_ultra_restricted"` IsStranger bool `json:"is_stranger"` IsAppUser bool `json:"is_app_user"` + IsConnectorBot bool `json:"is_connector_bot"` + IsWorkflowBot bool `json:"is_workflow_bot"` IsInvitedUser bool `json:"is_invited_user"` IsEmailConfirmed bool `json:"is_email_confirmed"` - Has2FA bool `json:"has_2fa"` + Has2FA *bool `json:"has_2fa,omitempty"` TwoFactorType *string `json:"two_factor_type"` HasFiles bool `json:"has_files"` Presence string `json:"presence"` @@ -178,7 +182,7 @@ type UserIdentity struct { } // EnterpriseUser is present when a user is part of Slack Enterprise Grid -// https://api.slack.com/types/user#enterprise_grid_user_objects +// https://docs.slack.dev/reference/objects/user-object/#fields type EnterpriseUser struct { ID string `json:"id"` EnterpriseID string `json:"enterprise_id"` @@ -602,6 +606,54 @@ func (api *Client) SetUserRealNameContextWithUser(ctx context.Context, user, rea return response.Err() } +// SetUserProfile sets the profile for the provided user. +// For more information see the SetUserProfileContext documentation. +func (api *Client) SetUserProfile(user string, profile *UserProfile) error { + return api.SetUserProfileContext(context.Background(), user, profile) +} + +// SetUserProfileContext sets the profile for the provided user with a custom context. +// +// The profile parameter is serialized as-is. Fields present in the JSON (including +// zero-value fields without an omitempty tag, such as RealName and DisplayName) will +// be updated by Slack. To avoid unintended changes, retrieve the current profile with +// GetUserProfile, modify the desired fields, and pass the result. +// +// For setting individual fields, prefer the targeted methods: SetUserRealName, +// SetUserCustomStatus, SetUserCustomFields. +// +// If a workspace admin has mapped custom profile fields to standard fields (e.g. +// title), the custom field takes precedence. Update the custom field via +// SetUserCustomFields instead. +// +// The user parameter is required when setting another user's profile (admin only, +// paid plans). Pass an empty string to modify the authenticated user's own profile. +// +// Slack API docs: https://docs.slack.dev/reference/methods/users.profile.set/ +func (api *Client) SetUserProfileContext(ctx context.Context, user string, profile *UserProfile) error { + profileJSON, err := json.Marshal(profile) + if err != nil { + return err + } + + values := url.Values{ + "token": {api.token}, + "profile": {string(profileJSON)}, + } + + // optional field. It should not be set if empty + if user != "" { + values["user"] = []string{user} + } + + response := &userResponseFull{} + if err = api.postMethod(ctx, "users.profile.set", values, response); err != nil { + return err + } + + return response.Err() +} + // SetUserCustomFields sets Custom Profile fields on the provided users account. // For more information see the SetUserCustomFieldsContext documentation. func (api *Client) SetUserCustomFields(userID string, customFields map[string]UserProfileCustomField) error { @@ -645,7 +697,7 @@ func (api *Client) SetUserCustomFieldsContext(ctx context.Context, userID string } response := &userResponseFull{} - if err := postForm(ctx, api.httpclient, APIURL+"users.profile.set", values, response, api); err != nil { + if _, err := postForm(ctx, api.httpclient, APIURL+"users.profile.set", values, response, api); err != nil { return err } @@ -678,16 +730,16 @@ func (api *Client) SetUserCustomStatusWithUser(user, statusText, statusEmoji str // // Slack API docs: https://api.slack.com/methods/users.profile.set func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, statusText, statusEmoji string, statusExpiration int64) error { - // XXX(theckman): this anonymous struct is for making requests to the Slack - // API for setting and unsetting a User's Custom Status/Emoji. To change - // these values we must provide a JSON document as the profile POST field. + // This anonymous struct is for making requests to the Slack API for setting and + // unsetting a User's Custom Status/Emoji. To change these values we must provide a + // JSON document as the profile POST field. // - // We use an anonymous struct over UserProfile because to unset the values - // on the User's profile we cannot use the `json:"omitempty"` tag. This is - // because an empty string ("") is what's used to unset the values. Check - // out the API docs for more details: + // We use an anonymous struct over UserProfile because to unset the values on the + // User's profile we cannot use the `json:"omitempty"` tag. This is because an empty + // string ("") is what's used to unset the values. Check out the API docs for more + // details: // - // - https://api.slack.com/docs/presence-and-status#custom_status + // - https://docs.slack.dev/apis/web-api/user-presence-and-status/#custom-status profile, err := json.Marshal( &struct { StatusText string `json:"status_text"` diff --git a/vendor/github.com/slack-go/slack/views.go b/vendor/github.com/slack-go/slack/views.go index 94c7dae7..c16503c0 100644 --- a/vendor/github.com/slack-go/slack/views.go +++ b/vendor/github.com/slack-go/slack/views.go @@ -70,12 +70,30 @@ type ViewSubmissionResponse struct { Errors map[string]string `json:"errors,omitempty"` } +// NewClearViewSubmissionResponse closes all open modals in the current stack. +// +// For HTTP-based apps, marshal this to JSON and write it as the HTTP response +// body. The response is not sent until the handler returns, so start any slow +// work in a goroutine and return promptly. +// +// For Socket Mode apps, pass this as the payload argument to Ack(). +// +// See https://docs.slack.dev/surfaces/modals#closing_views func NewClearViewSubmissionResponse() *ViewSubmissionResponse { return &ViewSubmissionResponse{ ResponseAction: RAClear, } } +// NewUpdateViewSubmissionResponse replaces the current modal with a new view. +// +// For HTTP-based apps, marshal this to JSON and write it as the HTTP response +// body. The response is not sent until the handler returns, so start any slow +// work in a goroutine and return promptly. +// +// For Socket Mode apps, pass this as the payload argument to Ack(). +// +// See https://docs.slack.dev/surfaces/modals#updating_views func NewUpdateViewSubmissionResponse(view *ModalViewRequest) *ViewSubmissionResponse { return &ViewSubmissionResponse{ ResponseAction: RAUpdate, @@ -83,6 +101,15 @@ func NewUpdateViewSubmissionResponse(view *ModalViewRequest) *ViewSubmissionResp } } +// NewPushViewSubmissionResponse pushes a new view onto the modal stack. +// +// For HTTP-based apps, marshal this to JSON and write it as the HTTP response +// body. The response is not sent until the handler returns, so start any slow +// work in a goroutine and return promptly. +// +// For Socket Mode apps, pass this as the payload argument to Ack(). +// +// See https://docs.slack.dev/surfaces/modals#pushing_views func NewPushViewSubmissionResponse(view *ModalViewRequest) *ViewSubmissionResponse { return &ViewSubmissionResponse{ ResponseAction: RAPush, @@ -90,6 +117,19 @@ func NewPushViewSubmissionResponse(view *ModalViewRequest) *ViewSubmissionRespon } } +// NewErrorsViewSubmissionResponse displays validation errors on form fields. +// +// The errors map keys must be the BlockID of an InputBlock in the view. Keys +// that reference other block types (e.g. SectionBlock) are silently ignored +// by Slack, which shows a generic "trouble connecting" error instead. +// +// For HTTP-based apps, marshal this to JSON and write it as the HTTP response +// body. The response is not sent until the handler returns, so start any slow +// work in a goroutine and return promptly. +// +// For Socket Mode apps, pass this as the payload argument to Ack(). +// +// See https://docs.slack.dev/surfaces/modals/#displaying_errors func NewErrorsViewSubmissionResponse(errors map[string]string) *ViewSubmissionResponse { return &ViewSubmissionResponse{ ResponseAction: RAErrors, @@ -167,6 +207,9 @@ func ValidateUniqueBlockID(view ModalViewRequest) bool { for _, b := range view.Blocks.BlockSet { if inputBlock, ok := b.(*InputBlock); ok { + if inputBlock.BlockID == "" { + continue + } if _, ok := uniqueBlockID[inputBlock.BlockID]; ok { return false } @@ -178,7 +221,7 @@ func ValidateUniqueBlockID(view ModalViewRequest) bool { } // OpenViewContext opens a view for a user with a custom context. -// Slack API docs: https://api.slack.com/methods/views.open +// Slack API docs: https://docs.slack.dev/reference/methods/views.open func (api *Client) OpenViewContext( ctx context.Context, triggerID string, @@ -200,9 +243,8 @@ func (api *Client) OpenViewContext( if err != nil { return nil, err } - endpoint := api.endpoint + "views.open" resp := &ViewResponse{} - err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api) + err = api.postJSONMethod(ctx, "views.open", api.token, encoded, resp) if err != nil { return nil, err } @@ -220,7 +262,7 @@ func (api *Client) PublishView(userID string, view HomeTabViewRequest, hash stri } // PublishViewContext publishes a static view for a user with a custom context. -// Slack API docs: https://api.slack.com/methods/views.publish +// Slack API docs: https://docs.slack.dev/reference/methods/views.publish func (api *Client) PublishViewContext( ctx context.Context, req PublishViewContextRequest, @@ -232,9 +274,8 @@ func (api *Client) PublishViewContext( if err != nil { return nil, err } - endpoint := api.endpoint + "views.publish" resp := &ViewResponse{} - err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api) + err = api.postJSONMethod(ctx, "views.publish", api.token, encoded, resp) if err != nil { return nil, err } @@ -248,7 +289,7 @@ func (api *Client) PushView(triggerID string, view ModalViewRequest) (*ViewRespo } // PushViewContext pushes a view onto the stack of a root view with a custom context. -// Slack API docs: https://api.slack.com/methods/views.push +// Slack API docs: https://docs.slack.dev/reference/methods/views.push func (api *Client) PushViewContext( ctx context.Context, triggerID string, @@ -265,9 +306,8 @@ func (api *Client) PushViewContext( if err != nil { return nil, err } - endpoint := api.endpoint + "views.push" resp := &ViewResponse{} - err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api) + err = api.postJSONMethod(ctx, "views.push", api.token, encoded, resp) if err != nil { return nil, err } @@ -281,7 +321,7 @@ func (api *Client) UpdateView(view ModalViewRequest, externalID, hash, viewID st } // UpdateViewContext updates an existing view with a custom context. -// Slack API docs: https://api.slack.com/methods/views.update +// Slack API docs: https://docs.slack.dev/reference/methods/views.update func (api *Client) UpdateViewContext( ctx context.Context, view ModalViewRequest, @@ -301,9 +341,8 @@ func (api *Client) UpdateViewContext( if err != nil { return nil, err } - endpoint := api.endpoint + "views.update" resp := &ViewResponse{} - err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api) + err = api.postJSONMethod(ctx, "views.update", api.token, encoded, resp) if err != nil { return nil, err } diff --git a/vendor/github.com/slack-go/slack/webhooks.go b/vendor/github.com/slack-go/slack/webhooks.go index 5a854f38..729bce40 100644 --- a/vendor/github.com/slack-go/slack/webhooks.go +++ b/vendor/github.com/slack-go/slack/webhooks.go @@ -23,8 +23,8 @@ type WebhookMessage struct { ReplaceOriginal bool `json:"replace_original"` DeleteOriginal bool `json:"delete_original"` ReplyBroadcast bool `json:"reply_broadcast,omitempty"` - UnfurlLinks bool `json:"unfurl_links,omitempty"` - UnfurlMedia bool `json:"unfurl_media,omitempty"` + UnfurlLinks *bool `json:"unfurl_links,omitempty"` + UnfurlMedia *bool `json:"unfurl_media,omitempty"` } func PostWebhook(url string, msg *WebhookMessage) error { diff --git a/vendor/github.com/slack-go/slack/websocket_groups.go b/vendor/github.com/slack-go/slack/websocket_groups.go index eb88985c..c35d5f35 100644 --- a/vendor/github.com/slack-go/slack/websocket_groups.go +++ b/vendor/github.com/slack-go/slack/websocket_groups.go @@ -7,9 +7,6 @@ type GroupCreatedEvent struct { Channel ChannelCreatedInfo `json:"channel"` } -// XXX: Should we really do this? event.Group is probably nicer than event.Channel -// even though the api returns "channel" - // GroupMarkedEvent represents the Group marked event type GroupMarkedEvent ChannelInfoEvent diff --git a/vendor/github.com/slack-go/slack/websocket_managed_conn.go b/vendor/github.com/slack-go/slack/websocket_managed_conn.go index f107b2a4..da861fa8 100644 --- a/vendor/github.com/slack-go/slack/websocket_managed_conn.go +++ b/vendor/github.com/slack-go/slack/websocket_managed_conn.go @@ -582,7 +582,10 @@ var EventMapping = map[string]interface{}{ "manual_presence_change": ManualPresenceChangeEvent{}, - "user_change": UserChangeEvent{}, + "user_change": UserChangeEvent{}, + "user_status_changed": UserStatusChangedEvent{}, + "user_huddle_changed": UserHuddleChangedEvent{}, + "user_profile_changed": UserProfileChangedEvent{}, "emoji_changed": EmojiChangedEvent{}, @@ -595,6 +598,10 @@ var EventMapping = map[string]interface{}{ "accounts_changed": AccountsChangedEvent{}, + "apps_uninstalled": AppsUninstalledEvent{}, + "activity": ActivityEvent{}, + "badge_counts_updated": BadgeCountsUpdatedEvent{}, + "reconnect_url": ReconnectUrlEvent{}, "member_joined_channel": MemberJoinedChannelEvent{}, @@ -608,4 +615,10 @@ var EventMapping = map[string]interface{}{ "desktop_notification": DesktopNotificationEvent{}, "mobile_in_app_notification": MobileInAppNotificationEvent{}, + + "channel_updated": ChannelUpdatedEvent{}, + + "sh_room_join": SHRoomJoinEvent{}, + "sh_room_leave": SHRoomLeaveEvent{}, + "sh_room_update": SHRoomUpdateEvent{}, } diff --git a/vendor/github.com/slack-go/slack/websocket_misc.go b/vendor/github.com/slack-go/slack/websocket_misc.go index 65a8bb65..fb301f5e 100644 --- a/vendor/github.com/slack-go/slack/websocket_misc.go +++ b/vendor/github.com/slack-go/slack/websocket_misc.go @@ -71,8 +71,34 @@ type ManualPresenceChangeEvent struct { // UserChangeEvent represents the user change event type UserChangeEvent struct { - Type string `json:"type"` - User User `json:"user"` + Type string `json:"type"` + User User `json:"user"` + CacheTS int64 `json:"cache_ts"` + EventTS string `json:"event_ts"` +} + +// UserStatusChangedEvent represents the user status changed event +type UserStatusChangedEvent struct { + Type string `json:"type"` + User User `json:"user"` + CacheTS int64 `json:"cache_ts"` + EventTS string `json:"event_ts"` +} + +// UserHuddleChangedEvent represents the user huddle changed event +type UserHuddleChangedEvent struct { + Type string `json:"type"` + User User `json:"user"` + CacheTS int64 `json:"cache_ts"` + EventTS string `json:"event_ts"` +} + +// UserProfileChangedEvent represents the user profile changed event +type UserProfileChangedEvent struct { + Type string `json:"type"` + User User `json:"user"` + CacheTS int64 `json:"cache_ts"` + EventTS string `json:"event_ts"` } // EmojiChangedEvent represents the emoji changed event @@ -139,3 +165,115 @@ type MemberLeftChannelEvent struct { ChannelType string `json:"channel_type"` Team string `json:"team"` } + +// ChannelUpdatedEvent is fired when a channel's properties are updated (tabs, meeting +// notes, etc.). +type ChannelUpdatedEvent struct { + Type string `json:"type"` + Updates map[string]any `json:"updates"` + Channel string `json:"channel"` + Channels []string `json:"channels"` + EventTS string `json:"event_ts"` + TS string `json:"ts"` +} + +// SHRoomRecording holds recording metadata for a Slack Call/Huddle room. +type SHRoomRecording struct { + CanRecordSummary string `json:"can_record_summary,omitempty"` +} + +// SHRoom represents a Slack Huddle/Call room. +type SHRoom struct { + ID string `json:"id"` + Name *string `json:"name"` // nullable in Slack's response + MediaServer string `json:"media_server"` + CreatedBy string `json:"created_by"` + DateStart int64 `json:"date_start"` + DateEnd int64 `json:"date_end"` + Participants []string `json:"participants"` + ParticipantHistory []string `json:"participant_history"` + ParticipantsEvents map[string]map[string]any `json:"participants_events,omitempty"` + ParticipantsCameraOn []string `json:"participants_camera_on"` + ParticipantsCameraOff []string `json:"participants_camera_off"` + ParticipantsScreenshareOn []string `json:"participants_screenshare_on"` + ParticipantsScreenshareOff []string `json:"participants_screenshare_off"` + CanvasThreadTS string `json:"canvas_thread_ts,omitempty"` + ThreadRootTS string `json:"thread_root_ts,omitempty"` + Channels []string `json:"channels"` + IsDMCall bool `json:"is_dm_call"` + WasRejected bool `json:"was_rejected"` + WasMissed bool `json:"was_missed"` + WasAccepted bool `json:"was_accepted"` + HasEnded bool `json:"has_ended"` + BackgroundID string `json:"background_id,omitempty"` + CanvasBackground string `json:"canvas_background,omitempty"` + IsPrewarmed bool `json:"is_prewarmed,omitempty"` + IsScheduled bool `json:"is_scheduled,omitempty"` + Recording *SHRoomRecording `json:"recording,omitempty"` + Locale string `json:"locale,omitempty"` + AttachedFileIDs []string `json:"attached_file_ids,omitempty"` + MediaBackendType string `json:"media_backend_type"` + DisplayID string `json:"display_id,omitempty"` + ExternalUniqueID string `json:"external_unique_id"` + AppID string `json:"app_id"` + CallFamily string `json:"call_family,omitempty"` + HuddleLink string `json:"huddle_link,omitempty"` +} + +// SHRoomHuddle holds the huddle-specific metadata on sh_room events. +type SHRoomHuddle struct { + ChannelID string `json:"channel_id"` +} + +// SHRoomJoinEvent is fired when a user joins a Slack Call/Huddle room. +type SHRoomJoinEvent struct { + Type string `json:"type"` + Room SHRoom `json:"room"` + User string `json:"user"` + Huddle *SHRoomHuddle `json:"huddle,omitempty"` + EventTS string `json:"event_ts"` + TS string `json:"ts"` +} + +// SHRoomLeaveEvent is fired when a user leaves a Slack Call/Huddle room. +type SHRoomLeaveEvent struct { + Type string `json:"type"` + Room SHRoom `json:"room"` + User string `json:"user"` + Huddle *SHRoomHuddle `json:"huddle,omitempty"` + EventTS string `json:"event_ts"` + TS string `json:"ts"` +} + +// SHRoomUpdateEvent is fired when a Slack Call/Huddle room is updated. +type SHRoomUpdateEvent struct { + Type string `json:"type"` + Room SHRoom `json:"room"` + User string `json:"user"` + Huddle *SHRoomHuddle `json:"huddle,omitempty"` + EventTS string `json:"event_ts"` + TS string `json:"ts"` +} + +// AppsUninstalledEvent represents the apps_uninstalled event sent via RTM +// when one or more apps are uninstalled from the workspace. +type AppsUninstalledEvent struct { + Type string `json:"type"` +} + +// ActivityEvent represents the activity event sent via RTM. This is an +// internal Slack event that fires during normal workspace usage (e.g. new +// messages, bundle updates). +type ActivityEvent struct { + Type string `json:"type"` + SubType string `json:"subtype"` + Key string `json:"key"` + Entry json.RawMessage `json:"entry"` + EventTimestamp string `json:"event_ts"` +} + +// BadgeCountsUpdatedEvent represents the badge_counts_updated event sent via +// RTM when notification badge counts change. +type BadgeCountsUpdatedEvent struct { + Type string `json:"type"` +} diff --git a/vendor/github.com/slack-go/slack/workflows_featured.go b/vendor/github.com/slack-go/slack/workflows_featured.go new file mode 100644 index 00000000..08b33986 --- /dev/null +++ b/vendor/github.com/slack-go/slack/workflows_featured.go @@ -0,0 +1,143 @@ +package slack + +import ( + "context" + "encoding/json" + "fmt" +) + +type ( + FeaturedWorkflowTrigger struct { + ID string `json:"id"` + Title string `json:"title"` + } + + FeaturedWorkflow struct { + ChannelID string `json:"channel_id"` + Triggers []FeaturedWorkflowTrigger `json:"triggers"` + } + + WorkflowsFeaturedAddInput struct { + ChannelID string `json:"channel_id"` + TriggerIDs []string `json:"trigger_ids"` + } + + WorkflowsFeaturedListInput struct { + ChannelIDs []string `json:"channel_ids"` + } + + WorkflowsFeaturedListOutput struct { + FeaturedWorkflows []FeaturedWorkflow `json:"featured_workflows"` + } + + WorkflowsFeaturedRemoveInput struct { + ChannelID string `json:"channel_id"` + TriggerIDs []string `json:"trigger_ids"` + } + + WorkflowsFeaturedSetInput struct { + ChannelID string `json:"channel_id"` + TriggerIDs []string `json:"trigger_ids"` + } +) + +// WorkflowsFeaturedAdd adds featured workflows to a channel. +// +// Slack API Docs:https://api.slack.com/methods/workflows.featured.add +func (api *Client) WorkflowsFeaturedAdd(ctx context.Context, input *WorkflowsFeaturedAddInput) error { + response := struct { + SlackResponse + }{} + + jsonPayload, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to marshal WorkflowsFeaturedAddInput: %w", err) + } + + err = api.postJSONMethod(ctx, "workflows.featured.add", api.token, jsonPayload, &response) + if err != nil { + return err + } + + if err := response.Err(); err != nil { + return err + } + + return nil +} + +// WorkflowsFeaturedList lists featured workflows for the given channels. +// +// Slack API Docs:https://api.slack.com/methods/workflows.featured.list +func (api *Client) WorkflowsFeaturedList(ctx context.Context, input *WorkflowsFeaturedListInput) (*WorkflowsFeaturedListOutput, error) { + response := struct { + SlackResponse + *WorkflowsFeaturedListOutput + }{} + + jsonPayload, err := json.Marshal(input) + if err != nil { + return nil, fmt.Errorf("failed to marshal WorkflowsFeaturedListInput: %w", err) + } + + err = api.postJSONMethod(ctx, "workflows.featured.list", api.token, jsonPayload, &response) + if err != nil { + return nil, err + } + + if err := response.Err(); err != nil { + return nil, err + } + + return response.WorkflowsFeaturedListOutput, nil +} + +// WorkflowsFeaturedRemove removes featured workflows from a channel. +// +// Slack API Docs:https://api.slack.com/methods/workflows.featured.remove +func (api *Client) WorkflowsFeaturedRemove(ctx context.Context, input *WorkflowsFeaturedRemoveInput) error { + response := struct { + SlackResponse + }{} + + jsonPayload, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to marshal WorkflowsFeaturedRemoveInput: %w", err) + } + + err = api.postJSONMethod(ctx, "workflows.featured.remove", api.token, jsonPayload, &response) + if err != nil { + return err + } + + if err := response.Err(); err != nil { + return err + } + + return nil +} + +// WorkflowsFeaturedSet replaces all featured workflows in a channel with the given triggers. +// +// Slack API Docs:https://api.slack.com/methods/workflows.featured.set +func (api *Client) WorkflowsFeaturedSet(ctx context.Context, input *WorkflowsFeaturedSetInput) error { + response := struct { + SlackResponse + }{} + + jsonPayload, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to marshal WorkflowsFeaturedSetInput: %w", err) + } + + err = api.postJSONMethod(ctx, "workflows.featured.set", api.token, jsonPayload, &response) + if err != nil { + return err + } + + if err := response.Err(); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/slack-go/slack/workflows_triggers.go b/vendor/github.com/slack-go/slack/workflows_triggers.go index f9a7cd90..34834806 100644 --- a/vendor/github.com/slack-go/slack/workflows_triggers.go +++ b/vendor/github.com/slack-go/slack/workflows_triggers.go @@ -84,7 +84,7 @@ func (api *Client) WorkflowsTriggersPermissionsAdd(ctx context.Context, input *W return nil, fmt.Errorf("failed to marshal WorkflowsTriggersPermissionsAddInput: %w", err) } - err = postJSON(ctx, api.httpclient, api.endpoint+"workflows.triggers.permissions.add", api.token, jsonPayload, &response, api) + err = api.postJSONMethod(ctx, "workflows.triggers.permissions.add", api.token, jsonPayload, &response) if err != nil { return nil, err } @@ -111,7 +111,7 @@ func (api *Client) WorkflowsTriggersPermissionsList(ctx context.Context, input * return nil, fmt.Errorf("failed to marshal WorkflowsTriggersPermissionsListInput: %w", err) } - err = postJSON(ctx, api.httpclient, api.endpoint+"workflows.triggers.permissions.list", api.token, jsonPayload, &response, api) + err = api.postJSONMethod(ctx, "workflows.triggers.permissions.list", api.token, jsonPayload, &response) if err != nil { return nil, err } @@ -138,7 +138,7 @@ func (api *Client) WorkflowsTriggersPermissionsRemove(ctx context.Context, input return nil, fmt.Errorf("failed to marshal WorkflowsTriggersPermissionsRemoveInput: %w", err) } - err = postJSON(ctx, api.httpclient, api.endpoint+"workflows.triggers.permissions.remove", api.token, jsonPayload, &response, api) + err = api.postJSONMethod(ctx, "workflows.triggers.permissions.remove", api.token, jsonPayload, &response) if err != nil { return nil, err } @@ -164,7 +164,7 @@ func (api *Client) WorkflowsTriggersPermissionsSet(ctx context.Context, input *W return nil, fmt.Errorf("failed to marshal WorkflowsTriggersPermissionsSetInput: %w", err) } - err = postJSON(ctx, api.httpclient, api.endpoint+"workflows.triggers.permissions.set", api.token, jsonPayload, &response, api) + err = api.postJSONMethod(ctx, "workflows.triggers.permissions.set", api.token, jsonPayload, &response) if err != nil { return nil, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index d64a581a..1c08f551 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -79,7 +79,7 @@ github.com/pmezard/go-difflib/difflib # github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec ## explicit; go 1.12 github.com/remyoudompheng/bigfft -# github.com/slack-go/slack v0.20.0 +# github.com/slack-go/slack v0.23.1 ## explicit; go 1.25 github.com/slack-go/slack github.com/slack-go/slack/internal/backoff