Update vendor (slack)

This commit is contained in:
Wim 2018-01-08 22:41:38 +01:00
parent 9a95293bdf
commit 4a96a977c0
58 changed files with 768 additions and 166 deletions

View File

@ -1,20 +0,0 @@
package slack
import (
"net"
"net/url"
)
var portMapping = map[string]string{"ws": "80", "wss": "443"}
func websocketizeURLPort(orig string) (string, error) {
urlObj, err := url.ParseRequestURI(orig)
if err != nil {
return "", err
}
_, _, err = net.SplitHostPort(urlObj.Host)
if err != nil {
return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
}
return orig, nil
}

View File

@ -25,6 +25,7 @@ type AttachmentAction struct {
SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu. SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional. OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional. Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
URL string `json:"url,omitempty"` // Optional.
} }
// AttachmentActionOption the individual option to appear in action menu. // AttachmentActionOption the individual option to appear in action menu.
@ -48,6 +49,9 @@ type AttachmentActionCallback struct {
Channel Channel `json:"channel"` Channel Channel `json:"channel"`
User User `json:"user"` User User `json:"user"`
Name string `json:"name"`
Value string `json:"value"`
OriginalMessage Message `json:"original_message"` OriginalMessage Message `json:"original_message"`
ActionTs string `json:"action_ts"` ActionTs string `json:"action_ts"`

View File

@ -38,51 +38,51 @@ func channelRequest(ctx context.Context, path string, values url.Values, debug b
} }
// ArchiveChannel archives the given channel // ArchiveChannel archives the given channel
func (api *Client) ArchiveChannel(channel string) error { // see https://api.slack.com/methods/channels.archive
return api.ArchiveChannelContext(context.Background(), channel) func (api *Client) ArchiveChannel(channelID string) error {
return api.ArchiveChannelContext(context.Background(), channelID)
} }
// ArchiveChannelContext archives the given channel with a custom context // ArchiveChannelContext archives the given channel with a custom context
func (api *Client) ArchiveChannelContext(ctx context.Context, channel string) error { // see https://api.slack.com/methods/channels.archive
func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) error {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
} }
_, err := channelRequest(ctx, "channels.archive", values, api.debug) _, err := channelRequest(ctx, "channels.archive", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// UnarchiveChannel unarchives the given channel // UnarchiveChannel unarchives the given channel
func (api *Client) UnarchiveChannel(channel string) error { // see https://api.slack.com/methods/channels.unarchive
return api.UnarchiveChannelContext(context.Background(), channel) func (api *Client) UnarchiveChannel(channelID string) error {
return api.UnarchiveChannelContext(context.Background(), channelID)
} }
// UnarchiveChannelContext unarchives the given channel with a custom context // UnarchiveChannelContext unarchives the given channel with a custom context
func (api *Client) UnarchiveChannelContext(ctx context.Context, channel string) error { // see https://api.slack.com/methods/channels.unarchive
func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string) error {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
} }
_, err := channelRequest(ctx, "channels.unarchive", values, api.debug) _, err := channelRequest(ctx, "channels.unarchive", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// CreateChannel creates a channel with the given name and returns a *Channel // CreateChannel creates a channel with the given name and returns a *Channel
func (api *Client) CreateChannel(channel string) (*Channel, error) { // see https://api.slack.com/methods/channels.create
return api.CreateChannelContext(context.Background(), channel) func (api *Client) CreateChannel(channelName string) (*Channel, error) {
return api.CreateChannelContext(context.Background(), channelName)
} }
// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context // CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context
func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*Channel, error) { // see https://api.slack.com/methods/channels.create
func (api *Client) CreateChannelContext(ctx context.Context, channelName string) (*Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"name": {channel}, "name": {channelName},
} }
response, err := channelRequest(ctx, "channels.create", values, api.debug) response, err := channelRequest(ctx, "channels.create", values, api.debug)
if err != nil { if err != nil {
@ -92,15 +92,17 @@ func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*C
} }
// GetChannelHistory retrieves the channel history // GetChannelHistory retrieves the channel history
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) { // see https://api.slack.com/methods/channels.history
return api.GetChannelHistoryContext(context.Background(), channel, params) func (api *Client) GetChannelHistory(channelID string, params HistoryParameters) (*History, error) {
return api.GetChannelHistoryContext(context.Background(), channelID, params)
} }
// GetChannelHistoryContext retrieves the channel history with a custom context // GetChannelHistoryContext retrieves the channel history with a custom context
func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) { // see https://api.slack.com/methods/channels.history
func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID string, params HistoryParameters) (*History, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
} }
if params.Latest != DEFAULT_HISTORY_LATEST { if params.Latest != DEFAULT_HISTORY_LATEST {
values.Add("latest", params.Latest) values.Add("latest", params.Latest)
@ -133,15 +135,17 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
} }
// GetChannelInfo retrieves the given channel // GetChannelInfo retrieves the given channel
func (api *Client) GetChannelInfo(channel string) (*Channel, error) { // see https://api.slack.com/methods/channels.info
return api.GetChannelInfoContext(context.Background(), channel) func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
return api.GetChannelInfoContext(context.Background(), channelID)
} }
// GetChannelInfoContext retrieves the given channel with a custom context // GetChannelInfoContext retrieves the given channel with a custom context
func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*Channel, error) { // see https://api.slack.com/methods/channels.info
func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) (*Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
} }
response, err := channelRequest(ctx, "channels.info", values, api.debug) response, err := channelRequest(ctx, "channels.info", values, api.debug)
if err != nil { if err != nil {
@ -151,15 +155,17 @@ func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*
} }
// InviteUserToChannel invites a user to a given channel and returns a *Channel // InviteUserToChannel invites a user to a given channel and returns a *Channel
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) { // see https://api.slack.com/methods/channels.invite
return api.InviteUserToChannelContext(context.Background(), channel, user) func (api *Client) InviteUserToChannel(channelID, user string) (*Channel, error) {
return api.InviteUserToChannelContext(context.Background(), channelID, user)
} }
// InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context // InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context
func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user string) (*Channel, error) { // see https://api.slack.com/methods/channels.invite
func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, user string) (*Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"user": {user}, "user": {user},
} }
response, err := channelRequest(ctx, "channels.invite", values, api.debug) response, err := channelRequest(ctx, "channels.invite", values, api.debug)
@ -170,15 +176,17 @@ func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user
} }
// JoinChannel joins the currently authenticated user to a channel // JoinChannel joins the currently authenticated user to a channel
func (api *Client) JoinChannel(channel string) (*Channel, error) { // see https://api.slack.com/methods/channels.join
return api.JoinChannelContext(context.Background(), channel) func (api *Client) JoinChannel(channelName string) (*Channel, error) {
return api.JoinChannelContext(context.Background(), channelName)
} }
// JoinChannelContext joins the currently authenticated user to a channel with a custom context // JoinChannelContext joins the currently authenticated user to a channel with a custom context
func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Channel, error) { // see https://api.slack.com/methods/channels.join
func (api *Client) JoinChannelContext(ctx context.Context, channelName string) (*Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"name": {channel}, "name": {channelName},
} }
response, err := channelRequest(ctx, "channels.join", values, api.debug) response, err := channelRequest(ctx, "channels.join", values, api.debug)
if err != nil { if err != nil {
@ -188,15 +196,17 @@ func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Cha
} }
// LeaveChannel makes the authenticated user leave the given channel // LeaveChannel makes the authenticated user leave the given channel
func (api *Client) LeaveChannel(channel string) (bool, error) { // see https://api.slack.com/methods/channels.leave
return api.LeaveChannelContext(context.Background(), channel) func (api *Client) LeaveChannel(channelID string) (bool, error) {
return api.LeaveChannelContext(context.Background(), channelID)
} }
// LeaveChannelContext makes the authenticated user leave the given channel with a custom context // LeaveChannelContext makes the authenticated user leave the given channel with a custom context
func (api *Client) LeaveChannelContext(ctx context.Context, channel string) (bool, error) { // see https://api.slack.com/methods/channels.leave
func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (bool, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
} }
response, err := channelRequest(ctx, "channels.leave", values, api.debug) response, err := channelRequest(ctx, "channels.leave", values, api.debug)
if err != nil { if err != nil {
@ -209,30 +219,31 @@ func (api *Client) LeaveChannelContext(ctx context.Context, channel string) (boo
} }
// KickUserFromChannel kicks a user from a given channel // KickUserFromChannel kicks a user from a given channel
func (api *Client) KickUserFromChannel(channel, user string) error { // see https://api.slack.com/methods/channels.kick
return api.KickUserFromChannelContext(context.Background(), channel, user) func (api *Client) KickUserFromChannel(channelID, user string) error {
return api.KickUserFromChannelContext(context.Background(), channelID, user)
} }
// KickUserFromChannelContext kicks a user from a given channel with a custom context // KickUserFromChannelContext kicks a user from a given channel with a custom context
func (api *Client) KickUserFromChannelContext(ctx context.Context, channel, user string) error { // see https://api.slack.com/methods/channels.kick
func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, user string) error {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"user": {user}, "user": {user},
} }
_, err := channelRequest(ctx, "channels.kick", values, api.debug) _, err := channelRequest(ctx, "channels.kick", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// GetChannels retrieves all the channels // GetChannels retrieves all the channels
// see https://api.slack.com/methods/channels.list
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) { func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
return api.GetChannelsContext(context.Background(), excludeArchived) return api.GetChannelsContext(context.Background(), excludeArchived)
} }
// GetChannelsContext retrieves all the channels with a custom context // GetChannelsContext retrieves all the channels with a custom context
// see https://api.slack.com/methods/channels.list
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) { func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
@ -252,35 +263,36 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool)
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls // timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A // (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. // timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
func (api *Client) SetChannelReadMark(channel, ts string) error { // see https://api.slack.com/methods/channels.mark
return api.SetChannelReadMarkContext(context.Background(), channel, ts) func (api *Client) SetChannelReadMark(channelID, ts string) error {
return api.SetChannelReadMarkContext(context.Background(), channelID, ts)
} }
// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context // SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
// For more details see SetChannelReadMark documentation // For more details see SetChannelReadMark documentation
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channel, ts string) error { // see https://api.slack.com/methods/channels.mark
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts string) error {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"ts": {ts}, "ts": {ts},
} }
_, err := channelRequest(ctx, "channels.mark", values, api.debug) _, err := channelRequest(ctx, "channels.mark", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// RenameChannel renames a given channel // RenameChannel renames a given channel
func (api *Client) RenameChannel(channel, name string) (*Channel, error) { // see https://api.slack.com/methods/channels.rename
return api.RenameChannelContext(context.Background(), channel, name) func (api *Client) RenameChannel(channelID, name string) (*Channel, error) {
return api.RenameChannelContext(context.Background(), channelID, name)
} }
// RenameChannelContext renames a given channel with a custom context // RenameChannelContext renames a given channel with a custom context
func (api *Client) RenameChannelContext(ctx context.Context, channel, name string) (*Channel, error) { // see https://api.slack.com/methods/channels.rename
func (api *Client) RenameChannelContext(ctx context.Context, channelID, name string) (*Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"name": {name}, "name": {name},
} }
// XXX: the created entry in this call returns a string instead of a number // XXX: the created entry in this call returns a string instead of a number
@ -293,15 +305,17 @@ func (api *Client) RenameChannelContext(ctx context.Context, channel, name strin
} }
// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set // SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) { // see https://api.slack.com/methods/channels.setPurpose
return api.SetChannelPurposeContext(context.Background(), channel, purpose) func (api *Client) SetChannelPurpose(channelID, purpose string) (string, error) {
return api.SetChannelPurposeContext(context.Background(), channelID, purpose)
} }
// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context // SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context
func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpose string) (string, error) { // see https://api.slack.com/methods/channels.setPurpose
func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purpose string) (string, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"purpose": {purpose}, "purpose": {purpose},
} }
response, err := channelRequest(ctx, "channels.setPurpose", values, api.debug) response, err := channelRequest(ctx, "channels.setPurpose", values, api.debug)
@ -312,15 +326,17 @@ func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpos
} }
// SetChannelTopic sets the channel topic and returns the topic that was successfully set // SetChannelTopic sets the channel topic and returns the topic that was successfully set
func (api *Client) SetChannelTopic(channel, topic string) (string, error) { // see https://api.slack.com/methods/channels.setTopic
return api.SetChannelTopicContext(context.Background(), channel, topic) func (api *Client) SetChannelTopic(channelID, topic string) (string, error) {
return api.SetChannelTopicContext(context.Background(), channelID, topic)
} }
// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context // SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context
func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic string) (string, error) { // see https://api.slack.com/methods/channels.setTopic
func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic string) (string, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"topic": {topic}, "topic": {topic},
} }
response, err := channelRequest(ctx, "channels.setTopic", values, api.debug) response, err := channelRequest(ctx, "channels.setTopic", values, api.debug)
@ -331,15 +347,17 @@ func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic st
} }
// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it). // GetChannelReplies gets an entire thread (a message plus all the messages in reply to it).
func (api *Client) GetChannelReplies(channel, thread_ts string) ([]Message, error) { // see https://api.slack.com/methods/channels.replies
return api.GetChannelRepliesContext(context.Background(), channel, thread_ts) func (api *Client) GetChannelReplies(channelID, thread_ts string) ([]Message, error) {
return api.GetChannelRepliesContext(context.Background(), channelID, thread_ts)
} }
// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context // GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context
func (api *Client) GetChannelRepliesContext(ctx context.Context, channel, thread_ts string) ([]Message, error) { // see https://api.slack.com/methods/channels.replies
func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thread_ts string) ([]Message, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},
"channel": {channel}, "channel": {channelID},
"thread_ts": {thread_ts}, "thread_ts": {thread_ts},
} }
response, err := channelRequest(ctx, "channels.replies", values, api.debug) response, err := channelRequest(ctx, "channels.replies", values, api.debug)

