Skip to content

Commit

Permalink
support response urls from slash commands
Browse files Browse the repository at this point in the history
  • Loading branch information
James Lawrence authored and james-lawrence committed Aug 31, 2019
1 parent a6140ff commit c664c83
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 30 deletions.
7 changes: 2 additions & 5 deletions channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ func (api *Client) channelRequest(ctx context.Context, path string, values url.V
return nil, err
}

if err := response.Err(); err != nil {
return nil, err
}

return response, nil
return response, response.Err()
}

type channelsConfig struct {
Expand Down Expand Up @@ -284,6 +280,7 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool,
"token": {api.token},
},
}

if excludeArchived {
options = append(options, GetChannelsOptionExcludeArchived())
}
Expand Down
97 changes: 89 additions & 8 deletions chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package slack
import (
"context"
"encoding/json"
"net/http"
"net/url"

"github.com/nlopes/slack/slackutilsx"
Expand All @@ -25,7 +26,7 @@ const (

type chatResponseFull struct {
Channel string `json:"channel"`
Timestamp string `json:"ts"` //Regualr message timestamp
Timestamp string `json:"ts"` //Regular message timestamp
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
Text string `json:"text"`
SlackResponse
Expand Down Expand Up @@ -156,17 +157,18 @@ func (api *Client) SendMessage(channel string, options ...MsgOption) (string, st
}

// SendMessageContext more flexible method for configuring messages with a custom context.
func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (channel string, timestamp string, text string, err error) {
func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (_channel string, _timestamp string, _text string, err error) {
var (
config sendConfig
req *http.Request
parser func(*chatResponseFull) responseParser
response chatResponseFull
)

if config, err = applyMsgOptions(api.token, channelID, api.endpoint, options...); err != nil {
if req, parser, err = buildSender(api.endpoint, options...).BuildRequest(api.token, channelID); err != nil {
return "", "", "", err
}

if err = postForm(ctx, api.httpclient, config.endpoint, config.values, &response, api); err != nil {
if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil {
return "", "", "", err
}

Expand Down Expand Up @@ -200,23 +202,84 @@ func applyMsgOptions(token, channel, apiurl string, options ...MsgOption) (sendC
return config, nil
}

func buildSender(apiurl string, options ...MsgOption) sendConfig {
return sendConfig{
apiurl: apiurl,
options: options,
}
}

type sendMode string

const (
chatUpdate sendMode = "chat.update"
chatPostMessage sendMode = "chat.postMessage"
chatDelete sendMode = "chat.delete"
chatPostEphemeral sendMode = "chat.postEphemeral"
chatResponse sendMode = "chat.responseURL"
chatMeMessage sendMode = "chat.meMessage"
chatUnfurl sendMode = "chat.unfurl"
)

type sendConfig struct {
apiurl string
apiurl string
options []MsgOption
mode sendMode
endpoint string
values url.Values
attachments []Attachment
responseType string
}

func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ func(*chatResponseFull) responseParser, err error) {
if t, err = applyMsgOptions(token, channelID, t.apiurl, t.options...); err != nil {
return nil, nil, err
}

switch t.mode {
case chatResponse:
return responseURLSender{
endpoint: t.endpoint,
values: t.values,
attachments: t.attachments,
responseType: t.responseType,
}.BuildRequest()
default:
return formSender{endpoint: t.endpoint, values: t.values}.BuildRequest()
}
}

type formSender struct {
endpoint string
values url.Values
}

func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) {
req, err := formReq(t.endpoint, t.values)
return req, func(resp *chatResponseFull) responseParser {
return newJSONParser(resp)
}, err
}

type responseURLSender struct {
endpoint string
values url.Values
attachments []Attachment
responseType string
}

func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) {
req, err := jsonReq(t.endpoint, Msg{
Text: t.values.Get("text"),
Timestamp: t.values.Get("ts"),
Attachments: t.attachments,
ResponseType: t.responseType,
})
return req, func(resp *chatResponseFull) responseParser {
return newContentTypeParser(resp)
}, err
}

// MsgOption option provided when sending a message.
type MsgOption func(*sendConfig) error

Expand Down Expand Up @@ -279,6 +342,17 @@ func MsgOptionUnfurl(timestamp string, unfurls map[string]Attachment) MsgOption
}
}

// MsgOptionResponseURL supplies a url to use as the endpoint.
func MsgOptionResponseURL(url string, rt string) MsgOption {
return func(config *sendConfig) error {
config.mode = chatResponse
config.endpoint = url
config.responseType = rt
config.values.Del("ts")
return nil
}
}

// MsgOptionAsUser whether or not to send the message as the user.
func MsgOptionAsUser(b bool) MsgOption {
return func(config *sendConfig) error {
Expand Down Expand Up @@ -324,10 +398,17 @@ func MsgOptionAttachments(attachments ...Attachment) MsgOption {
return nil
}

attachments, err := json.Marshal(attachments)
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(attachments))
config.values.Set("attachments", string(attachmentBytes))
}

return err
}
}
Expand Down
9 changes: 9 additions & 0 deletions conversation.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}

