diff --git a/vendor/github.com/nlopes/slack/.travis.yml b/vendor/github.com/nlopes/slack/.travis.yml index ed99d9e..6a96823 100644 --- a/vendor/github.com/nlopes/slack/.travis.yml +++ b/vendor/github.com/nlopes/slack/.travis.yml @@ -28,6 +28,10 @@ matrix: script: go test -v ./... - go: "1.11.x" script: go test -v -mod=vendor ./... + - go: "1.12.x" + script: go test -v -mod=vendor ./... + - go: "1.13.x" + script: go test -v -mod=vendor ./... - go: "tip" script: go test -v -mod=vendor ./... diff --git a/vendor/github.com/nlopes/slack/attachments.go b/vendor/github.com/nlopes/slack/attachments.go index cf8b5c6..73cdebe 100644 --- a/vendor/github.com/nlopes/slack/attachments.go +++ b/vendor/github.com/nlopes/slack/attachments.go @@ -61,7 +61,7 @@ type ConfirmationField struct { // Attachment contains all the information for an attachment type Attachment struct { Color string `json:"color,omitempty"` - Fallback string `json:"fallback"` + Fallback string `json:"fallback,omitempty"` CallbackID string `json:"callback_id,omitempty"` ID int `json:"id,omitempty"` @@ -75,7 +75,7 @@ type Attachment struct { Title string `json:"title,omitempty"` TitleLink string `json:"title_link,omitempty"` Pretext string `json:"pretext,omitempty"` - Text string `json:"text"` + Text string `json:"text,omitempty"` ImageURL string `json:"image_url,omitempty"` ThumbURL string `json:"thumb_url,omitempty"` @@ -84,6 +84,8 @@ type Attachment struct { Actions []AttachmentAction `json:"actions,omitempty"` MarkdownIn []string `json:"mrkdwn_in,omitempty"` + Blocks []Block `json:"blocks,omitempty"` + Footer string `json:"footer,omitempty"` FooterIcon string `json:"footer_icon,omitempty"` diff --git a/vendor/github.com/nlopes/slack/block.go b/vendor/github.com/nlopes/slack/block.go index 1fc7fec..502b113 100644 --- a/vendor/github.com/nlopes/slack/block.go +++ b/vendor/github.com/nlopes/slack/block.go @@ -30,22 +30,23 @@ type Blocks struct { // BlockAction is the action callback sent when a block is interacted with type BlockAction struct { - ActionID string `json:"action_id"` - BlockID string `json:"block_id"` - Type actionType `json:"type"` - Text TextBlockObject `json:"text"` - Value string `json:"value"` - ActionTs string `json:"action_ts"` - SelectedOption OptionBlockObject `json:"selected_option"` - SelectedUser string `json:"selected_user"` - SelectedChannel string `json:"selected_channel"` - SelectedConversation string `json:"selected_conversation"` - SelectedDate string `json:"selected_date"` - InitialOption OptionBlockObject `json:"initial_option"` - InitialUser string `json:"initial_user"` - InitialChannel string `json:"initial_channel"` - InitialConversation string `json:"initial_conversation"` - InitialDate string `json:"initial_date"` + ActionID string `json:"action_id"` + BlockID string `json:"block_id"` + Type actionType `json:"type"` + Text TextBlockObject `json:"text"` + Value string `json:"value"` + ActionTs string `json:"action_ts"` + SelectedOption OptionBlockObject `json:"selected_option"` + SelectedOptions []OptionBlockObject `json:"selected_options"` + SelectedUser string `json:"selected_user"` + SelectedChannel string `json:"selected_channel"` + SelectedConversation string `json:"selected_conversation"` + SelectedDate string `json:"selected_date"` + InitialOption OptionBlockObject `json:"initial_option"` + InitialUser string `json:"initial_user"` + InitialChannel string `json:"initial_channel"` + InitialConversation string `json:"initial_conversation"` + InitialDate string `json:"initial_date"` } // actionType returns the type of the action diff --git a/vendor/github.com/nlopes/slack/block_conv.go b/vendor/github.com/nlopes/slack/block_conv.go index 619867e..bdc490e 100644 --- a/vendor/github.com/nlopes/slack/block_conv.go +++ b/vendor/github.com/nlopes/slack/block_conv.go @@ -60,6 +60,9 @@ func (b *Blocks) UnmarshalJSON(data []byte) error { block = &ImageBlock{} case "section": block = &SectionBlock{} + case "rich_text": + // for now ignore the (complex) content of rich_text blocks until we can fully support it + continue default: return errors.New("unsupported block type") } diff --git a/vendor/github.com/nlopes/slack/block_object.go b/vendor/github.com/nlopes/slack/block_object.go index 9e77e6c..824ec93 100644 --- a/vendor/github.com/nlopes/slack/block_object.go +++ b/vendor/github.com/nlopes/slack/block_object.go @@ -178,7 +178,7 @@ func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *Co type OptionBlockObject struct { Text *TextBlockObject `json:"text"` Value string `json:"value"` - URL string `json:"url"` + URL string `json:"url,omitempty"` } // NewOptionBlockObject returns an instance of a new Option Block Element diff --git a/vendor/github.com/nlopes/slack/bots.go b/vendor/github.com/nlopes/slack/bots.go index 5d5a2ad..da21ba0 100644 --- a/vendor/github.com/nlopes/slack/bots.go +++ b/vendor/github.com/nlopes/slack/bots.go @@ -7,10 +7,13 @@ import ( // Bot contains information about a bot type Bot struct { - ID string `json:"id"` - Name string `json:"name"` - Deleted bool `json:"deleted"` - Icons Icons `json:"icons"` + ID string `json:"id"` + Name string `json:"name"` + Deleted bool `json:"deleted"` + UserID string `json:"user_id"` + AppID string `json:"app_id"` + Updated JSONTime `json:"updated"` + Icons Icons `json:"icons"` } type botResponseFull struct { diff --git a/vendor/github.com/nlopes/slack/chat.go b/vendor/github.com/nlopes/slack/chat.go index a480e5a..c074484 100644 --- a/vendor/github.com/nlopes/slack/chat.go +++ b/vendor/github.com/nlopes/slack/chat.go @@ -228,6 +228,7 @@ type sendConfig struct { endpoint string values url.Values attachments []Attachment + blocks Blocks responseType string } @@ -242,6 +243,7 @@ func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ endpoint: t.endpoint, values: t.values, attachments: t.attachments, + blocks: t.blocks, responseType: t.responseType, }.BuildRequest() default: @@ -265,6 +267,7 @@ type responseURLSender struct { endpoint string values url.Values attachments []Attachment + blocks Blocks responseType string } @@ -273,6 +276,7 @@ func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull Text: t.values.Get("text"), Timestamp: t.values.Get("ts"), Attachments: t.attachments, + Blocks: t.blocks, ResponseType: t.responseType, }) return req, func(resp *chatResponseFull) responseParser { @@ -420,6 +424,8 @@ func MsgOptionBlocks(blocks ...Block) MsgOption { return nil } + config.blocks.BlockSet = append(config.blocks.BlockSet, blocks...) + blocks, err := json.Marshal(blocks) if err == nil { config.values.Set("blocks", string(blocks)) diff --git a/vendor/github.com/nlopes/slack/errors.go b/vendor/github.com/nlopes/slack/errors.go index 09113ff..b31e2ca 100644 --- a/vendor/github.com/nlopes/slack/errors.go +++ b/vendor/github.com/nlopes/slack/errors.go @@ -6,6 +6,8 @@ import "github.com/nlopes/slack/internal/errorsx" const ( ErrAlreadyDisconnected = errorsx.String("Invalid call to Disconnect - Slack API is already disconnected") ErrRTMDisconnected = errorsx.String("disconnect received while trying to connect") + ErrRTMGoodbye = errorsx.String("goodbye detected") + ErrRTMDeadman = errorsx.String("deadman switch triggered") ErrParametersMissing = errorsx.String("received empty parameters") ErrInvalidConfiguration = errorsx.String("invalid configuration") ErrMissingHeaders = errorsx.String("missing headers") diff --git a/vendor/github.com/nlopes/slack/interactions.go b/vendor/github.com/nlopes/slack/interactions.go index 5433463..de1ed37 100644 --- a/vendor/github.com/nlopes/slack/interactions.go +++ b/vendor/github.com/nlopes/slack/interactions.go @@ -1,6 +1,7 @@ package slack import ( + "bytes" "encoding/json" ) @@ -53,6 +54,48 @@ type ActionCallbacks struct { BlockActions []*BlockAction } +// MarshalJSON implements the Marshaller interface in order to combine both +// action callback types back into a single array, like how the api responds. +// This makes Marshaling and Unmarshaling an InteractionCallback symmetrical +func (a ActionCallbacks) MarshalJSON() ([]byte, error) { + count := 0 + length := len(a.AttachmentActions) + len(a.BlockActions) + buffer := bytes.NewBufferString("[") + + f := func(obj interface{}) error { + js, err := json.Marshal(obj) + if err != nil { + return err + } + _, err = buffer.Write(js) + if err != nil { + return err + } + + count++ + if count < length { + _, err = buffer.WriteString(",") + return err + } + return nil + } + + for _, act := range a.AttachmentActions { + err := f(act) + if err != nil { + return nil, err + } + } + for _, blk := range a.BlockActions { + err := f(blk) + if err != nil { + return nil, err + } + } + buffer.WriteString("]") + return buffer.Bytes(), nil +} + // UnmarshalJSON implements the Marshaller interface in order to delegate // marshalling and allow for proper type assertion when decoding the response func (a *ActionCallbacks) UnmarshalJSON(data []byte) error { diff --git a/vendor/github.com/nlopes/slack/messages.go b/vendor/github.com/nlopes/slack/messages.go index 37a2633..f67d99a 100644 --- a/vendor/github.com/nlopes/slack/messages.go +++ b/vendor/github.com/nlopes/slack/messages.go @@ -101,7 +101,7 @@ type Msg struct { const ( // ResponseTypeInChannel in channel response for slash commands. ResponseTypeInChannel = "in_channel" - // ResponseTypeEphemeral ephemeral respone for slash commands. + // ResponseTypeEphemeral ephemeral response for slash commands. ResponseTypeEphemeral = "ephemeral" ) diff --git a/vendor/github.com/nlopes/slack/rtm.go b/vendor/github.com/nlopes/slack/rtm.go index 09cb51c..ef6ba34 100644 --- a/vendor/github.com/nlopes/slack/rtm.go +++ b/vendor/github.com/nlopes/slack/rtm.go @@ -2,7 +2,6 @@ package slack import ( "context" - "encoding/json" "net/url" "sync" "time" @@ -120,7 +119,6 @@ func (api *Client) NewRTM(options ...RTMOption) *RTM { disconnected: make(chan struct{}), disconnectedm: &sync.Once{}, forcePing: make(chan bool), - rawEvents: make(chan json.RawMessage), idGen: NewSafeID(1), mu: &sync.Mutex{}, } diff --git a/vendor/github.com/nlopes/slack/search.go b/vendor/github.com/nlopes/slack/search.go index 67e3b1d..de6b40a 100644 --- a/vendor/github.com/nlopes/slack/search.go +++ b/vendor/github.com/nlopes/slack/search.go @@ -23,8 +23,14 @@ type SearchParameters struct { } type CtxChannel struct { - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id"` + Name string `json:"name"` + IsExtShared bool `json:"is_ext_shared"` + IsMPIM bool `json:"is_mpim"` + ISOrgShared bool `json:"is_org_shared"` + IsPendingExtShared bool `json:"is_pending_ext_shared"` + IsPrivate bool `json:"is_private"` + IsShared bool `json:"is_shared"` } type CtxMessage struct { diff --git a/vendor/github.com/nlopes/slack/stars.go b/vendor/github.com/nlopes/slack/stars.go index e84d044..5296760 100644 --- a/vendor/github.com/nlopes/slack/stars.go +++ b/vendor/github.com/nlopes/slack/stars.go @@ -4,6 +4,7 @@ import ( "context" "net/url" "strconv" + "time" ) const ( @@ -158,3 +159,105 @@ func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters } return starredItems, paging, nil } + +type listResponsePaginated struct { + Items []Item `json:"items"` + SlackResponse + Metadata ResponseMetadata `json:"response_metadata"` +} + +// StarredItemPagination allows for paginating over the starred items +type StarredItemPagination struct { + Items []Item + limit int + previousResp *ResponseMetadata + c *Client +} + +// ListStarsOption options for the GetUsers method call. +type ListStarsOption func(*StarredItemPagination) + +// ListAllStars returns the complete list of starred items +func (api *Client) ListAllStars() ([]Item, error) { + return api.ListAllStarsContext(context.Background()) +} + +// ListAllStarsContext returns the list of users (with their detailed information) with a custom context +func (api *Client) ListAllStarsContext(ctx context.Context) (results []Item, err error) { + p := api.ListStarsPaginated() + for err == nil { + p, err = p.next(ctx) + if err == nil { + results = append(results, p.Items...) + } else if rateLimitedError, ok := err.(*RateLimitedError); ok { + select { + case <-ctx.Done(): + err = ctx.Err() + case <-time.After(rateLimitedError.RetryAfter): + err = nil + } + } + } + + return results, p.failure(err) +} + +// ListStarsPaginated fetches users in a paginated fashion, see ListStarsPaginationContext for usage. +func (api *Client) ListStarsPaginated(options ...ListStarsOption) StarredItemPagination { + return newStarPagination(api, options...) +} + +func newStarPagination(c *Client, options ...ListStarsOption) (sip StarredItemPagination) { + sip = StarredItemPagination{ + c: c, + limit: 200, // per slack api documentation. + } + + for _, opt := range options { + opt(&sip) + } + + return sip +} + +// done checks if the pagination has completed +func (StarredItemPagination) done(err error) bool { + return err == errPaginationComplete +} + +// done checks if pagination failed. +func (t StarredItemPagination) failure(err error) error { + if t.done(err) { + return nil + } + + return err +} + +// next gets the next list of starred items based on the cursor value +func (t StarredItemPagination) next(ctx context.Context) (_ StarredItemPagination, err error) { + var ( + resp *listResponsePaginated + ) + + if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") { + return t, errPaginationComplete + } + + t.previousResp = t.previousResp.initialize() + + values := url.Values{ + "limit": {strconv.Itoa(t.limit)}, + "token": {t.c.token}, + "cursor": {t.previousResp.Cursor}, + } + + if err = t.c.postMethod(ctx, "stars.list", values, &resp); err != nil { + return t, err + } + + t.previousResp = &resp.Metadata + t.Items = resp.Items + + return t, nil +} diff --git a/vendor/github.com/nlopes/slack/websocket.go b/vendor/github.com/nlopes/slack/websocket.go index 122807b..d6895f2 100644 --- a/vendor/github.com/nlopes/slack/websocket.go +++ b/vendor/github.com/nlopes/slack/websocket.go @@ -1,7 +1,6 @@ package slack import ( - "encoding/json" "net/url" "sync" "time" @@ -35,7 +34,6 @@ type RTM struct { disconnected chan struct{} disconnectedm *sync.Once forcePing chan bool - rawEvents chan json.RawMessage // UserDetails upon connection info *Info diff --git a/vendor/github.com/nlopes/slack/websocket_desktop_notification.go b/vendor/github.com/nlopes/slack/websocket_desktop_notification.go new file mode 100644 index 0000000..7c61abf --- /dev/null +++ b/vendor/github.com/nlopes/slack/websocket_desktop_notification.go @@ -0,0 +1,19 @@ +package slack + +// DesktopNotificationEvent represents the update event for Desktop Notification. +type DesktopNotificationEvent struct { + Type string `json:"type"` + Title string `json:"title"` + Subtitle string `json:"subtitle"` + Message string `json:"msg"` + Timestamp string `json:"ts"` + Content string `json:"content"` + Channel string `json:"channel"` + LaunchURI string `json:"launchUri"` + AvatarImage string `json:"avatarImage"` + SsbFilename string `json:"ssbFilename"` + ImageURI string `json:"imageUri"` + IsShared bool `json:"is_shared"` + IsChannelInvite bool `json:"is_channel_invite"` + EventTimestamp string `json:"event_ts"` +} diff --git a/vendor/github.com/nlopes/slack/websocket_managed_conn.go b/vendor/github.com/nlopes/slack/websocket_managed_conn.go index 8b3b383..dbbf682 100644 --- a/vendor/github.com/nlopes/slack/websocket_managed_conn.go +++ b/vendor/github.com/nlopes/slack/websocket_managed_conn.go @@ -58,15 +58,19 @@ func (rtm *RTM) ManageConnection() { rtm.Debugf("RTM connection succeeded on try %d", connectionCount) + rawEvents := make(chan json.RawMessage) // we're now connected so we can set up listeners - go rtm.handleIncomingEvents() - + go rtm.handleIncomingEvents(rawEvents) // this should be a blocking call until the connection has ended - rtm.handleEvents() + rtm.handleEvents(rawEvents) select { case <-rtm.disconnected: // after handle events returns we need to check if we're disconnected + // when this happens we need to cleanup the newly created connection. + if err = conn.Close(); err != nil { + rtm.Debugln("failed to close conn on disconnected RTM", err) + } return default: // otherwise continue and run the loop again to reconnect @@ -208,7 +212,7 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (info *Info, _ *websocket.Conn // This should not be called directly! Instead a boolean value (true for // intentional, false otherwise) should be sent to the killChannel on the RTM. func (rtm *RTM) killConnection(intentional bool, cause error) (err error) { - rtm.Debugln("killing connection") + rtm.Debugln("killing connection", cause) if rtm.conn != nil { err = rtm.conn.Close() @@ -228,7 +232,7 @@ func (rtm *RTM) killConnection(intentional bool, cause error) (err error) { // interval. This also sends outgoing messages that are received from the RTM's // outgoingMessages channel. This also handles incoming raw events from the RTM // rawEvents channel. -func (rtm *RTM) handleEvents() { +func (rtm *RTM) handleEvents(events chan json.RawMessage) { ticker := time.NewTicker(rtm.pingInterval) defer ticker.Stop() for { @@ -239,7 +243,7 @@ func (rtm *RTM) handleEvents() { return // detect when the connection is dead. case <-rtm.pingDeadman.C: - _ = rtm.killConnection(false, errorsx.String("deadman switch triggered")) + _ = rtm.killConnection(false, ErrRTMDeadman) return // send pings on ticker interval case <-ticker.C: @@ -255,12 +259,17 @@ func (rtm *RTM) handleEvents() { // listen for messages that need to be sent case msg := <-rtm.outgoingMessages: rtm.sendOutgoingMessage(msg) - // listen for incoming messages that need to be parsed - case rawEvent := <-rtm.rawEvents: + // listen for incoming messages that need to be parsed + case rawEvent := <-events: switch rtm.handleRawEvent(rawEvent) { case rtmEventTypeGoodbye: - _ = rtm.killConnection(false, errorsx.String("goodbye detected")) - return + // kill the connection, but DO NOT RETURN, a follow up kill signal will + // be sent that still needs to be processed. this duplication is because + // the event reader restarts once it emits the goodbye event. + // unlike the other cases in this function a final read will be triggered + // against the connection which will emit a kill signal. if we return early + // this kill signal will be processed by the next connection. + _ = rtm.killConnection(false, ErrRTMGoodbye) default: } } @@ -268,13 +277,17 @@ func (rtm *RTM) handleEvents() { } // handleIncomingEvents monitors the RTM's opened websocket for any incoming -// events. It pushes the raw events onto the RTM channel rawEvents. +// events. It pushes the raw events into the channel. // -// This will stop executing once the RTM's keepRunning channel has been closed -// or has anything sent to it. -func (rtm *RTM) handleIncomingEvents() { +// This will stop executing once the RTM's when a fatal error is detected, or +// a disconnect occurs. +func (rtm *RTM) handleIncomingEvents(events chan json.RawMessage) { for { - if err := rtm.receiveIncomingEvent(); err != nil { + if err := rtm.receiveIncomingEvent(events); err != nil { + select { + case rtm.killChannel <- false: + case <-rtm.disconnected: + } return } } @@ -336,9 +349,15 @@ func (rtm *RTM) ping() error { // receiveIncomingEvent attempts to receive an event from the RTM's websocket. // This will block until a frame is available from the websocket. // If the read from the websocket results in a fatal error, this function will return non-nil. -func (rtm *RTM) receiveIncomingEvent() error { +func (rtm *RTM) receiveIncomingEvent(events chan json.RawMessage) error { event := json.RawMessage{} err := rtm.conn.ReadJSON(&event) + + // check if the connection was closed. + if websocket.IsUnexpectedCloseError(err) { + return err + } + switch { case err == io.ErrUnexpectedEOF: // EOF's don't seem to signify a failed connection so instead we ignore @@ -357,22 +376,18 @@ func (rtm *RTM) receiveIncomingEvent() error { ErrorObj: err, }} - select { - case rtm.killChannel <- false: - case <-rtm.disconnected: - } - return err case len(event) == 0: rtm.Debugln("Received empty event") default: rtm.Debugln("Incoming Event:", string(event)) select { - case rtm.rawEvents <- event: + case events <- event: case <-rtm.disconnected: rtm.Debugln("disonnected while attempting to send raw event") } } + return nil } @@ -396,8 +411,6 @@ func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) string { rtm.handlePong(rawEvent) case rtmEventTypeGoodbye: // just return the event type up for goodbye, will be handled by caller. - case rtmEventTypeDesktopNotification: - rtm.Debugln("Received desktop notification, ignoring") default: rtm.handleEvent(event.Type, rawEvent) } @@ -563,4 +576,6 @@ var EventMapping = map[string]interface{}{ "subteam_self_added": SubteamSelfAddedEvent{}, "subteam_self_removed": SubteamSelfRemovedEvent{}, "subteam_updated": SubteamUpdatedEvent{}, + + "desktop_notification": DesktopNotificationEvent{}, } diff --git a/vendor/github.com/nlopes/slack/websocket_misc.go b/vendor/github.com/nlopes/slack/websocket_misc.go index bfcc805..65a8bb6 100644 --- a/vendor/github.com/nlopes/slack/websocket_misc.go +++ b/vendor/github.com/nlopes/slack/websocket_misc.go @@ -131,7 +131,7 @@ type MemberJoinedChannelEvent struct { Inviter string `json:"inviter"` } -// MemberJoinedChannelEvent, a user left a public or private channel +// MemberLeftChannelEvent a user left a public or private channel type MemberLeftChannelEvent struct { Type string `json:"type"` User string `json:"user"`