View File

@ -11,6 +11,7 @@ import (
const ( const (
DEFAULT_MESSAGE_USERNAME = "" DEFAULT_MESSAGE_USERNAME = ""
DEFAULT_MESSAGE_THREAD_TIMESTAMP = "" DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
DEFAULT_MESSAGE_REPLY_BROADCAST = false
DEFAULT_MESSAGE_ASUSER = false DEFAULT_MESSAGE_ASUSER = false
DEFAULT_MESSAGE_PARSE = "" DEFAULT_MESSAGE_PARSE = ""
DEFAULT_MESSAGE_LINK_NAMES = 0 DEFAULT_MESSAGE_LINK_NAMES = 0
@ -36,6 +37,7 @@ type PostMessageParameters struct {
AsUser bool `json:"as_user"` AsUser bool `json:"as_user"`
Parse string `json:"parse"` Parse string `json:"parse"`
ThreadTimestamp string `json:"thread_ts"` ThreadTimestamp string `json:"thread_ts"`
ReplyBroadcast bool `json:"reply_broadcast"`
LinkNames int `json:"link_names"` LinkNames int `json:"link_names"`
Attachments []Attachment `json:"attachments"` Attachments []Attachment `json:"attachments"`
UnfurlLinks bool `json:"unfurl_links"` UnfurlLinks bool `json:"unfurl_links"`
@ -44,12 +46,17 @@ type PostMessageParameters struct {
IconEmoji string `json:"icon_emoji"` IconEmoji string `json:"icon_emoji"`
Markdown bool `json:"mrkdwn,omitempty"` Markdown bool `json:"mrkdwn,omitempty"`
EscapeText bool `json:"escape_text"` EscapeText bool `json:"escape_text"`
// chat.postEphemeral support
Channel string `json:"channel"`
User string `json:"user"`
} }
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set // NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
func NewPostMessageParameters() PostMessageParameters { func NewPostMessageParameters() PostMessageParameters {
return PostMessageParameters{ return PostMessageParameters{
Username: DEFAULT_MESSAGE_USERNAME, Username: DEFAULT_MESSAGE_USERNAME,
User: DEFAULT_MESSAGE_USERNAME,
AsUser: DEFAULT_MESSAGE_ASUSER, AsUser: DEFAULT_MESSAGE_ASUSER,
Parse: DEFAULT_MESSAGE_PARSE, Parse: DEFAULT_MESSAGE_PARSE,
LinkNames: DEFAULT_MESSAGE_LINK_NAMES, LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
@ -102,6 +109,37 @@ func (api *Client) PostMessageContext(ctx context.Context, channel, text string,
return respChannel, respTimestamp, err return respChannel, respTimestamp, err
} }
// PostEphemeral sends an ephemeral message to a user in a channel.
// Message is escaped by default according to https://api.slack.com/docs/formatting
// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
func (api *Client) PostEphemeral(channel, userID string, options ...MsgOption) (string, error) {
options = append(options, MsgOptionPostEphemeral())
return api.PostEphemeralContext(
context.Background(),
channel,
userID,
options...,
)
}
// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context
// For more details, see PostEphemeral documentation
func (api *Client) PostEphemeralContext(ctx context.Context, channel, userID string, options ...MsgOption) (string, error) {
path, values, err := ApplyMsgOptions(api.config.token, channel, options...)
if err != nil {
return "", err
}
values.Add("user", userID)
response, err := chatRequest(ctx, path, values, api.debug)
if err != nil {
return "", err
}
return response.Timestamp, nil
}
// UpdateMessage updates a message in a channel // UpdateMessage updates a message in a channel
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) { func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
return api.UpdateMessageContext(context.Background(), channel, timestamp, text) return api.UpdateMessageContext(context.Background(), channel, timestamp, text)
@ -174,6 +212,7 @@ const (
chatUpdate sendMode = "chat.update" chatUpdate sendMode = "chat.update"
chatPostMessage sendMode = "chat.postMessage" chatPostMessage sendMode = "chat.postMessage"
chatDelete sendMode = "chat.delete" chatDelete sendMode = "chat.delete"
chatPostEphemeral sendMode = "chat.postEphemeral"
) )
type sendConfig struct { type sendConfig struct {
@ -193,6 +232,15 @@ func MsgOptionPost() MsgOption {
} }
} }
// MsgOptionPostEphemeral posts an ephemeral message
func MsgOptionPostEphemeral() MsgOption {
return func(config *sendConfig) error {
config.mode = chatPostEphemeral
config.values.Del("ts")
return nil
}
}
// MsgOptionUpdate updates a message based on the timestamp. // MsgOptionUpdate updates a message based on the timestamp.
func MsgOptionUpdate(timestamp string) MsgOption { func MsgOptionUpdate(timestamp string) MsgOption {
return func(config *sendConfig) error { return func(config *sendConfig) error {
@ -279,6 +327,11 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
config.values.Set("username", string(params.Username)) config.values.Set("username", string(params.Username))
} }
// chat.postEphemeral support
if params.User != DEFAULT_MESSAGE_USERNAME {
config.values.Set("user", params.User)
}
// never generates an error. // never generates an error.
MsgOptionAsUser(params.AsUser)(config) MsgOptionAsUser(params.AsUser)(config)
@ -314,6 +367,9 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP { if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
config.values.Set("thread_ts", params.ThreadTimestamp) config.values.Set("thread_ts", params.ThreadTimestamp)
} }
if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST {
config.values.Set("reply_broadcast", "true")
}
return nil return nil
} }

View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
channels, err := api.GetChannels(false)
if err != nil {
fmt.Printf("%s\n", err)
return
}
for _, channel := range channels {
fmt.Println(channel.Name)
// channel is of type conversation & groupConversation
// see all available methods in `conversation.go`
}
}

30
vendor/github.com/nlopes/slack/examples/files/files.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
params := slack.FileUploadParameters{
Title: "Batman Example",
//Filetype: "txt",
File: "example.txt",
//Content: "Nan Nan Nan Nan Nan Nan Nan Nan Batman",
}
file, err := api.UploadFile(params)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("Name: %s, URL: %s\n", file.Name, file.URL)
err = api.DeleteFile(file.ID)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("File %s deleted successfully.\n", file.Name)
}

View File

@ -0,0 +1,22 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
// If you set debugging, it will log all requests to the console
// Useful when encountering issues
// api.SetDebug(true)
groups, err := api.GetGroups(false)
if err != nil {
fmt.Printf("%s\n", err)
return
}
for _, group := range groups {
fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name)
}
}

