diff --git a/go.mod b/go.mod index 2e0935f..56245a3 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.14 require ( github.com/sdslabs/allot v1.2.6 github.com/shomali11/slacker v1.2.0 - github.com/slack-go/slack v0.11.0 + github.com/slack-go/slack v0.12.1 ) diff --git a/go.sum b/go.sum index 1ef637f..a5309a0 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,9 @@ github.com/shomali11/proper v0.0.0-20180607004733-233a9a872c30 h1:56awf1OXG6Jc2P github.com/shomali11/proper v0.0.0-20180607004733-233a9a872c30/go.mod h1:O723XwIZBX3FR45rBic/Eyp/DKo/YtchYFURzpUWY2c= github.com/shomali11/slacker v1.2.0 h1:thQumhzlxGJoUlvUna9ei+UrDNKBDPWJxBUs8KnGsVQ= github.com/shomali11/slacker v1.2.0/go.mod h1:PIklQ8hzev0yT5eAxCMmyX1X9Lm/O0GauKNrUOsWhK4= -github.com/slack-go/slack v0.11.0 h1:sBBjQz8LY++6eeWhGJNZpRm5jvLRNnWBFZ/cAq58a6k= github.com/slack-go/slack v0.11.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw= +github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/vendor/github.com/slack-go/slack/auth.go b/vendor/github.com/slack-go/slack/auth.go index f4f7f00..bf6e80d 100644 --- a/vendor/github.com/slack-go/slack/auth.go +++ b/vendor/github.com/slack-go/slack/auth.go @@ -38,3 +38,37 @@ func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*Au return api.authRequest(ctx, "auth.revoke", values) } + +type listTeamsResponse struct { + Teams []Team `json:"teams"` + SlackResponse +} + +type ListTeamsParameters struct { + Limit int + Cursor string +} + +// ListTeams returns all workspaces a token can access. +// More info: https://api.slack.com/methods/admin.teams.list +func (api *Client) ListTeams(params ListTeamsParameters) ([]Team, string, error) { + return api.ListTeamsContext(context.Background(), params) +} + +// ListTeams returns all workspaces a token can access with a custom context. +func (api *Client) ListTeamsContext(ctx context.Context, params ListTeamsParameters) ([]Team, string, error) { + values := url.Values{ + "token": {api.token}, + } + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + + response := &listTeamsResponse{} + err := api.postMethod(ctx, "auth.teams.list", values, response) + if err != nil { + return nil, "", err + } + + return response.Teams, response.ResponseMetadata.Cursor, response.Err() +} diff --git a/vendor/github.com/slack-go/slack/block_conv.go b/vendor/github.com/slack-go/slack/block_conv.go index 1a2c57e..555e481 100644 --- a/vendor/github.com/slack-go/slack/block_conv.go +++ b/vendor/github.com/slack-go/slack/block_conv.go @@ -112,6 +112,10 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error { e = &TimePickerBlockElement{} case "plain_text_input": e = &PlainTextInputBlockElement{} + case "email_text_input": + e = &EmailTextInputBlockElement{} + case "url_text_input": + e = &URLTextInputBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": e = &SelectBlockElement{} case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select": @@ -122,6 +126,8 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error { e = &OverflowBlockElement{} case "radio_buttons": e = &RadioButtonsBlockElement{} + case "number_input": + e = &NumberInputBlockElement{} default: return errors.New("unsupported block element type") } @@ -186,12 +192,18 @@ func (b *BlockElements) UnmarshalJSON(data []byte) error { blockElement = &TimePickerBlockElement{} case "plain_text_input": blockElement = &PlainTextInputBlockElement{} + case "email_text_input": + blockElement = &EmailTextInputBlockElement{} + case "url_text_input": + blockElement = &URLTextInputBlockElement{} case "checkboxes": blockElement = &CheckboxGroupsBlockElement{} case "radio_buttons": blockElement = &RadioButtonsBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": blockElement = &SelectBlockElement{} + case "number_input": + blockElement = &NumberInputBlockElement{} default: return fmt.Errorf("unsupported block element type %v", blockElementType) } diff --git a/vendor/github.com/slack-go/slack/block_element.go b/vendor/github.com/slack-go/slack/block_element.go index 643529f..aba29c6 100644 --- a/vendor/github.com/slack-go/slack/block_element.go +++ b/vendor/github.com/slack-go/slack/block_element.go @@ -11,6 +11,9 @@ const ( METTimepicker MessageElementType = "timepicker" METPlainTextInput MessageElementType = "plain_text_input" METRadioButtons MessageElementType = "radio_buttons" + METEmailTextInput MessageElementType = "email_text_input" + METURLTextInput MessageElementType = "url_text_input" + METNumber MessageElementType = "number_input" MixedElementImage MixedElementType = "mixed_image" MixedElementText MixedElementType = "mixed_text" @@ -389,6 +392,64 @@ func NewTimePickerBlockElement(actionID string) *TimePickerBlockElement { } } +// EmailTextInputBlockElement creates a field where a user can enter email +// data. +// email-text-input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#email +type EmailTextInputBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` + FocusOnLoad bool `json:"focus_on_load,omitempty"` +} + +// ElementType returns the type of the Element +func (s EmailTextInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewEmailTextInputBlockElement returns an instance of a plain-text input +// element +func NewEmailTextInputBlockElement(placeholder *TextBlockObject, actionID string) *EmailTextInputBlockElement { + return &EmailTextInputBlockElement{ + Type: METEmailTextInput, + ActionID: actionID, + Placeholder: placeholder, + } +} + +// URLTextInputBlockElement creates a field where a user can enter url data. +// +// url-text-input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#url +type URLTextInputBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` + FocusOnLoad bool `json:"focus_on_load,omitempty"` +} + +// ElementType returns the type of the Element +func (s URLTextInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewURLTextInputBlockElement returns an instance of a plain-text input +// element +func NewURLTextInputBlockElement(placeholder *TextBlockObject, actionID string) *URLTextInputBlockElement { + return &URLTextInputBlockElement{ + Type: METURLTextInput, + ActionID: actionID, + Placeholder: placeholder, + } +} + // PlainTextInputBlockElement creates a field where a user can enter freeform // data. // Plain-text input elements are currently only available in modals. @@ -475,3 +536,34 @@ func NewRadioButtonsBlockElement(actionID string, options ...*OptionBlockObject) Options: options, } } + +// NumberInputBlockElement creates a field where a user can enter number +// data. +// Number input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#number +type NumberInputBlockElement struct { + Type MessageElementType `json:"type"` + IsDecimalAllowed bool `json:"is_decimal_allowed"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + MinValue string `json:"min_value,omitempty"` + MaxValue string `json:"max_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` +} + +// ElementType returns the type of the Element +func (s NumberInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewNumberInputBlockElement returns an instance of a number input element +func NewNumberInputBlockElement(placeholder *TextBlockObject, actionID string, isDecimalAllowed bool) *NumberInputBlockElement { + return &NumberInputBlockElement{ + Type: METNumber, + ActionID: actionID, + Placeholder: placeholder, + IsDecimalAllowed: isDecimalAllowed, + } +} 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 281db21..555a619 100644 --- a/vendor/github.com/slack-go/slack/block_rich_text.go +++ b/vendor/github.com/slack-go/slack/block_rich_text.go @@ -327,17 +327,17 @@ func NewRichTextSectionUserGroupElement(usergroupID string) *RichTextSectionUser type RichTextSectionDateElement struct { Type RichTextSectionElementType `json:"type"` - Timestamp string `json:"timestamp"` + Timestamp JSONTime `json:"timestamp"` } func (r RichTextSectionDateElement) RichTextSectionElementType() RichTextSectionElementType { return r.Type } -func NewRichTextSectionDateElement(timestamp string) *RichTextSectionDateElement { +func NewRichTextSectionDateElement(timestamp int64) *RichTextSectionDateElement { return &RichTextSectionDateElement{ Type: RTSEDate, - Timestamp: timestamp, + Timestamp: JSONTime(timestamp), } } diff --git a/vendor/github.com/slack-go/slack/chat.go b/vendor/github.com/slack-go/slack/chat.go index 34848d1..4448e9f 100644 --- a/vendor/github.com/slack-go/slack/chat.go +++ b/vendor/github.com/slack-go/slack/chat.go @@ -64,6 +64,9 @@ type PostMessageParameters struct { // chat.postEphemeral support Channel string `json:"channel"` User string `json:"user"` + + // chat metadata support + MetaData SlackMetadata `json:"metadata"` } // NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set @@ -285,6 +288,7 @@ type sendConfig struct { endpoint string values url.Values attachments []Attachment + metadata SlackMetadata blocks Blocks responseType string replaceOriginal bool @@ -306,6 +310,7 @@ func (t sendConfig) BuildRequestContext(ctx context.Context, token, channelID st endpoint: t.endpoint, values: t.values, attachments: t.attachments, + metadata: t.metadata, blocks: t.blocks, responseType: t.responseType, replaceOriginal: t.replaceOriginal, @@ -336,6 +341,7 @@ type responseURLSender struct { endpoint string values url.Values attachments []Attachment + metadata SlackMetadata blocks Blocks responseType string replaceOriginal bool @@ -352,6 +358,7 @@ func (t responseURLSender) BuildRequestContext(ctx context.Context) (*http.Reque Timestamp: t.values.Get("ts"), Attachments: t.attachments, Blocks: t.blocks, + Metadata: t.metadata, ResponseType: t.responseType, ReplaceOriginal: t.replaceOriginal, DeleteOriginal: t.deleteOriginal, @@ -662,6 +669,18 @@ func MsgOptionIconEmoji(iconEmoji string) MsgOption { } } +// MsgOptionMetadata sets message metadata +func MsgOptionMetadata(metadata SlackMetadata) MsgOption { + return func(config *sendConfig) error { + config.metadata = metadata + meta, err := json.Marshal(metadata) + if err == nil { + config.values.Set("metadata", string(meta)) + } + return err + } +} + // 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 diff --git a/vendor/github.com/slack-go/slack/conversation.go b/vendor/github.com/slack-go/slack/conversation.go index 2993626..44f06ea 100644 --- a/vendor/github.com/slack-go/slack/conversation.go +++ b/vendor/github.com/slack-go/slack/conversation.go @@ -2,6 +2,7 @@ package slack import ( "context" + "errors" "net/url" "strconv" "strings" @@ -71,6 +72,7 @@ type GetConversationsForUserParameters struct { Types []string Limit int ExcludeArchived bool + TeamID string } type responseMetaData struct { @@ -137,6 +139,10 @@ func (api *Client) GetConversationsForUserContext(ctx context.Context, params *G if params.ExcludeArchived { values.Add("exclude_archived", "true") } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } + response := struct { Channels []Channel `json:"channels"` ResponseMetaData responseMetaData `json:"response_metadata"` @@ -337,17 +343,26 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin return response.NoOp, response.AlreadyClosed, response.Err() } +type CreateConversationParams struct { + ChannelName string + IsPrivate bool + TeamID string +} + // CreateConversation initiates a public or private channel-based conversation -func (api *Client) CreateConversation(channelName string, isPrivate bool) (*Channel, error) { - return api.CreateConversationContext(context.Background(), channelName, isPrivate) +func (api *Client) CreateConversation(params CreateConversationParams) (*Channel, error) { + return api.CreateConversationContext(context.Background(), params) } // CreateConversationContext initiates a public or private channel-based conversation with a custom context -func (api *Client) CreateConversationContext(ctx context.Context, channelName string, isPrivate bool) (*Channel, error) { +func (api *Client) CreateConversationContext(ctx context.Context, params CreateConversationParams) (*Channel, error) { values := url.Values{ "token": {api.token}, - "name": {channelName}, - "is_private": {strconv.FormatBool(isPrivate)}, + "name": {params.ChannelName}, + "is_private": {strconv.FormatBool(params.IsPrivate)}, + } + if params.TeamID != "" { + values.Set("team_id", params.TeamID) } response, err := api.channelRequest(ctx, "conversations.create", values) if err != nil { @@ -357,17 +372,33 @@ func (api *Client) CreateConversationContext(ctx context.Context, channelName st return &response.Channel, nil } +// GetConversationInfoInput Defines the parameters of a GetConversationInfo and GetConversationInfoContext function +type GetConversationInfoInput struct { + ChannelID string + IncludeLocale bool + IncludeNumMembers bool +} + // GetConversationInfo retrieves information about a conversation -func (api *Client) GetConversationInfo(channelID string, includeLocale bool) (*Channel, error) { - return api.GetConversationInfoContext(context.Background(), channelID, includeLocale) +func (api *Client) GetConversationInfo(input *GetConversationInfoInput) (*Channel, error) { + return api.GetConversationInfoContext(context.Background(), input) } // GetConversationInfoContext retrieves information about a conversation with a custom context -func (api *Client) GetConversationInfoContext(ctx context.Context, channelID string, includeLocale bool) (*Channel, error) { +func (api *Client) GetConversationInfoContext(ctx context.Context, input *GetConversationInfoInput) (*Channel, error) { + if input == nil { + return nil, errors.New("GetConversationInfoInput must not be nil") + } + + if input.ChannelID == "" { + return nil, errors.New("ChannelID must be defined") + } + values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "include_locale": {strconv.FormatBool(includeLocale)}, + "token": {api.token}, + "channel": {input.ChannelID}, + "include_locale": {strconv.FormatBool(input.IncludeLocale)}, + "include_num_members": {strconv.FormatBool(input.IncludeNumMembers)}, } response, err := api.channelRequest(ctx, "conversations.info", values) if err != nil { @@ -398,13 +429,14 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin } type GetConversationRepliesParameters struct { - ChannelID string - Timestamp string - Cursor string - Inclusive bool - Latest string - Limit int - Oldest string + ChannelID string + Timestamp string + Cursor string + Inclusive bool + Latest string + Limit int + Oldest string + IncludeAllMetadata bool } // GetConversationReplies retrieves a thread of messages posted to a conversation @@ -436,6 +468,11 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge } else { values.Add("inclusive", "0") } + if params.IncludeAllMetadata { + values.Add("include_all_metadata", "1") + } else { + values.Add("include_all_metadata", "0") + } response := struct { SlackResponse HasMore bool `json:"has_more"` @@ -571,12 +608,13 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string } type GetConversationHistoryParameters struct { - ChannelID string - Cursor string - Inclusive bool - Latest string - Limit int - Oldest string + ChannelID string + Cursor string + Inclusive bool + Latest string + Limit int + Oldest string + IncludeAllMetadata bool } type GetConversationHistoryResponse struct { @@ -615,6 +653,11 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge if params.Oldest != "" { values.Add("oldest", params.Oldest) } + if params.IncludeAllMetadata { + values.Add("include_all_metadata", "1") + } else { + values.Add("include_all_metadata", "0") + } response := GetConversationHistoryResponse{} diff --git a/vendor/github.com/slack-go/slack/dialog_text.go b/vendor/github.com/slack-go/slack/dialog_text.go index da06bd6..25fa1b6 100644 --- a/vendor/github.com/slack-go/slack/dialog_text.go +++ b/vendor/github.com/slack-go/slack/dialog_text.go @@ -18,7 +18,7 @@ const ( ) // TextInputElement subtype of DialogInput -// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes +// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes type TextInputElement struct { DialogInput MaxLength int `json:"max_length,omitempty"` diff --git a/vendor/github.com/slack-go/slack/files.go b/vendor/github.com/slack-go/slack/files.go index e7e71c4..3562844 100644 --- a/vendor/github.com/slack-go/slack/files.go +++ b/vendor/github.com/slack-go/slack/files.go @@ -2,6 +2,7 @@ package slack import ( "context" + "encoding/json" "fmt" "io" "net/url" @@ -145,6 +146,58 @@ type ListFilesParameters struct { Cursor string } +type UploadFileV2Parameters struct { + File string + FileSize int + Content string + Reader io.Reader + Filename string + Title string + InitialComment string + Channel string + ThreadTimestamp string + AltTxt string + SnippetText string +} + +type getUploadURLExternalParameters struct { + altText string + fileSize int + fileName string + snippetText string +} + +type getUploadURLExternalResponse struct { + UploadURL string `json:"upload_url"` + FileID string `json:"file_id"` + SlackResponse +} + +type uploadToURLParameters struct { + UploadURL string + Reader io.Reader + File string + Content string + Filename string +} + +type FileSummary struct { + ID string `json:"id"` + Title string `json:"title"` +} + +type completeUploadExternalParameters struct { + title string + channel string + initialComment string + threadTimestamp string +} + +type completeUploadExternalResponse struct { + SlackResponse + Files []FileSummary `json:"files"` +} + type fileResponseFull struct { File `json:"file"` Paging `json:"paging"` @@ -299,7 +352,7 @@ func (api *Client) UploadFile(params FileUploadParameters) (file *File, err erro func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) { // Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More // investigation needed, but for now this will do. - _, err = api.AuthTest() + _, err = api.AuthTestContext(ctx) if err != nil { return nil, err } @@ -416,3 +469,129 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) } return &response.File, response.Comments, &response.Paging, nil } + +// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file +func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURLExternalParameters) (*getUploadURLExternalResponse, error) { + values := url.Values{ + "token": {api.token}, + "filename": {params.fileName}, + "length": {strconv.Itoa(params.fileSize)}, + } + if params.altText != "" { + values.Add("initial_comment", params.altText) + } + if params.snippetText != "" { + values.Add("thread_ts", params.snippetText) + } + response := &getUploadURLExternalResponse{} + err := api.postMethod(ctx, "files.getUploadURLExternal", values, response) + if err != nil { + return nil, err + } + + return response, response.Err() +} + +// uploadToURL uploads the file to the provided URL using post method +func (api *Client) uploadToURL(ctx context.Context, params uploadToURLParameters) (err error) { + values := url.Values{} + if params.Content != "" { + values.Add("content", params.Content) + values.Add("token", api.token) + err = postForm(ctx, api.httpclient, params.UploadURL, values, nil, api) + } else if params.File != "" { + err = postLocalWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.File, "file", api.token, values, nil, api) + } else if params.Reader != nil { + err = postWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.Filename, "file", api.token, values, params.Reader, nil, api) + } + return err +} + +// completeUploadExternal once files are uploaded, this completes the upload and shares it to the specified channel +func (api *Client) completeUploadExternal(ctx context.Context, fileID string, params completeUploadExternalParameters) (file *completeUploadExternalResponse, err error) { + request := []FileSummary{{ID: fileID, Title: params.title}} + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + values := url.Values{ + "token": {api.token}, + "files": {string(requestBytes)}, + "channel_id": {params.channel}, + } + + if params.initialComment != "" { + values.Add("initial_comment", params.initialComment) + } + if params.threadTimestamp != "" { + values.Add("thread_ts", params.threadTimestamp) + } + response := &completeUploadExternalResponse{} + err = api.postMethod(ctx, "files.completeUploadExternal", values, response) + if err != nil { + return nil, err + } + if response.Err() != nil { + return nil, response.Err() + } + return response, nil +} + +// UploadFileV2 uploads file to a given slack channel using 3 steps - +// 1. Get an upload URL using files.getUploadURLExternal API +// 2. Send the file as a post to the URL provided by slack +// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal +func (api *Client) UploadFileV2(params UploadFileV2Parameters) (*FileSummary, error) { + return api.UploadFileV2Context(context.Background(), params) +} + +// UploadFileV2 uploads file to a given slack channel using 3 steps with a custom context - +// 1. Get an upload URL using files.getUploadURLExternal API +// 2. Send the file as a post to the URL provided by slack +// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal +func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2Parameters) (file *FileSummary, err error) { + if params.Filename == "" { + return nil, fmt.Errorf("file.upload.v2: filename cannot be empty") + } + if params.FileSize == 0 { + return nil, fmt.Errorf("file.upload.v2: file size cannot be 0") + } + if params.Channel == "" { + return nil, fmt.Errorf("file.upload.v2: channel cannot be empty") + } + u, err := api.getUploadURLExternal(ctx, getUploadURLExternalParameters{ + altText: params.AltTxt, + fileName: params.Filename, + fileSize: params.FileSize, + snippetText: params.SnippetText, + }) + if err != nil { + return nil, err + } + + err = api.uploadToURL(ctx, uploadToURLParameters{ + UploadURL: u.UploadURL, + Reader: params.Reader, + File: params.File, + Content: params.Content, + Filename: params.Filename, + }) + if err != nil { + return nil, err + } + + c, err := api.completeUploadExternal(ctx, u.FileID, completeUploadExternalParameters{ + title: params.Title, + channel: params.Channel, + initialComment: params.InitialComment, + threadTimestamp: params.ThreadTimestamp, + }) + if err != nil { + return nil, err + } + if len(c.Files) != 1 { + return nil, fmt.Errorf("file.upload.v2: something went wrong; received %d files instead of 1", len(c.Files)) + } + + return &c.Files[0], nil +} diff --git a/vendor/github.com/slack-go/slack/info.go b/vendor/github.com/slack-go/slack/info.go index fde2bc9..b06dffd 100644 --- a/vendor/github.com/slack-go/slack/info.go +++ b/vendor/github.com/slack-go/slack/info.go @@ -409,6 +409,11 @@ func (t JSONTime) Time() time.Time { func (t *JSONTime) UnmarshalJSON(buf []byte) error { s := bytes.Trim(buf, `"`) + if bytes.EqualFold(s, []byte("null")) { + *t = JSONTime(0) + return nil + } + v, err := strconv.Atoi(string(s)) if err != nil { return err diff --git a/vendor/github.com/slack-go/slack/internal/backoff/backoff.go b/vendor/github.com/slack-go/slack/internal/backoff/backoff.go index df210f8..833e9f2 100644 --- a/vendor/github.com/slack-go/slack/internal/backoff/backoff.go +++ b/vendor/github.com/slack-go/slack/internal/backoff/backoff.go @@ -51,7 +51,7 @@ func (b *Backoff) Duration() (dur time.Duration) { return dur } -//Resets the current value of the counter back to Min +// Resets the current value of the counter back to Min func (b *Backoff) Reset() { b.attempts = 0 } diff --git a/vendor/github.com/slack-go/slack/messages.go b/vendor/github.com/slack-go/slack/messages.go index 2cc31d5..3334045 100644 --- a/vendor/github.com/slack-go/slack/messages.go +++ b/vendor/github.com/slack-go/slack/messages.go @@ -129,8 +129,13 @@ type Msg struct { ReplaceOriginal bool `json:"replace_original"` DeleteOriginal bool `json:"delete_original"` + // metadata + Metadata SlackMetadata `json:"metadata,omitempty"` + // Block type Message Blocks Blocks `json:"blocks,omitempty"` + // permalink + Permalink string `json:"permalink,omitempty"` } const ( diff --git a/vendor/github.com/slack-go/slack/metadata.go b/vendor/github.com/slack-go/slack/metadata.go new file mode 100644 index 0000000..a8c0650 --- /dev/null +++ b/vendor/github.com/slack-go/slack/metadata.go @@ -0,0 +1,7 @@ +package slack + +// SlackMetadata https://api.slack.com/reference/metadata +type SlackMetadata struct { + EventType string `json:"event_type"` + EventPayload map[string]interface{} `json:"event_payload"` +} diff --git a/vendor/github.com/slack-go/slack/misc.go b/vendor/github.com/slack-go/slack/misc.go index bb99f2c..7e5a8d5 100644 --- a/vendor/github.com/slack-go/slack/misc.go +++ b/vendor/github.com/slack-go/slack/misc.go @@ -50,7 +50,7 @@ type SlackErrorResponse struct { func (r SlackErrorResponse) Error() string { return r.Err } -// RateLimitedError represents the rate limit respond from slack +// RateLimitedError represents the rate limit response from slack type RateLimitedError struct { RetryAfter time.Duration } @@ -307,6 +307,9 @@ type responseParser func(*http.Response) error func newJSONParser(dst interface{}) responseParser { return func(resp *http.Response) error { + if dst == nil { + return nil + } return json.NewDecoder(resp.Body).Decode(dst) } } diff --git a/vendor/github.com/slack-go/slack/slackevents/inner_events.go b/vendor/github.com/slack-go/slack/slackevents/inner_events.go index 02767eb..e88b6ce 100644 --- a/vendor/github.com/slack-go/slack/slackevents/inner_events.go +++ b/vendor/github.com/slack-go/slack/slackevents/inner_events.go @@ -155,6 +155,47 @@ type GroupRenameInfo struct { Created int `json:"created"` } +// FileChangeEvent represents the information associated with the File change +// event. +type FileChangeEvent struct { + Type string `json:"type"` + FileID string `json:"file_id"` + File FileEventFile `json:"file"` +} + +// FileDeletedEvent represents the information associated with the File deleted +// event. +type FileDeletedEvent struct { + Type string `json:"type"` + FileID string `json:"file_id"` + EventTimestamp string `json:"event_ts"` +} + +// FileSharedEvent represents the information associated with the File shared +// event. +type FileSharedEvent struct { + Type string `json:"type"` + ChannelID string `json:"channel_id"` + FileID string `json:"file_id"` + UserID string `json:"user_id"` + File FileEventFile `json:"file"` + EventTimestamp string `json:"event_ts"` +} + +// FileUnsharedEvent represents the information associated with the File +// unshared event. +type FileUnsharedEvent struct { + Type string `json:"type"` + FileID string `json:"file_id"` + File FileEventFile `json:"file"` +} + +// FileEventFile represents information on the specific file being shared in a +// file-related Slack event. +type FileEventFile struct { + ID string `json:"id"` +} + // GridMigrationFinishedEvent An enterprise grid migration has finished on this workspace. type GridMigrationFinishedEvent struct { Type string `json:"type"` @@ -178,11 +219,11 @@ type LinkSharedEvent struct { // compose text area. MessageTimeStamp string `json:"message_ts"` ThreadTimeStamp string `json:"thread_ts"` - Links []sharedLinks `json:"links"` + Links []SharedLinks `json:"links"` EventTimestamp string `json:"event_ts"` } -type sharedLinks struct { +type SharedLinks struct { Domain string `json:"domain"` URL string `json:"url"` } @@ -332,6 +373,47 @@ type WorkflowStepExecuteEvent struct { EventTimestamp string `json:"event_ts"` } +// MessageMetadataPostedEvent is sent, if a message with metadata is posted +type MessageMetadataPostedEvent struct { + Type string `json:"type"` + AppId string `json:"app_id"` + BotId string `json:"bot_id"` + UserId string `json:"user_id"` + TeamId string `json:"team_id"` + ChannelId string `json:"channel_id"` + Metadata *slack.SlackMetadata `json:"metadata"` + MessageTimestamp string `json:"message_ts"` + EventTimestamp string `json:"event_ts"` +} + +// MessageMetadataUpdatedEvent is sent, if a message with metadata is deleted +type MessageMetadataUpdatedEvent struct { + Type string `json:"type"` + ChannelId string `json:"channel_id"` + EventTimestamp string `json:"event_ts"` + PreviousMetadata *slack.SlackMetadata `json:"previous_metadata"` + AppId string `json:"app_id"` + BotId string `json:"bot_id"` + UserId string `json:"user_id"` + TeamId string `json:"team_id"` + MessageTimestamp string `json:"message_ts"` + Metadata *slack.SlackMetadata `json:"metadata"` +} + +// MessageMetadataDeletedEvent is sent, if a message with metadata is deleted +type MessageMetadataDeletedEvent struct { + Type string `json:"type"` + ChannelId string `json:"channel_id"` + EventTimestamp string `json:"event_ts"` + PreviousMetadata *slack.SlackMetadata `json:"previous_metadata"` + AppId string `json:"app_id"` + BotId string `json:"bot_id"` + UserId string `json:"user_id"` + TeamId string `json:"team_id"` + MessageTimestamp string `json:"message_ts"` + DeletedTimestamp string `json:"deleted_ts"` +} + type EventWorkflowStep struct { WorkflowStepExecuteID string `json:"workflow_step_execute_id"` WorkflowID string `json:"workflow_id"` @@ -442,6 +524,18 @@ func (e MessageEvent) IsEdited() bool { e.Message.Edited != nil } +// TeamAccessGrantedEvent is sent if access to teams was granted for your org-wide app. +type TeamAccessGrantedEvent struct { + Type string `json:"type"` + TeamIDs []string `json:"team_ids"` +} + +// TeamAccessRevokedEvent is sent if access to teams was revoked for your org-wide app. +type TeamAccessRevokedEvent struct { + Type string `json:"type"` + TeamIDs []string `json:"team_ids"` +} + type EventsAPIType string const ( @@ -475,6 +569,14 @@ const ( GroupLeft = EventsAPIType("group_left") // GroupRename is sent when a group is renamed. GroupRename = EventsAPIType("group_rename") + // FileChange is sent when a file is changed. + FileChange = EventsAPIType("file_change") + // FileDeleted is sent when a file is deleted. + FileDeleted = EventsAPIType("file_deleted") + // FileShared is sent when a file is shared. + FileShared = EventsAPIType("file_shared") + // FileUnshared is sent when a file is unshared. + FileUnshared = EventsAPIType("file_unshared") // GridMigrationFinished An enterprise grid migration has finished on this workspace. GridMigrationFinished = EventsAPIType("grid_migration_finished") // GridMigrationStarted An enterprise grid migration has started on this workspace. @@ -503,39 +605,58 @@ const ( EmojiChanged = EventsAPIType("emoji_changed") // WorkflowStepExecute Happens, if a workflow step of your app is invoked WorkflowStepExecute = EventsAPIType("workflow_step_execute") + // MessageMetadataPosted A message with metadata was posted + MessageMetadataPosted = EventsAPIType("message_metadata_posted") + // MessageMetadataPosted A message with metadata was updated + MessageMetadataUpdated = EventsAPIType("message_metadata_updated") + // MessageMetadataPosted A message with metadata was deleted + MessageMetadataDeleted = EventsAPIType("message_metadata_deleted") + // TeamAccessGranted is sent if access to teams was granted for your org-wide app. + TeamAccessGranted = EventsAPIType("team_access_granted") + // TeamAccessrevoked is sent if access to teams was revoked for your org-wide app. + TeamAccessrevoked = EventsAPIType("team_access_revoked") ) // EventsAPIInnerEventMapping maps INNER Event API events to their corresponding struct // implementations. The structs should be instances of the unmarshalling // target for the matching event type. var EventsAPIInnerEventMapping = map[EventsAPIType]interface{}{ - AppMention: AppMentionEvent{}, - AppHomeOpened: AppHomeOpenedEvent{}, - AppUninstalled: AppUninstalledEvent{}, - ChannelCreated: ChannelCreatedEvent{}, - ChannelDeleted: ChannelDeletedEvent{}, - ChannelArchive: ChannelArchiveEvent{}, - ChannelUnarchive: ChannelUnarchiveEvent{}, - ChannelLeft: ChannelLeftEvent{}, - ChannelRename: ChannelRenameEvent{}, - ChannelIDChanged: ChannelIDChangedEvent{}, - GroupDeleted: GroupDeletedEvent{}, - GroupArchive: GroupArchiveEvent{}, - GroupUnarchive: GroupUnarchiveEvent{}, - GroupLeft: GroupLeftEvent{}, - GroupRename: GroupRenameEvent{}, - GridMigrationFinished: GridMigrationFinishedEvent{}, - GridMigrationStarted: GridMigrationStartedEvent{}, - LinkShared: LinkSharedEvent{}, - Message: MessageEvent{}, - MemberJoinedChannel: MemberJoinedChannelEvent{}, - MemberLeftChannel: MemberLeftChannelEvent{}, - PinAdded: PinAddedEvent{}, - PinRemoved: PinRemovedEvent{}, - ReactionAdded: ReactionAddedEvent{}, - ReactionRemoved: ReactionRemovedEvent{}, - TeamJoin: TeamJoinEvent{}, - TokensRevoked: TokensRevokedEvent{}, - EmojiChanged: EmojiChangedEvent{}, - WorkflowStepExecute: WorkflowStepExecuteEvent{}, + AppMention: AppMentionEvent{}, + AppHomeOpened: AppHomeOpenedEvent{}, + AppUninstalled: AppUninstalledEvent{}, + ChannelCreated: ChannelCreatedEvent{}, + ChannelDeleted: ChannelDeletedEvent{}, + ChannelArchive: ChannelArchiveEvent{}, + ChannelUnarchive: ChannelUnarchiveEvent{}, + ChannelLeft: ChannelLeftEvent{}, + ChannelRename: ChannelRenameEvent{}, + ChannelIDChanged: ChannelIDChangedEvent{}, + FileChange: FileChangeEvent{}, + FileDeleted: FileDeletedEvent{}, + FileShared: FileSharedEvent{}, + FileUnshared: FileUnsharedEvent{}, + GroupDeleted: GroupDeletedEvent{}, + GroupArchive: GroupArchiveEvent{}, + GroupUnarchive: GroupUnarchiveEvent{}, + GroupLeft: GroupLeftEvent{}, + GroupRename: GroupRenameEvent{}, + GridMigrationFinished: GridMigrationFinishedEvent{}, + GridMigrationStarted: GridMigrationStartedEvent{}, + LinkShared: LinkSharedEvent{}, + Message: MessageEvent{}, + MemberJoinedChannel: MemberJoinedChannelEvent{}, + MemberLeftChannel: MemberLeftChannelEvent{}, + PinAdded: PinAddedEvent{}, + PinRemoved: PinRemovedEvent{}, + ReactionAdded: ReactionAddedEvent{}, + ReactionRemoved: ReactionRemovedEvent{}, + TeamJoin: TeamJoinEvent{}, + TokensRevoked: TokensRevokedEvent{}, + EmojiChanged: EmojiChangedEvent{}, + WorkflowStepExecute: WorkflowStepExecuteEvent{}, + MessageMetadataPosted: MessageMetadataPostedEvent{}, + MessageMetadataUpdated: MessageMetadataUpdatedEvent{}, + MessageMetadataDeleted: MessageMetadataDeletedEvent{}, + TeamAccessGranted: TeamAccessGrantedEvent{}, + TeamAccessrevoked: TeamAccessRevokedEvent{}, } diff --git a/vendor/github.com/slack-go/slack/slackevents/outer_events.go b/vendor/github.com/slack-go/slack/slackevents/outer_events.go index 8d682cc..bcc85f3 100644 --- a/vendor/github.com/slack-go/slack/slackevents/outer_events.go +++ b/vendor/github.com/slack-go/slack/slackevents/outer_events.go @@ -35,6 +35,7 @@ type EventsAPICallbackEvent struct { Token string `json:"token"` TeamID string `json:"team_id"` APIAppID string `json:"api_app_id"` + EnterpriseID string `json:"enterprise_id"` InnerEvent *json.RawMessage `json:"event"` AuthedUsers []string `json:"authed_users"` AuthedTeams []string `json:"authed_teams"` @@ -61,7 +62,7 @@ const ( AppRateLimited = "app_rate_limited" ) -// EventsAPIEventMap maps OUTTER Event API events to their corresponding struct +// EventsAPIEventMap maps OUTER Event API events to their corresponding struct // implementations. The structs should be instances of the unmarshalling // target for the matching event type. var EventsAPIEventMap = map[string]interface{}{ diff --git a/vendor/github.com/slack-go/slack/slackevents/parsers.go b/vendor/github.com/slack-go/slack/slackevents/parsers.go index 114dba8..96ba568 100644 --- a/vendor/github.com/slack-go/slack/slackevents/parsers.go +++ b/vendor/github.com/slack-go/slack/slackevents/parsers.go @@ -12,7 +12,7 @@ import ( // eventsMap checks both slack.EventsMapping and // and slackevents.EventsAPIInnerEventMapping. If the event -// exists, returns the the unmarshalled struct instance of +// exists, returns the unmarshalled struct instance of // target for the matching event type. // TODO: Consider moving all events into its own package? func eventsMap(t string) (interface{}, bool) { @@ -102,7 +102,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, "unmarshalling_error", e.APIAppID, - "", + e.EnterpriseID, &slack.UnmarshallingErrorEvent{ErrorObj: err}, EventsAPIInnerEvent{}, }, err @@ -114,7 +114,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, iE.Type, e.APIAppID, - "", + e.EnterpriseID, nil, EventsAPIInnerEvent{}, }, fmt.Errorf("Inner Event does not exist! %s", iE.Type) @@ -128,7 +128,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, "unmarshalling_error", e.APIAppID, - "", + e.EnterpriseID, &slack.UnmarshallingErrorEvent{ErrorObj: err}, EventsAPIInnerEvent{}, }, err @@ -138,7 +138,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, e.Type, e.APIAppID, - "", + e.EnterpriseID, e, EventsAPIInnerEvent{iE.Type, recvEvent}, }, nil @@ -176,7 +176,7 @@ func (c TokenComparator) Verify(t string) bool { return subtle.ConstantTimeCompare([]byte(c.VerificationToken), []byte(t)) == 1 } -// ParseEvent parses the outter and inner events (if applicable) of an events +// ParseEvent parses the outer and inner events (if applicable) of an events // api event returning a EventsAPIEvent type. If the event is a url_verification event, // the inner event is empty. func ParseEvent(rawEvent json.RawMessage, opts ...Option) (EventsAPIEvent, error) { diff --git a/vendor/github.com/slack-go/slack/socketmode/request.go b/vendor/github.com/slack-go/slack/socketmode/request.go index 078003a..254c8c4 100644 --- a/vendor/github.com/slack-go/slack/socketmode/request.go +++ b/vendor/github.com/slack-go/slack/socketmode/request.go @@ -6,7 +6,7 @@ import "encoding/json" // // We call this a "request" rather than e.g. a WebSocket message or an Socket Mode "event" following python-slack-sdk: // -// https://github.com/slackapi/python-slack-sdk/blob/3f1c4c6e27bf7ee8af57699b2543e6eb7848bcf9/slack_sdk/socket_mode/request.py#L6 +// https://github.com/slackapi/python-slack-sdk/blob/3f1c4c6e27bf7ee8af57699b2543e6eb7848bcf9/slack_sdk/socket_mode/request.py#L6 // // We know that node-slack-sdk calls it an "event", that makes it hard for us to distinguish our client's own event // that wraps both internal events and Socket Mode "events", vs node-slack-sdk's is for the latter only. diff --git a/vendor/github.com/slack-go/slack/socketmode/socket_mode_managed_conn.go b/vendor/github.com/slack-go/slack/socketmode/socket_mode_managed_conn.go index 4289997..b259fd6 100644 --- a/vendor/github.com/slack-go/slack/socketmode/socket_mode_managed_conn.go +++ b/vendor/github.com/slack-go/slack/socketmode/socket_mode_managed_conn.go @@ -123,9 +123,9 @@ func (smc *Client) run(ctx context.Context, connectionCount int) error { } }() - wg.Add(1) + // We don't wait on runMessageReceiver because it doesn't block on a select with the context, + // so we'd have to wait for the ReadJSON to time out, which can take a while. go func() { - defer wg.Done() defer cancel() // The receiver reads WebSocket messages, and enqueues parsed Socket Mode requests to be handled by @@ -162,7 +162,8 @@ func (smc *Client) run(ctx context.Context, connectionCount int) error { return firstErr } - // wg.Wait() finishes only after any of the above go routines finishes. + // wg.Wait() finishes only after any of the above go routines finishes and cancels the + // context, allowing the other threads to shut down gracefully. // Also, we can expect firstErr to be not nil, as goroutines can finish only on error. smc.Debugf("Reconnecting due to %v", firstErr) diff --git a/vendor/github.com/slack-go/slack/stars.go b/vendor/github.com/slack-go/slack/stars.go index 5296760..6e0ebbe 100644 --- a/vendor/github.com/slack-go/slack/stars.go +++ b/vendor/github.com/slack-go/slack/stars.go @@ -130,17 +130,18 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) // GetStarred returns a list of StarredItem items. // // The user then has to iterate over them and figure out what they should -// 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": -// ... +// 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": +// ... +// } // -// } // This function still exists to maintain backwards compatibility. -// I exposed it as returning []StarredItem, so it shall stay as StarredItem +// I exposed it as returning []StarredItem, so it shall stay as StarredItem. func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) { return api.GetStarredContext(context.Background(), params) } diff --git a/vendor/github.com/slack-go/slack/team.go b/vendor/github.com/slack-go/slack/team.go index 029e2b5..d21a1b6 100644 --- a/vendor/github.com/slack-go/slack/team.go +++ b/vendor/github.com/slack-go/slack/team.go @@ -24,6 +24,26 @@ type TeamInfo struct { Icon map[string]interface{} `json:"icon"` } +type TeamProfileResponse struct { + Profile TeamProfile `json:"profile"` + SlackResponse +} + +type TeamProfile struct { + Fields []TeamProfileField `json:"fields"` +} + +type TeamProfileField struct { + ID string `json:"id"` + Ordering int `json:"ordering"` + Label string `json:"label"` + Hint string `json:"hint"` + Type string `json:"type"` + PossibleValues []string `json:"possible_values"` + IsHidden bool `json:"is_hidden"` + Options map[string]bool `json:"options"` +} + type LoginResponse struct { Logins []Login `json:"logins"` Paging `json:"paging"` @@ -95,11 +115,41 @@ func (api *Client) accessLogsRequest(ctx context.Context, path string, values ur return response, response.Err() } +func (api *Client) teamProfileRequest(ctx context.Context, client httpClient, path string, values url.Values) (*TeamProfileResponse, error) { + response := &TeamProfileResponse{} + err := api.postMethod(ctx, path, values, response) + if err != nil { + return nil, err + } + return response, response.Err() +} + // GetTeamInfo gets the Team Information of the user func (api *Client) GetTeamInfo() (*TeamInfo, error) { return api.GetTeamInfoContext(context.Background()) } +// GetOtherTeamInfoContext gets Team information for any team with a custom context +func (api *Client) GetOtherTeamInfoContext(ctx context.Context, team string) (*TeamInfo, error) { + if team == "" { + return api.GetTeamInfoContext(ctx) + } + values := url.Values{ + "token": {api.token}, + } + values.Add("team", team) + response, err := api.teamRequest(ctx, "team.info", values) + if err != nil { + return nil, err + } + return &response.Team, nil +} + +// GetOtherTeamInfo gets Team information for any team +func (api *Client) GetOtherTeamInfo(team string) (*TeamInfo, error) { + return api.GetOtherTeamInfoContext(context.Background(), team) +} + // GetTeamInfoContext gets the Team Information of the user with a custom context func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) { values := url.Values{ @@ -113,6 +163,25 @@ func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) { return &response.Team, nil } +// GetTeamProfile gets the Team Profile settings of the user +func (api *Client) GetTeamProfile() (*TeamProfile, error) { + return api.GetTeamProfileContext(context.Background()) +} + +// GetTeamProfileContext gets the Team Profile settings of the user with a custom context +func (api *Client) GetTeamProfileContext(ctx context.Context) (*TeamProfile, error) { + values := url.Values{ + "token": {api.token}, + } + + response, err := api.teamProfileRequest(ctx, api.httpclient, "team.profile.get", values) + if err != nil { + return nil, err + } + return &response.Profile, nil + +} + // GetAccessLogs retrieves a page of logins according to the parameters given func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) { return api.GetAccessLogsContext(context.Background(), params) diff --git a/vendor/github.com/slack-go/slack/usergroups.go b/vendor/github.com/slack-go/slack/usergroups.go index 9417f81..c5d7a17 100644 --- a/vendor/github.com/slack-go/slack/usergroups.go +++ b/vendor/github.com/slack-go/slack/usergroups.go @@ -183,32 +183,77 @@ func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserG return response.UserGroups, nil } +// UpdateUserGroupsOption options for the UpdateUserGroup method call. +type UpdateUserGroupsOption func(*UpdateUserGroupsParams) + +// UpdateUserGroupsOptionName change the name of the User Group (default: empty, so it's no-op) +func UpdateUserGroupsOptionName(name string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Name = name + } +} + +// UpdateUserGroupsOptionHandle change the handle of the User Group (default: empty, so it's no-op) +func UpdateUserGroupsOptionHandle(handle string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Handle = handle + } +} + +// UpdateUserGroupsOptionDescription change the description of the User Group. (default: nil, so it's no-op) +func UpdateUserGroupsOptionDescription(description *string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Description = description + } +} + +// UpdateUserGroupsOptionChannels change the default channels of the User Group. (default: unspecified, so it's no-op) +func UpdateUserGroupsOptionChannels(channels []string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Channels = &channels + } +} + +// UpdateUserGroupsParams contains arguments for UpdateUserGroup method call +type UpdateUserGroupsParams struct { + Name string + Handle string + Description *string + Channels *[]string +} + // UpdateUserGroup will update an existing user group -func (api *Client) UpdateUserGroup(userGroup UserGroup) (UserGroup, error) { - return api.UpdateUserGroupContext(context.Background(), userGroup) +func (api *Client) UpdateUserGroup(userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) { + return api.UpdateUserGroupContext(context.Background(), userGroupID, options...) } // UpdateUserGroupContext will update an existing user group with a custom context -func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) { +func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) { + params := UpdateUserGroupsParams{} + + for _, opt := range options { + opt(¶ms) + } + values := url.Values{ "token": {api.token}, - "usergroup": {userGroup.ID}, + "usergroup": {userGroupID}, } - if userGroup.Name != "" { - values["name"] = []string{userGroup.Name} + if params.Name != "" { + values["name"] = []string{params.Name} } - if userGroup.Handle != "" { - values["handle"] = []string{userGroup.Handle} + if params.Handle != "" { + values["handle"] = []string{params.Handle} } - if userGroup.Description != "" { - values["description"] = []string{userGroup.Description} + if params.Description != nil { + values["description"] = []string{*params.Description} } - if len(userGroup.Prefs.Channels) > 0 { - values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")} + if params.Channels != nil { + values["channels"] = []string{strings.Join(*params.Channels, ",")} } response, err := api.userGroupRequest(ctx, "usergroups.update", values) diff --git a/vendor/github.com/slack-go/slack/users.go b/vendor/github.com/slack-go/slack/users.go index 8731156..55f4211 100644 --- a/vendor/github.com/slack-go/slack/users.go +++ b/vendor/github.com/slack-go/slack/users.go @@ -17,30 +17,38 @@ const ( // UserProfile contains all the information details of a given user type UserProfile struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - RealName string `json:"real_name"` - RealNameNormalized string `json:"real_name_normalized"` - DisplayName string `json:"display_name"` - DisplayNameNormalized string `json:"display_name_normalized"` - Email string `json:"email"` - Skype string `json:"skype"` - Phone string `json:"phone"` - Image24 string `json:"image_24"` - Image32 string `json:"image_32"` - Image48 string `json:"image_48"` - Image72 string `json:"image_72"` - Image192 string `json:"image_192"` - Image512 string `json:"image_512"` - ImageOriginal string `json:"image_original"` - Title string `json:"title"` - BotID string `json:"bot_id,omitempty"` - ApiAppID string `json:"api_app_id,omitempty"` - StatusText string `json:"status_text,omitempty"` - StatusEmoji string `json:"status_emoji,omitempty"` - StatusExpiration int `json:"status_expiration"` - Team string `json:"team"` - Fields UserProfileCustomFields `json:"fields"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + RealName string `json:"real_name"` + RealNameNormalized string `json:"real_name_normalized"` + DisplayName string `json:"display_name"` + DisplayNameNormalized string `json:"display_name_normalized"` + Email string `json:"email"` + Skype string `json:"skype"` + Phone string `json:"phone"` + Image24 string `json:"image_24"` + Image32 string `json:"image_32"` + Image48 string `json:"image_48"` + Image72 string `json:"image_72"` + Image192 string `json:"image_192"` + Image512 string `json:"image_512"` + ImageOriginal string `json:"image_original"` + Title string `json:"title"` + BotID string `json:"bot_id,omitempty"` + ApiAppID string `json:"api_app_id,omitempty"` + StatusText string `json:"status_text,omitempty"` + StatusEmoji string `json:"status_emoji,omitempty"` + StatusEmojiDisplayInfo []UserProfileStatusEmojiDisplayInfo `json:"status_emoji_display_info,omitempty"` + StatusExpiration int `json:"status_expiration"` + Team string `json:"team"` + Fields UserProfileCustomFields `json:"fields"` +} + +type UserProfileStatusEmojiDisplayInfo struct { + EmojiName string `json:"emoji_name"` + DisplayAlias string `json:"display_alias,omitempty"` + DisplayURL string `json:"display_url,omitempty"` + Unicode string `json:"unicode,omitempty"` } // UserProfileCustomFields represents user profile's custom fields. @@ -292,6 +300,13 @@ func GetUsersOptionPresence(n bool) GetUsersOption { } } +// GetUsersOptionTeamID include team Id +func GetUsersOptionTeamID(teamId string) GetUsersOption { + return func(p *UserPagination) { + p.teamId = teamId + } +} + func newUserPagination(c *Client, options ...GetUsersOption) (up UserPagination) { up = UserPagination{ c: c, @@ -310,6 +325,7 @@ type UserPagination struct { Users []User limit int presence bool + teamId string previousResp *ResponseMetadata c *Client } @@ -344,6 +360,7 @@ func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error) "presence": {strconv.FormatBool(t.presence)}, "token": {t.c.token}, "cursor": {t.previousResp.Cursor}, + "team_id": {t.teamId}, "include_locale": {strconv.FormatBool(true)}, } @@ -364,13 +381,13 @@ func (api *Client) GetUsersPaginated(options ...GetUsersOption) UserPagination { } // GetUsers returns the list of users (with their detailed information) -func (api *Client) GetUsers() ([]User, error) { - return api.GetUsersContext(context.Background()) +func (api *Client) GetUsers(options ...GetUsersOption) ([]User, error) { + return api.GetUsersContext(context.Background(), options...) } // GetUsersContext returns the list of users (with their detailed information) with a custom context -func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) { - p := api.GetUsersPaginated() +func (api *Client) GetUsersContext(ctx context.Context, options ...GetUsersOption) (results []User, err error) { + p := api.GetUsersPaginated(options...) for err == nil { p, err = p.Next(ctx) if err == nil { @@ -547,6 +564,55 @@ func (api *Client) SetUserRealNameContextWithUser(ctx context.Context, user, rea return response.Err() } +// SetUserCustomFields sets Custom Profile fields on the provided users account. Due to the non-repeating elements +// within the request, a map fields is required. The key in the map signifies the field that will be updated. +// +// Note: You may need to change the way the custom field is populated within the Profile section of the Admin Console from +// SCIM or User Entered to API. +// +// See GetTeamProfile for information to retrieve possible fields for your account. +func (api *Client) SetUserCustomFields(userID string, customFields map[string]UserProfileCustomField) error { + return api.SetUserCustomFieldsContext(context.Background(), userID, customFields) +} + +// SetUserCustomFieldsContext will set a users custom profile field with context. +// +// For more information see SetUserCustomFields +func (api *Client) SetUserCustomFieldsContext(ctx context.Context, userID string, customFields map[string]UserProfileCustomField) error { + + // Convert data to data type with custom marshall / unmarshall + // For more information, see UserProfileCustomFields definition. + updateFields := UserProfileCustomFields{} + updateFields.SetMap(customFields) + + // This anonymous struct is needed to set the fields level of the request data. The base struct for + // UserProfileCustomFields has an unexported variable named fields that does not contain a struct tag, + // which has resulted in this configuration. + profile, err := json.Marshal(&struct { + Fields UserProfileCustomFields `json:"fields"` + }{ + Fields: updateFields, + }) + + if err != nil { + return err + } + + values := url.Values{ + "token": {api.token}, + "user": {userID}, + "profile": {string(profile)}, + } + + response := &userResponseFull{} + if err := postForm(ctx, api.httpclient, APIURL+"users.profile.set", values, response, api); err != nil { + return err + } + + return response.Err() + +} + // SetUserCustomStatus will set a custom status and emoji for the currently // authenticated user. If statusEmoji is "" and statusText is not, the Slack API // will automatically set it to ":speech_balloon:". Otherwise, if both are "" diff --git a/vendor/github.com/slack-go/slack/webhooks.go b/vendor/github.com/slack-go/slack/webhooks.go index 2f8fb47..e323353 100644 --- a/vendor/github.com/slack-go/slack/webhooks.go +++ b/vendor/github.com/slack-go/slack/webhooks.go @@ -21,8 +21,8 @@ type WebhookMessage struct { Parse string `json:"parse,omitempty"` Blocks *Blocks `json:"blocks,omitempty"` ResponseType string `json:"response_type,omitempty"` - ReplaceOriginal bool `json:"replace_original,omitempty"` - DeleteOriginal bool `json:"delete_original,omitempty"` + ReplaceOriginal bool `json:"replace_original"` + DeleteOriginal bool `json:"delete_original"` ReplyBroadcast bool `json:"reply_broadcast,omitempty"` } 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 14dea21..f107b2a 100644 --- a/vendor/github.com/slack-go/slack/websocket_managed_conn.go +++ b/vendor/github.com/slack-go/slack/websocket_managed_conn.go @@ -16,6 +16,31 @@ import ( "github.com/slack-go/slack/internal/timex" ) +// UnmappedError represents error occurred when there is no mapping between given event name +// and corresponding Go struct. +type UnmappedError struct { + // EventType returns event type name. + EventType string + // RawEvent returns raw event body. + RawEvent json.RawMessage + + ctxMsg string +} + +// NewUnmappedError returns new UnmappedError instance. +func NewUnmappedError(ctxMsg, eventType string, raw json.RawMessage) *UnmappedError { + return &UnmappedError{ + ctxMsg: ctxMsg, + EventType: eventType, + RawEvent: raw, + } +} + +// Error returns human-readable error message. +func (u UnmappedError) Error() string { + return fmt.Sprintf("%s: Received unmapped event %q", u.ctxMsg, u.EventType) +} + // ManageConnection can be called on a Slack RTM instance returned by the // NewRTM method. It will connect to the slack RTM API and handle all incoming // and outgoing events. If a connection fails then it will attempt to reconnect @@ -474,7 +499,7 @@ func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) { v, exists := EventMapping[typeStr] if !exists { rtm.Debugf("RTM Error - received unmapped event %q: %s\n", typeStr, string(event)) - err := fmt.Errorf("RTM Error: Received unmapped event %q", typeStr) + err := NewUnmappedError("RTM Error", typeStr, event) rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}} return } diff --git a/vendor/github.com/slack-go/slack/websocket_reactions.go b/vendor/github.com/slack-go/slack/websocket_reactions.go index e497387..6098a6c 100644 --- a/vendor/github.com/slack-go/slack/websocket_reactions.go +++ b/vendor/github.com/slack-go/slack/websocket_reactions.go @@ -1,7 +1,7 @@ package slack -// reactionItem is a lighter-weight item than is returned by the reactions list. -type reactionItem struct { +// ReactionItem is a lighter-weight item than is returned by the reactions list. +type ReactionItem struct { Type string `json:"type"` Channel string `json:"channel,omitempty"` File string `json:"file,omitempty"` @@ -9,17 +9,17 @@ type reactionItem struct { Timestamp string `json:"ts,omitempty"` } -type reactionEvent struct { +type ReactionEvent struct { Type string `json:"type"` User string `json:"user"` ItemUser string `json:"item_user"` - Item reactionItem `json:"item"` + Item ReactionItem `json:"item"` Reaction string `json:"reaction"` EventTimestamp string `json:"event_ts"` } // ReactionAddedEvent represents the Reaction added event -type ReactionAddedEvent reactionEvent +type ReactionAddedEvent ReactionEvent // ReactionRemovedEvent represents the Reaction removed event -type ReactionRemovedEvent reactionEvent +type ReactionRemovedEvent ReactionEvent diff --git a/vendor/github.com/slack-go/slack/workflow_step_execute.go b/vendor/github.com/slack-go/slack/workflow_step_execute.go new file mode 100644 index 0000000..32db9ba --- /dev/null +++ b/vendor/github.com/slack-go/slack/workflow_step_execute.go @@ -0,0 +1,85 @@ +package slack + +import ( + "context" + "encoding/json" +) + +type ( + WorkflowStepCompletedRequest struct { + WorkflowStepExecuteID string `json:"workflow_step_execute_id"` + Outputs map[string]string `json:"outputs"` + } + + WorkflowStepFailedRequest struct { + WorkflowStepExecuteID string `json:"workflow_step_execute_id"` + Error struct { + Message string `json:"message"` + } `json:"error"` + } +) + +type WorkflowStepCompletedRequestOption func(opt *WorkflowStepCompletedRequest) error + +func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) WorkflowStepCompletedRequestOption { + return func(opt *WorkflowStepCompletedRequest) error { + if len(outputs) > 0 { + opt.Outputs = outputs + } + return nil + } +} + +// WorkflowStepCompleted indicates step is completed +func (api *Client) WorkflowStepCompleted(workflowStepExecuteID string, options ...WorkflowStepCompletedRequestOption) error { + // More information: https://api.slack.com/methods/workflows.stepCompleted + r := &WorkflowStepCompletedRequest{ + WorkflowStepExecuteID: workflowStepExecuteID, + } + for _, option := range options { + option(r) + } + + endpoint := api.endpoint + "workflows.stepCompleted" + jsonData, err := json.Marshal(r) + if err != nil { + return err + } + + response := &SlackResponse{} + if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + return err + } + + if !response.Ok { + return response.Err() + } + + return nil +} + +// WorkflowStepFailed indicates step is failed +func (api *Client) WorkflowStepFailed(workflowStepExecuteID string, errorMessage string) error { + // More information: https://api.slack.com/methods/workflows.stepFailed + r := WorkflowStepFailedRequest{ + WorkflowStepExecuteID: workflowStepExecuteID, + } + r.Error.Message = errorMessage + + endpoint := api.endpoint + "workflows.stepFailed" + jsonData, err := json.Marshal(r) + if err != nil { + return err + } + + response := &SlackResponse{} + if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + return err + } + + if !response.Ok { + return response.Err() + } + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b799a86..dde1449 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/shomali11/proper # github.com/shomali11/slacker v1.2.0 ## explicit github.com/shomali11/slacker -# github.com/slack-go/slack v0.11.0 +# github.com/slack-go/slack v0.12.1 ## explicit github.com/slack-go/slack github.com/slack-go/slack/internal/backoff