err := api.postMethod(ctx, "conversations.members", values, &response)
if err != nil {
return nil, "", err
Expand Down Expand Up @@ -160,6 +161,7 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str
"token": {api.token},
"channel": {channelID},
}

response := SlackResponse{}
err := api.postMethod(ctx, "conversations.archive", values, &response)
if err != nil {
Expand Down Expand Up @@ -229,6 +231,7 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI
SlackResponse
Channel *Channel `json:"channel"`
}{}

err := api.postMethod(ctx, "conversations.setPurpose", values, &response)
if err != nil {
return nil, err
Expand All @@ -253,6 +256,7 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha
SlackResponse
Channel *Channel `json:"channel"`
}{}

err := api.postMethod(ctx, "conversations.rename", values, &response)
if err != nil {
return nil, err
Expand All @@ -277,6 +281,7 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
SlackResponse
Channel *Channel `json:"channel"`
}{}

err := api.postMethod(ctx, "conversations.invite", values, &response)
if err != nil {
return nil, err
Expand All @@ -297,6 +302,7 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI
"channel": {channelID},
"user": {user},
}

response := SlackResponse{}
err := api.postMethod(ctx, "conversations.kick", values, &response)
if err != nil {
Expand Down Expand Up @@ -479,6 +485,7 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}

err = api.postMethod(ctx, "conversations.list", values, &response)
if err != nil {
return nil, "", err
Expand Down Expand Up @@ -516,6 +523,7 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv
AlreadyOpen bool `json:"already_open"`
SlackResponse
}{}

err := api.postMethod(ctx, "conversations.open", values, &response)
if err != nil {
return nil, false, false, err
Expand All @@ -540,6 +548,7 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string
} `json:"response_metadata"`
SlackResponse
}{}

err := api.postMethod(ctx, "conversations.join", values, &response)
if err != nil {
return nil, "", nil, err
Expand Down
1 change: 1 addition & 0 deletions files.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
}
err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api)
}

if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ func TestUploadFile(t *testing.T) {

reader := bytes.NewBufferString("test reader")
params = FileUploadParameters{
Filename: "test.txt", Reader: reader,
Filename: "test.txt",
Reader: reader,
Channels: []string{"CXXXXXXXX"}}
if _, err := api.UploadFile(params); err != nil {
t.Errorf("Unexpected error: %s", err)
Expand Down
7 changes: 7 additions & 0 deletions messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ type Msg struct {
Blocks Blocks `json:"blocks,omitempty"`
}

const (
// ResponseTypeInChannel in channel response for slash commands.
ResponseTypeInChannel = "in_channel"
// ResponseTypeEphemeral ephemeral respone for slash commands.
ResponseTypeEphemeral = "ephemeral"
)

// Icon is used for bot messages
type Icon struct {
IconURL string `json:"icon_url,omitempty"`
Expand Down
Loading

0 comments on commit c664c83

Please sign in to comment.