21
vendor/github.com/nlopes/slack/examples/ims/ims.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
userID := "USER_ID"
_, _, channelID, err := api.OpenIMChannel(userID)
if err != nil {
fmt.Printf("%s\n", err)
}
api.PostMessage(channelID, "Hello World!", slack.PostMessageParameters{})
}

View File

@ -0,0 +1,32 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
params := slack.PostMessageParameters{}
attachment := slack.Attachment{
Pretext: "some pretext",
Text: "some text",
// Uncomment the following part to send a field too
/*
Fields: []slack.AttachmentField{
slack.AttachmentField{
Title: "a",
Value: "no",
},
},
*/
}
params.Attachments = []slack.Attachment{attachment}
channelID, timestamp, err := api.PostMessage("CHANNEL_ID", "Some text", params)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp)
}

123
vendor/github.com/nlopes/slack/examples/pins/pins.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
package main
import (
"flag"
"fmt"
"github.com/nlopes/slack"
)
/*
WARNING: This example is destructive in the sense that it create a channel called testpinning
*/
func main() {
var (
apiToken string
debug bool
)
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
flag.BoolVar(&debug, "debug", false, "Show JSON output")
flag.Parse()
api := slack.New(apiToken)
if debug {
api.SetDebug(true)
}
var (
postAsUserName string
postAsUserID string
postToChannelID string
)
// Find the user to post as.
authTest, err := api.AuthTest()
if err != nil {
fmt.Printf("Error getting channels: %s\n", err)
return
}
channelName := "testpinning"
// Post as the authenticated user.
postAsUserName = authTest.User
postAsUserID = authTest.UserID
// Create a temporary channel
channel, err := api.CreateChannel(channelName)
if err != nil {
// If the channel exists, that means we just need to unarchive it
if err.Error() == "name_taken" {
err = nil
channels, err := api.GetChannels(false)
if err != nil {
fmt.Println("Could not retrieve channels")
return
}
for _, archivedChannel := range channels {
if archivedChannel.Name == channelName {
if archivedChannel.IsArchived {
err = api.UnarchiveChannel(archivedChannel.ID)
if err != nil {
fmt.Printf("Could not unarchive %s: %s\n", archivedChannel.ID, err)
return
}
}
channel = &archivedChannel
break
}
}
}
if err != nil {
fmt.Printf("Error setting test channel for pinning: %s\n", err)
return
}
}
postToChannelID = channel.ID
fmt.Printf("Posting as %s (%s) in channel %s\n", postAsUserName, postAsUserID, postToChannelID)
// Post a message.
postParams := slack.PostMessageParameters{}
channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams)
if err != nil {
fmt.Printf("Error posting message: %s\n", err)
return
}
// Grab a reference to the message.
msgRef := slack.NewRefToMessage(channelID, timestamp)
// Add message pin to channel
if err := api.AddPin(channelID, msgRef); err != nil {
fmt.Printf("Error adding pin: %s\n", err)
return
}
// List all of the users pins.
listPins, _, err := api.ListPins(channelID)
if err != nil {
fmt.Printf("Error listing pins: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("All pins by %s...\n", authTest.User)
for _, item := range listPins {
fmt.Printf(" > Item type: %s\n", item.Type)
}
// Remove the pin.
err = api.RemovePin(channelID, msgRef)
if err != nil {
fmt.Printf("Error remove pin: %s\n", err)
return
}
if err = api.ArchiveChannel(channelID); err != nil {
fmt.Printf("Error archiving channel: %s\n", err)
return
}
}

View File

@ -0,0 +1,126 @@
package main
import (
"flag"
"fmt"
"github.com/nlopes/slack"
)
func main() {
var (
apiToken string
debug bool
)
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
flag.BoolVar(&debug, "debug", false, "Show JSON output")
flag.Parse()
api := slack.New(apiToken)
if debug {
api.SetDebug(true)
}
var (
postAsUserName string
postAsUserID string
postToUserName string
postToUserID string
postToChannelID string
)
// Find the user to post as.
authTest, err := api.AuthTest()
if err != nil {
fmt.Printf("Error getting channels: %s\n", err)
return
}
// Post as the authenticated user.
postAsUserName = authTest.User
postAsUserID = authTest.UserID
// Posting to DM with self causes a conversation with slackbot.
postToUserName = authTest.User
postToUserID = authTest.UserID
// Find the channel.
_, _, chanID, err := api.OpenIMChannel(postToUserID)
if err != nil {
fmt.Printf("Error opening IM: %s\n", err)
return
}
postToChannelID = chanID
fmt.Printf("Posting as %s (%s) in DM with %s (%s), channel %s\n", postAsUserName, postAsUserID, postToUserName, postToUserID, postToChannelID)
// Post a message.
postParams := slack.PostMessageParameters{}
channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams)
if err != nil {
fmt.Printf("Error posting message: %s\n", err)
return
}
// Grab a reference to the message.
msgRef := slack.NewRefToMessage(channelID, timestamp)
// React with :+1:
if err := api.AddReaction("+1", msgRef); err != nil {
fmt.Printf("Error adding reaction: %s\n", err)
return
}
// React with :-1:
if err := api.AddReaction("cry", msgRef); err != nil {
fmt.Printf("Error adding reaction: %s\n", err)
return
}
// Get all reactions on the message.
msgReactions, err := api.GetReactions(msgRef, slack.NewGetReactionsParameters())
if err != nil {
fmt.Printf("Error getting reactions: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("%d reactions to message...\n", len(msgReactions))
for _, r := range msgReactions {
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
}
// List all of the users reactions.
listReactions, _, err := api.ListReactions(slack.NewListReactionsParameters())
if err != nil {
fmt.Printf("Error listing reactions: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("All reactions by %s...\n", authTest.User)
for _, item := range listReactions {
fmt.Printf("%d on a %s...\n", len(item.Reactions), item.Type)
for _, r := range item.Reactions {
fmt.Printf(" %s (along with %d others)\n", r.Name, r.Count-1)
}
}
// Remove the :cry: reaction.
err = api.RemoveReaction("cry", msgRef)
if err != nil {
fmt.Printf("Error remove reaction: %s\n", err)
return
}
// Get all reactions on the message.
msgReactions, err = api.GetReactions(msgRef, slack.NewGetReactionsParameters())
if err != nil {
fmt.Printf("Error getting reactions: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("%d reactions to message after removing cry...\n", len(msgReactions))
for _, r := range msgReactions {
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
}
}

46
vendor/github.com/nlopes/slack/examples/stars/stars.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"flag"
"fmt"
"github.com/nlopes/slack"
)
func main() {
var (
apiToken string
debug bool
)
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
flag.BoolVar(&debug, "debug", false, "Show JSON output")
flag.Parse()
api := slack.New(apiToken)
if debug {
api.SetDebug(true)
}
// Get all stars for the usr.
params := slack.NewStarsParameters()
starredItems, _, err := api.GetStarred(params)
if err != nil {
fmt.Printf("Error getting stars: %s\n", err)
return
}
for _, s := range starredItems {
var desc string
switch s.Type {
case slack.TYPE_MESSAGE:
desc = s.Message.Text
case slack.TYPE_FILE:
desc = s.File.Name
case slack.TYPE_FILE_COMMENT:
desc = s.File.Name + " - " + s.Comment.Comment
case slack.TYPE_CHANNEL, slack.TYPE_IM, slack.TYPE_GROUP:
desc = s.Channel
}
fmt.Printf("Starred %s: %s\n", s.Type, desc)
}
}

25
vendor/github.com/nlopes/slack/examples/team/team.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
//Example for single user
billingActive, err := api.GetBillableInfo("U023BECGF")
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("ID: U023BECGF, BillingActive: %v\n\n\n", billingActive["U023BECGF"])
//Example for team
billingActiveForTeam, _ := api.GetBillableInfoForTeam()
for id, value := range billingActiveForTeam {
fmt.Printf("ID: %v, BillingActive: %v\n", id, value)
}
}

17
vendor/github.com/nlopes/slack/examples/users/users.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
user, err := api.GetUserInfo("U023BECGF")
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email)
}

View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
"log"
"os"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR TOKEN HERE")
logger := log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags)
slack.SetLogger(logger)
api.SetDebug(true)
rtm := api.NewRTM()
go rtm.ManageConnection()
for msg := range rtm.IncomingEvents {
fmt.Print("Event Received: ")
switch ev := msg.Data.(type) {
case *slack.HelloEvent:
// Ignore hello
case *slack.ConnectedEvent:
fmt.Println("Infos:", ev.Info)
fmt.Println("Connection counter:", ev.ConnectionCount)
// Replace C2147483705 with your Channel ID
rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "C2147483705"))
case *slack.MessageEvent:
fmt.Printf("Message: %v\n", ev)
case *slack.PresenceChangeEvent:
fmt.Printf("Presence Change: %v\n", ev)
case *slack.LatencyReport:
fmt.Printf("Current latency: %v\n", ev.Value)
case *slack.RTMError:
fmt.Printf("Error: %s\n", ev.Error())
case *slack.InvalidAuthEvent:
fmt.Printf("Invalid credentials")
return
default:
// Ignore other events..
// fmt.Printf("Unexpected: %v\n", msg.Data)
}
}
}

View File

@ -267,10 +267,7 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) error {
"file": {fileID}, "file": {fileID},
} }
_, err := fileRequest(ctx, "files.delete", values, api.debug) _, err := fileRequest(ctx, "files.delete", values, api.debug)
if err != nil {
return err return err
}
return nil
} }

View File

@ -208,11 +208,8 @@ func (api *Client) LeaveGroupContext(ctx context.Context, group string) error {
"channel": {group}, "channel": {group},
} }
_, err := groupRequest(ctx, "groups.leave", values, api.debug) _, err := groupRequest(ctx, "groups.leave", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// KickUserFromGroup kicks a user from a group // KickUserFromGroup kicks a user from a group
func (api *Client) KickUserFromGroup(group, user string) error { func (api *Client) KickUserFromGroup(group, user string) error {
@ -227,11 +224,8 @@ func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user str
"user": {user}, "user": {user},
} }
_, err := groupRequest(ctx, "groups.kick", values, api.debug) _, err := groupRequest(ctx, "groups.kick", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// GetGroups retrieves all groups // GetGroups retrieves all groups
func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) { func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
@ -289,11 +283,8 @@ func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string
"ts": {ts}, "ts": {ts},
} }
_, err := groupRequest(ctx, "groups.mark", values, api.debug) _, err := groupRequest(ctx, "groups.mark", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// OpenGroup opens a private group // OpenGroup opens a private group
func (api *Client) OpenGroup(group string) (bool, bool, error) { func (api *Client) OpenGroup(group string) (bool, bool, error) {

View File

@ -3,6 +3,7 @@ package slack
// OutgoingMessage is used for the realtime API, and seems incomplete. // OutgoingMessage is used for the realtime API, and seems incomplete.
type OutgoingMessage struct { type OutgoingMessage struct {
ID int `json:"id"` ID int `json:"id"`
// channel ID
Channel string `json:"channel,omitempty"` Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"` Text string `json:"text,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
@ -121,12 +122,12 @@ type Pong struct {
// NewOutgoingMessage prepares an OutgoingMessage that the user can // NewOutgoingMessage prepares an OutgoingMessage that the user can
// use to send a message. Use this function to properly set the // use to send a message. Use this function to properly set the
// messageID. // messageID.
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage { func (rtm *RTM) NewOutgoingMessage(text string, channelID string) *OutgoingMessage {
id := rtm.idGen.Next() id := rtm.idGen.Next()
return &OutgoingMessage{ return &OutgoingMessage{
ID: id, ID: id,
Type: "message", Type: "message",
Channel: channel, Channel: channelID,
Text: text, Text: text,
} }
} }
@ -134,11 +135,11 @@ func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage
// NewTypingMessage prepares an OutgoingMessage that the user can // NewTypingMessage prepares an OutgoingMessage that the user can
// use to send as a typing indicator. Use this function to properly set the // use to send as a typing indicator. Use this function to properly set the
// messageID. // messageID.
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage { func (rtm *RTM) NewTypingMessage(channelID string) *OutgoingMessage {
id := rtm.idGen.Next() id := rtm.idGen.Next()
return &OutgoingMessage{ return &OutgoingMessage{
ID: id, ID: id,
Type: "typing", Type: "typing",
Channel: channel, Channel: channelID,
} }
} }

View File

@ -13,6 +13,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -42,6 +43,14 @@ func (s WebError) Error() string {
return string(s) return string(s)
} }
type RateLimitedError struct {
RetryAfter time.Duration
}
func (e *RateLimitedError) Error() string {
return fmt.Sprintf("Slack rate limit exceeded, retry after %s", e.RetryAfter)
}
func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) { func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) {
body := &bytes.Buffer{} body := &bytes.Buffer{}
wr := multipart.NewWriter(body) wr := multipart.NewWriter(body)
@ -79,12 +88,7 @@ func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error
logger.Printf("parseResponseBody: %s\n", string(response)) logger.Printf("parseResponseBody: %s\n", string(response))
} }
err = json.Unmarshal(response, &intf) return json.Unmarshal(response, &intf)
if err != nil {
return err
}
return nil
} }
func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error { func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error {
@ -112,8 +116,16 @@ func postWithMultipartResponse(ctx context.Context, path, name, fieldname string
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
if err != nil {
return err
}
return &RateLimitedError{time.Duration(retry) * time.Second}
}
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it. // Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
if resp.StatusCode != 200 { if resp.StatusCode != http.StatusOK {
logResponse(resp, debug) logResponse(resp, debug)
return fmt.Errorf("Slack server error: %s.", resp.Status) return fmt.Errorf("Slack server error: %s.", resp.Status)
} }
@ -136,8 +148,16 @@ func postForm(ctx context.Context, endpoint string, values url.Values, intf inte
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
if err != nil {
return err
}
return &RateLimitedError{time.Duration(retry) * time.Second}
}
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it. // Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
if resp.StatusCode != 200 { if resp.StatusCode != http.StatusOK {
logResponse(resp, debug) logResponse(resp, debug)
return fmt.Errorf("Slack server error: %s.", resp.Status) return fmt.Errorf("Slack server error: %s.", resp.Status)
} }

View File

@ -27,17 +27,8 @@ func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketUR
if !response.Ok { if !response.Ok {
return nil, "", response.Error return nil, "", response.Error
} }
// websocket.Dial does not accept url without the port (yet)
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
// but slack returns the address with no port, so we have to fix it
api.Debugln("Using URL:", response.Info.URL) api.Debugln("Using URL:", response.Info.URL)
websocketURL, err = websocketizeURLPort(response.Info.URL) return &response.Info, response.Info.URL, nil
if err != nil {
return nil, "", fmt.Errorf("parsing response URL: %s", err)
}
return &response.Info, websocketURL, nil
} }
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block. // ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
@ -59,17 +50,8 @@ func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocket
if !response.Ok { if !response.Ok {
return nil, "", response.Error return nil, "", response.Error
} }
// websocket.Dial does not accept url without the port (yet)
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
// but slack returns the address with no port, so we have to fix it
api.Debugln("Using URL:", response.Info.URL) api.Debugln("Using URL:", response.Info.URL)
websocketURL, err = websocketizeURLPort(response.Info.URL) return &response.Info, response.Info.URL, nil
if err != nil {
return nil, "", fmt.Errorf("parsing response URL: %s", err)
}
return &response.Info, websocketURL, nil
} }
// NewRTM returns a RTM, which provides a fully managed connection to // NewRTM returns a RTM, which provides a fully managed connection to
@ -90,6 +72,7 @@ func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM {
isConnected: false, isConnected: false,
wasIntentional: true, wasIntentional: true,
killChannel: make(chan bool), killChannel: make(chan bool),
disconnected: make(chan struct{}),
forcePing: make(chan bool), forcePing: make(chan bool),
rawEvents: make(chan json.RawMessage), rawEvents: make(chan json.RawMessage),
idGen: NewSafeID(1), idGen: NewSafeID(1),

View File

@ -3,12 +3,13 @@ package slack
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"log" "log"
"net/url" "net/url"
"os" "os"
) )
var logger *log.Logger // A logger that can be set by consumers var logger stdLogger // A logger that can be set by consumers
/* /*
Added as a var so that we can change this for testing purposes Added as a var so that we can change this for testing purposes
*/ */
@ -41,12 +42,31 @@ type Client struct {
debug bool debug bool
} }
// stdLogger is a logger interface compatible with both stdlib and some
// 3rd party loggers such as logrus.
type stdLogger interface {
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
Fatal(...interface{})
Fatalf(string, ...interface{})
Fatalln(...interface{})
Panic(...interface{})
Panicf(string, ...interface{})
Panicln(...interface{})
Output(int, string) error
}
// SetLogger let's library users supply a logger, so that api debugging // SetLogger let's library users supply a logger, so that api debugging
// can be logged along with the application's debugging info. // can be logged along with the application's debugging info.
func SetLogger(l *log.Logger) { func SetLogger(l stdLogger) {
logger = l logger = l
} }
// New creates new Client.
func New(token string) *Client { func New(token string) *Client {
s := &Client{} s := &Client{}
s.config.token = token s.config.token = token
@ -83,12 +103,12 @@ func (api *Client) SetDebug(debug bool) {
func (api *Client) Debugf(format string, v ...interface{}) { func (api *Client) Debugf(format string, v ...interface{}) {
if api.debug { if api.debug {
logger.Printf(format, v...) logger.Output(2, fmt.Sprintf(format, v...))
} }
} }
func (api *Client) Debugln(v ...interface{}) { func (api *Client) Debugln(v ...interface{}) {
if api.debug { if api.debug {
logger.Println(v...) logger.Output(2, fmt.Sprintln(v...))
} }
} }

View File

@ -200,11 +200,8 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) error {
"token": {api.config.token}, "token": {api.config.token},
} }
_, err := userRequest(ctx, "users.setActive", values, api.debug) _, err := userRequest(ctx, "users.setActive", values, api.debug)
if err != nil {
return err return err
} }
return nil
}
// SetUserPresence changes the currently authenticated user presence // SetUserPresence changes the currently authenticated user presence
func (api *Client) SetUserPresence(presence string) error { func (api *Client) SetUserPresence(presence string) error {
@ -247,8 +244,8 @@ func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityRes
} }
// SetUserPhoto changes the currently authenticated user's profile image // SetUserPhoto changes the currently authenticated user's profile image
func (api *Client) SetUserPhoto(ctx context.Context, image string, params UserSetPhotoParams) error { func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
return api.SetUserPhoto(context.Background(), image, params) return api.SetUserPhotoContext(context.Background(), image, params)
} }
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context // SetUserPhotoContext changes the currently authenticated user's profile image using a custom context

View File

@ -27,6 +27,7 @@ type RTM struct {
IncomingEvents chan RTMEvent IncomingEvents chan RTMEvent
outgoingMessages chan OutgoingMessage outgoingMessages chan OutgoingMessage
killChannel chan bool killChannel chan bool
disconnected chan struct{} // disconnected is closed when Disconnect is invoked, regardless of connection state. Allows for ManagedConnection to not leak.
forcePing chan bool forcePing chan bool
rawEvents chan json.RawMessage rawEvents chan json.RawMessage
wasIntentional bool wasIntentional bool
@ -59,9 +60,14 @@ type RTMOptions struct {
// Disconnect and wait, blocking until a successful disconnection. // Disconnect and wait, blocking until a successful disconnection.
func (rtm *RTM) Disconnect() error { func (rtm *RTM) Disconnect() error {
// this channel is always closed on disconnect. lets the ManagedConnection() function
// properly clean up.
close(rtm.disconnected)
if !rtm.isConnected { if !rtm.isConnected {
return errors.New("Invalid call to Disconnect - Slack API is already disconnected") return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
} }
rtm.killChannel <- true rtm.killChannel <- true
return nil return nil
} }

View File

@ -99,6 +99,15 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
Attempt: boff.attempts, Attempt: boff.attempts,
ErrorObj: err, ErrorObj: err,
}} }}
// check if Disconnect() has been invoked.
select {
case _ = <-rtm.disconnected:
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: true}}
return nil, nil, fmt.Errorf("disconnect received while trying to connect")
default:
}
// get time we should wait before attempting to connect again // get time we should wait before attempting to connect again
dur := boff.Duration() dur := boff.Duration()
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err) rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
@ -124,7 +133,8 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (*Info, *websocket.Conn, error
return nil, nil, err return nil, nil, err
} }
conn, err := websocketProxyDial(url, "http://api.slack.com") // Only use HTTPS for connections to prevent MITM attacks on the connection.
conn, err := websocketProxyDial(url, "https://api.slack.com")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -317,10 +327,13 @@ func (rtm *RTM) handleAck(event json.RawMessage) {
rtm.Debugln(" -> Erroneous 'ack' event:", string(event)) rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
return return
} }
if ack.Ok { if ack.Ok {
rtm.IncomingEvents <- RTMEvent{"ack", ack} rtm.IncomingEvents <- RTMEvent{"ack", ack}
} else { } else if ack.RTMResponse.Error != nil {
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}} rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
} else {
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
} }
} }

View File

@ -32,8 +32,7 @@ func websocketHTTPConnect(proxy, urlString string) (net.Conn, error) {
} }
cc := httputil.NewProxyClientConn(p, nil) cc := httputil.NewProxyClientConn(p, nil)
cc.Do(&req) if _, err := cc.Do(&req); err != nil {
if err != nil && err != httputil.ErrPersistEOF {
return nil, err return nil, err
} }

18
vendor/manifest vendored
View File

@ -302,14 +302,6 @@
"branch": "work", "branch": "work",
"notests": true "notests": true
}, },
{
"importpath": "github.com/matterbridge/slack",
"repository": "https://github.com/matterbridge/slack",
"vcs": "git",
"revision": "1c6e6305bf9c07fc603c9cf28f09ab0517a03120",
"branch": "matterbridge",
"notests": true
},
{ {
"importpath": "github.com/mattermost/platform/einterfaces", "importpath": "github.com/mattermost/platform/einterfaces",
"repository": "https://github.com/mattermost/platform", "repository": "https://github.com/mattermost/platform",
@ -449,6 +441,14 @@
"path": "/i18n", "path": "/i18n",
"notests": true "notests": true
}, },
{
"importpath": "github.com/nlopes/slack",
"repository": "https://github.com/nlopes/slack",
"vcs": "git",
"revision": "107290b5bbaf3e634833346bb4ff389b1c782bc7",
"branch": "master",
"notests": true
},
{ {
"importpath": "github.com/paulrosania/go-charset", "importpath": "github.com/paulrosania/go-charset",
"repository": "https://github.com/paulrosania/go-charset", "repository": "https://github.com/paulrosania/go-charset",
@ -720,7 +720,7 @@
"importpath": "golang.org/x/net/websocket", "importpath": "golang.org/x/net/websocket",
"repository": "https://go.googlesource.com/net", "repository": "https://go.googlesource.com/net",
"vcs": "git", "vcs": "git",
"revision": "6c23252515492caf9b228a9d5cabcdbde29f7f82", "revision": "434ec0c7fe3742c984919a691b2018a6e9694425",
"branch": "master", "branch": "master",
"path": "/websocket", "path": "/websocket",
"notests": true "notests": true