package slack import ( "context" "encoding/json" "errors" "net/url" "strconv" "strings" "time" ) // Conversation is the foundation for IM and BaseGroupConversation type Conversation struct { ID string `json:"id"` Created JSONTime `json:"created"` IsOpen bool `json:"is_open"` LastRead string `json:"last_read,omitempty"` Latest *Message `json:"latest,omitempty"` UnreadCount int `json:"unread_count,omitempty"` UnreadCountDisplay int `json:"unread_count_display,omitempty"` IsGroup bool `json:"is_group"` IsShared bool `json:"is_shared"` IsIM bool `json:"is_im"` IsExtShared bool `json:"is_ext_shared"` IsOrgShared bool `json:"is_org_shared"` IsGlobalShared bool `json:"is_global_shared"` IsPendingExtShared bool `json:"is_pending_ext_shared"` IsPrivate bool `json:"is_private"` IsReadOnly bool `json:"is_read_only"` IsMpIM bool `json:"is_mpim"` IsUserDeleted bool `json:"is_user_deleted"` Unlinked int `json:"unlinked"` NameNormalized string `json:"name_normalized"` NumMembers int `json:"num_members"` Priority float64 `json:"priority"` User string `json:"user"` ConnectedTeamIDs []string `json:"connected_team_ids,omitempty"` SharedTeamIDs []string `json:"shared_team_ids,omitempty"` InternalTeamIDs []string `json:"internal_team_ids,omitempty"` ContextTeamID string `json:"context_team_id,omitempty"` ConversationHostID string `json:"conversation_host_id,omitempty"` PreviousNames []string `json:"previous_names,omitempty"` PendingShared []string `json:"pending_shared,omitempty"` } // GroupConversation is the foundation for Group and Channel type GroupConversation struct { Conversation Name string `json:"name"` Creator string `json:"creator"` IsArchived bool `json:"is_archived"` Members []string `json:"members"` Topic Topic `json:"topic"` Purpose Purpose `json:"purpose"` } // Topic contains information about the topic type Topic struct { Value string `json:"value"` Creator string `json:"creator"` LastSet JSONTime `json:"last_set"` } // Purpose contains information about the purpose type Purpose struct { Value string `json:"value"` Creator string `json:"creator"` LastSet JSONTime `json:"last_set"` } // Properties contains additional fields that appear based on the context of the conversation type Properties struct { Canvas Canvas `json:"canvas"` PostingRestrictedTo RestrictedTo `json:"posting_restricted_to"` Tabs []Tab `json:"tabs"` ThreadsRestrictedTo RestrictedTo `json:"threads_restricted_to"` RecordChannel RecordChannel `json:"record_channel"` } type RestrictedTo struct { Type []string `json:"type"` User []string `json:"user"` } type Tab struct { ID string `json:"id"` Label string `json:"label"` Type string `json:"type"` } type Canvas struct { FileId string `json:"file_id"` IsEmpty bool `json:"is_empty"` QuipThreadId string `json:"quip_thread_id"` } type RecordChannel struct { RecordID string `json:"record_id"` RecordType string `json:"record_type"` RecordLabel string `json:"record_label"` RecordLabelPlural string `json:"record_label_plural"` } type GetUsersInConversationParameters struct { ChannelID string Cursor string Limit int } type GetConversationsForUserParameters struct { UserID string Cursor string Types []string Limit int ExcludeArchived bool TeamID string } type responseMetaData struct { NextCursor string `json:"next_cursor"` } // GetUsersInConversation returns the list of users in a conversation. // For more details, see GetUsersInConversationContext documentation. func (api *Client) GetUsersInConversation(params *GetUsersInConversationParameters) ([]string, string, error) { return api.GetUsersInConversationContext(context.Background(), params) } // GetUsersInConversationContext returns the list of users in a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.members func (api *Client) GetUsersInConversationContext(ctx context.Context, params *GetUsersInConversationParameters) ([]string, string, error) { values := url.Values{ "token": {api.token}, "channel": {params.ChannelID}, } if params.Cursor != "" { values.Add("cursor", params.Cursor) } if params.Limit != 0 { values.Add("limit", strconv.Itoa(params.Limit)) } response := struct { Members []string `json:"members"` ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} err := api.postMethod(ctx, "conversations.members", values, &response) if err != nil { return nil, "", err } if err := response.Err(); err != nil { return nil, "", err } return response.Members, response.ResponseMetaData.NextCursor, nil } // GetConversationsForUser returns the list conversations for a given user. // For more details, see GetConversationsForUserContext documentation. func (api *Client) GetConversationsForUser(params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) { return api.GetConversationsForUserContext(context.Background(), params) } // GetConversationsForUserContext returns the list conversations for a given user with a custom context // Slack API docs: https://api.slack.com/methods/users.conversations func (api *Client) GetConversationsForUserContext(ctx context.Context, params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) { values := url.Values{ "token": {api.token}, } if params.UserID != "" { values.Add("user", params.UserID) } if params.Cursor != "" { values.Add("cursor", params.Cursor) } if params.Limit != 0 { values.Add("limit", strconv.Itoa(params.Limit)) } if params.Types != nil { values.Add("types", strings.Join(params.Types, ",")) } 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"` SlackResponse }{} err = api.postMethod(ctx, "users.conversations", values, &response) if err != nil { return nil, "", err } return response.Channels, response.ResponseMetaData.NextCursor, response.Err() } // ArchiveConversation archives a conversation. // For more details, see ArchiveConversationContext documentation. func (api *Client) ArchiveConversation(channelID string) error { return api.ArchiveConversationContext(context.Background(), channelID) } // ArchiveConversationContext archives a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.archive func (api *Client) ArchiveConversationContext(ctx context.Context, channelID string) error { values := url.Values{ "token": {api.token}, "channel": {channelID}, } response := SlackResponse{} err := api.postMethod(ctx, "conversations.archive", values, &response) if err != nil { return err } return response.Err() } // UnArchiveConversation reverses conversation archival. // For more details, see UnArchiveConversationContext documentation. func (api *Client) UnArchiveConversation(channelID string) error { return api.UnArchiveConversationContext(context.Background(), channelID) } // UnArchiveConversationContext reverses conversation archival with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.unarchive func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID string) error { values := url.Values{ "token": {api.token}, "channel": {channelID}, } response := SlackResponse{} err := api.postMethod(ctx, "conversations.unarchive", values, &response) if err != nil { return err } return response.Err() } // SetTopicOfConversation sets the topic for a conversation. // For more details, see SetTopicOfConversationContext documentation. func (api *Client) SetTopicOfConversation(channelID, topic string) (*Channel, error) { return api.SetTopicOfConversationContext(context.Background(), channelID, topic) } // SetTopicOfConversationContext sets the topic for a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.setTopic func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, topic string) (*Channel, error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, "topic": {topic}, } response := struct { SlackResponse Channel *Channel `json:"channel"` }{} err := api.postMethod(ctx, "conversations.setTopic", values, &response) if err != nil { return nil, err } return response.Channel, response.Err() } // SetPurposeOfConversation sets the purpose for a conversation. // For more details, see SetPurposeOfConversationContext documentation. func (api *Client) SetPurposeOfConversation(channelID, purpose string) (*Channel, error) { return api.SetPurposeOfConversationContext(context.Background(), channelID, purpose) } // SetPurposeOfConversationContext sets the purpose for a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.setPurpose func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelID, purpose string) (*Channel, error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, "purpose": {purpose}, } response := struct { SlackResponse Channel *Channel `json:"channel"` }{} err := api.postMethod(ctx, "conversations.setPurpose", values, &response) if err != nil { return nil, err } return response.Channel, response.Err() } // RenameConversation renames a conversation. // For more details, see RenameConversationContext documentation. func (api *Client) RenameConversation(channelID, channelName string) (*Channel, error) { return api.RenameConversationContext(context.Background(), channelID, channelName) } // RenameConversationContext renames a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.rename func (api *Client) RenameConversationContext(ctx context.Context, channelID, channelName string) (*Channel, error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, "name": {channelName}, } response := struct { SlackResponse Channel *Channel `json:"channel"` }{} err := api.postMethod(ctx, "conversations.rename", values, &response) if err != nil { return nil, err } return response.Channel, response.Err() } // InviteUsersToConversation invites users to a channel. // For more details, see InviteUsersToConversation documentation. func (api *Client) InviteUsersToConversation(channelID string, users ...string) (*Channel, error) { return api.InviteUsersToConversationContext(context.Background(), channelID, users...) } // InviteUsersToConversationContext invites users to a channel with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.invite func (api *Client) InviteUsersToConversationContext(ctx context.Context, channelID string, users ...string) (*Channel, error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, "users": {strings.Join(users, ",")}, } response := struct { SlackResponse Channel *Channel `json:"channel"` }{} err := api.postMethod(ctx, "conversations.invite", values, &response) if err != nil { return nil, err } return response.Channel, response.Err() } /********************************************************************************** The following functions are for inviting users to a channel but setting the `force` parameter to true. We have added this so that we don't break the existing API. IMPORTANT: If we ever get here for _another_ parameter, we should consider refactoring this to be more flexible. */ // ForceInviteUsersToConversation invites users to a channel but sets the `force` // parameter to true. // // For more details, see ForceInviteUsersToConversationContext documentation. func (api *Client) ForceInviteUsersToConversation(channelID string, users ...string) (*Channel, error) { return api.ForceInviteUsersToConversationContext(context.Background(), channelID, users...) } // ForceInviteUsersToConversationContext invites users to a channel with a custom context // while setting the `force` argument to true. // // Slack API docs: https://api.slack.com/methods/conversations.invite func (api *Client) ForceInviteUsersToConversationContext(ctx context.Context, channelID string, users ...string) (*Channel, error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, "users": {strings.Join(users, ",")}, "force": {"true"}, } response := struct { SlackResponse Channel *Channel `json:"channel"` }{} err := api.postMethod(ctx, "conversations.invite", values, &response) if err != nil { return nil, err } return response.Channel, response.Err() } // InviteSharedEmailsToConversation invites users to a shared channels by email. // For more details, see InviteSharedToConversationContext documentation. func (api *Client) InviteSharedEmailsToConversation(channelID string, emails ...string) (string, bool, error) { return api.InviteSharedToConversationContext(context.Background(), InviteSharedToConversationParams{ ChannelID: channelID, Emails: emails, }) } // InviteSharedEmailsToConversationContext invites users to a shared channels by email using context. // For more details, see InviteSharedToConversationContext documentation. func (api *Client) InviteSharedEmailsToConversationContext(ctx context.Context, channelID string, emails ...string) (string, bool, error) { return api.InviteSharedToConversationContext(ctx, InviteSharedToConversationParams{ ChannelID: channelID, Emails: emails, }) } // InviteSharedUserIDsToConversation invites users to a shared channels by user id. // For more details, see InviteSharedToConversationContext documentation. func (api *Client) InviteSharedUserIDsToConversation(channelID string, userIDs ...string) (string, bool, error) { return api.InviteSharedToConversationContext(context.Background(), InviteSharedToConversationParams{ ChannelID: channelID, UserIDs: userIDs, }) } // InviteSharedUserIDsToConversationContext invites users to a shared channels by user id with context. // For more details, see InviteSharedToConversationContext documentation. func (api *Client) InviteSharedUserIDsToConversationContext(ctx context.Context, channelID string, userIDs ...string) (string, bool, error) { return api.InviteSharedToConversationContext(ctx, InviteSharedToConversationParams{ ChannelID: channelID, UserIDs: userIDs, }) } // InviteSharedToConversationParams defines the parameters for the InviteSharedToConversation and InviteSharedToConversationContext functions. type InviteSharedToConversationParams struct { ChannelID string Emails []string UserIDs []string ExternalLimited *bool } // InviteSharedToConversation invites emails or userIDs to a channel. // For more details, see InviteSharedToConversationContext documentation. func (api *Client) InviteSharedToConversation(params InviteSharedToConversationParams) (string, bool, error) { return api.InviteSharedToConversationContext(context.Background(), params) } // InviteSharedToConversationContext invites emails or userIDs to a channel with a custom context. // This is a helper function for InviteSharedEmailsToConversation and InviteSharedUserIDsToConversation. // It accepts either emails or userIDs, but not both. // Slack API docs: https://api.slack.com/methods/conversations.inviteShared func (api *Client) InviteSharedToConversationContext(ctx context.Context, params InviteSharedToConversationParams) (string, bool, error) { values := url.Values{ "token": {api.token}, "channel": {params.ChannelID}, } if len(params.Emails) > 0 { values.Add("emails", strings.Join(params.Emails, ",")) } else if len(params.UserIDs) > 0 { values.Add("user_ids", strings.Join(params.UserIDs, ",")) } if params.ExternalLimited != nil { values.Add("external_limited", strconv.FormatBool(*params.ExternalLimited)) } response := struct { SlackResponse InviteID string `json:"invite_id"` IsLegacySharedChannel bool `json:"is_legacy_shared_channel"` }{} err := api.postMethod(ctx, "conversations.inviteShared", values, &response) if err != nil { return "", false, err } return response.InviteID, response.IsLegacySharedChannel, response.Err() } // KickUserFromConversation removes a user from a conversation. // For more details, see KickUserFromConversationContext documentation. func (api *Client) KickUserFromConversation(channelID string, user string) error { return api.KickUserFromConversationContext(context.Background(), channelID, user) } // KickUserFromConversationContext removes a user from a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.kick func (api *Client) KickUserFromConversationContext(ctx context.Context, channelID string, user string) error { values := url.Values{ "token": {api.token}, "channel": {channelID}, "user": {user}, } response := KickUserFromConversationSlackResponse{} err := api.postMethod(ctx, "conversations.kick", values, &response) if err != nil { return err } return response.Err() } // CloseConversation closes a direct message or multi-person direct message. // For more details, see CloseConversationContext documentation. func (api *Client) CloseConversation(channelID string) (noOp bool, alreadyClosed bool, err error) { return api.CloseConversationContext(context.Background(), channelID) } // CloseConversationContext closes a direct message or multi-person direct message with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.close func (api *Client) CloseConversationContext(ctx context.Context, channelID string) (noOp bool, alreadyClosed bool, err error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, } response := struct { SlackResponse NoOp bool `json:"no_op"` AlreadyClosed bool `json:"already_closed"` }{} err = api.postMethod(ctx, "conversations.close", values, &response) if err != nil { return false, false, err } 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. // For more details, see CreateConversationContext documentation. 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. // Slack API docs: https://api.slack.com/methods/conversations.create func (api *Client) CreateConversationContext(ctx context.Context, params CreateConversationParams) (*Channel, error) { values := url.Values{ "token": {api.token}, "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 { return nil, err } 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. // For more details, see GetConversationInfoContext documentation. func (api *Client) GetConversationInfo(input *GetConversationInfoInput) (*Channel, error) { return api.GetConversationInfoContext(context.Background(), input) } // GetConversationInfoContext retrieves information about a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.info 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": {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 { return nil, err } return &response.Channel, response.Err() } // LeaveConversation leaves a conversation. // For more details, see LeaveConversationContext documentation. func (api *Client) LeaveConversation(channelID string) (bool, error) { return api.LeaveConversationContext(context.Background(), channelID) } // LeaveConversationContext leaves a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.leave func (api *Client) LeaveConversationContext(ctx context.Context, channelID string) (bool, error) { values := url.Values{ "token": {api.token}, "channel": {channelID}, } response, err := api.channelRequest(ctx, "conversations.leave", values) if err != nil { return false, err } return response.NotInChannel, err } type GetConversationRepliesParameters struct { 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. // For more details, see GetConversationRepliesContext documentation. func (api *Client) GetConversationReplies(params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) { return api.GetConversationRepliesContext(context.Background(), params) } // GetConversationRepliesContext retrieves a thread of messages posted to a conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.replies func (api *Client) GetConversationRepliesContext(ctx context.Context, params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) { values := url.Values{ "token": {api.token}, "channel": {params.ChannelID}, "ts": {params.Timestamp}, } if params.Cursor != "" { values.Add("cursor", params.Cursor) } if params.Latest != "" { values.Add("latest", params.Latest) } if params.Limit != 0 { values.Add("limit", strconv.Itoa(params.Limit)) } if params.Oldest != "" { values.Add("oldest", params.Oldest) } if params.Inclusive { values.Add("inclusive", "1") } 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"` ResponseMetaData struct { NextCursor string `json:"next_cursor"` } `json:"response_metadata"` Messages []Message `json:"messages"` }{} err = api.postMethod(ctx, "conversations.replies", values, &response) if err != nil { return nil, false, "", err } return response.Messages, response.HasMore, response.ResponseMetaData.NextCursor, response.Err() } type GetConversationsParameters struct { Cursor string ExcludeArchived bool Limit int Types []string TeamID string } // GetConversationsOption options for the GetAllConversationsContext method call. type GetConversationsOption func(*ConversationPagination) // GetConversationsOptionLimit limit the number of conversations returned func GetConversationsOptionLimit(n int) GetConversationsOption { return func(p *ConversationPagination) { p.limit = n } } // GetConversationsOptionExcludeArchived exclude archived conversations func GetConversationsOptionExcludeArchived(exclude bool) GetConversationsOption { return func(p *ConversationPagination) { p.excludeArchived = exclude } } // GetConversationsOptionTypes filter conversations by type func GetConversationsOptionTypes(types []string) GetConversationsOption { return func(p *ConversationPagination) { p.types = types } } // GetConversationsOptionTeamID include team Id func GetConversationsOptionTeamID(teamId string) GetConversationsOption { return func(p *ConversationPagination) { p.teamId = teamId } } func newConversationPagination(c *Client, options ...GetConversationsOption) (cp ConversationPagination) { cp = ConversationPagination{ c: c, limit: 200, // per slack api documentation. } for _, opt := range options { opt(&cp) } return cp } // ConversationPagination allows for paginating over the conversations type ConversationPagination struct { Conversations []Channel limit int excludeArchived bool types []string teamId string previousResp *ResponseMetadata c *Client } // Done checks if the pagination has completed func (ConversationPagination) Done(err error) bool { return errors.Is(err, errPaginationComplete) } // Failure checks if pagination failed. func (t ConversationPagination) Failure(err error) error { if t.Done(err) { return nil } return err } func (t ConversationPagination) Next(ctx context.Context) (_ ConversationPagination, err error) { if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") { return t, errPaginationComplete } t.previousResp = t.previousResp.initialize() values := url.Values{ "token": {t.c.token}, "limit": {strconv.Itoa(t.limit)}, "cursor": {t.previousResp.Cursor}, } if t.excludeArchived { values.Add("exclude_archived", strconv.FormatBool(t.excludeArchived)) } if t.types != nil { values.Add("types", strings.Join(t.types, ",")) } if t.teamId != "" { values.Add("team_id", t.teamId) } response := struct { Channels []Channel `json:"channels"` ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} err = t.c.postMethod(ctx, "conversations.list", values, &response) if err != nil { return t, err } if err := response.Err(); err != nil { return t, err } t.c.Debugf("GetAllConversationsContext: got %d conversations; cursor %s", len(response.Channels), response.ResponseMetaData.NextCursor) t.Conversations = response.Channels t.previousResp = &ResponseMetadata{Cursor: response.ResponseMetaData.NextCursor} return t, nil } // GetConversationsPaginated fetches conversations in a paginated fashion, see GetAllConversationsContext for usage. func (api *Client) GetConversationsPaginated(options ...GetConversationsOption) ConversationPagination { return newConversationPagination(api, options...) } // GetAllConversations returns the list of all conversations, handling pagination and rate limiting func (api *Client) GetAllConversations(options ...GetConversationsOption) (results []Channel, err error) { return api.GetAllConversationsContext(context.Background(), options...) } // GetAllConversationsContext returns the list of all conversations with a custom context, handling pagination and rate limiting func (api *Client) GetAllConversationsContext(ctx context.Context, options ...GetConversationsOption) (results []Channel, err error) { results = []Channel{} p := api.GetConversationsPaginated(options...) for err == nil { p, err = p.Next(ctx) if err == nil { results = append(results, p.Conversations...) } 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) } // GetConversations returns the list of channels in a Slack team. // For more details, see GetConversationsContext documentation. func (api *Client) GetConversations(params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) { return api.GetConversationsContext(context.Background(), params) } // GetConversationsContext returns the list of channels in a Slack team with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.list func (api *Client) GetConversationsContext(ctx context.Context, params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) { values := url.Values{ "token": {api.token}, } if params.Cursor != "" { values.Add("cursor", params.Cursor) } if params.Limit != 0 { values.Add("limit", strconv.Itoa(params.Limit)) } if params.Types != nil { values.Add("types", strings.Join(params.Types, ",")) } if params.ExcludeArchived { values.Add("exclude_archived", strconv.FormatBool(params.ExcludeArchived)) } if params.TeamID != "" { values.Add("team_id", params.TeamID) } response := struct { Channels []Channel `json:"channels"` ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} err = api.postMethod(ctx, "conversations.list", values, &response) if err != nil { return nil, "", err } return response.Channels, response.ResponseMetaData.NextCursor, response.Err() } type OpenConversationParameters struct { ChannelID string ReturnIM bool Users []string } // OpenConversation opens or resumes a direct message or multi-person direct message. // For more details, see OpenConversationContext documentation. func (api *Client) OpenConversation(params *OpenConversationParameters) (*Channel, bool, bool, error) { return api.OpenConversationContext(context.Background(), params) } // OpenConversationContext opens or resumes a direct message or multi-person direct message with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.open func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConversationParameters) (*Channel, bool, bool, error) { values := url.Values{ "token": {api.token}, "return_im": {strconv.FormatBool(params.ReturnIM)}, } if params.ChannelID != "" { values.Add("channel", params.ChannelID) } if params.Users != nil { values.Add("users", strings.Join(params.Users, ",")) } response := struct { Channel *Channel `json:"channel"` NoOp bool `json:"no_op"` AlreadyOpen bool `json:"already_open"` SlackResponse }{} err := api.postMethod(ctx, "conversations.open", values, &response) if err != nil { return nil, false, false, err } return response.Channel, response.NoOp, response.AlreadyOpen, response.Err() } // JoinConversation joins an existing conversation. // For more details, see JoinConversationContext documentation. func (api *Client) JoinConversation(channelID string) (*Channel, string, []string, error) { return api.JoinConversationContext(context.Background(), channelID) } // JoinConversationContext joins an existing conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.join func (api *Client) JoinConversationContext(ctx context.Context, channelID string) (*Channel, string, []string, error) { values := url.Values{"token": {api.token}, "channel": {channelID}} response := struct { Channel *Channel `json:"channel"` Warning string `json:"warning"` ResponseMetaData *struct { Warnings []string `json:"warnings"` } `json:"response_metadata"` SlackResponse }{} err := api.postMethod(ctx, "conversations.join", values, &response) if err != nil { return nil, "", nil, err } if response.Err() != nil { return nil, "", nil, response.Err() } var warnings []string if response.ResponseMetaData != nil { warnings = response.ResponseMetaData.Warnings } return response.Channel, response.Warning, warnings, nil } type GetConversationHistoryParameters struct { ChannelID string Cursor string Inclusive bool Latest string Limit int Oldest string IncludeAllMetadata bool } type GetConversationHistoryResponse struct { SlackResponse HasMore bool `json:"has_more"` PinCount int `json:"pin_count"` Latest string `json:"latest"` ResponseMetaData struct { NextCursor string `json:"next_cursor"` } `json:"response_metadata"` Messages []Message `json:"messages"` } // GetConversationHistory retrieves the message history from the specified conversation. // For more details, see GetConversationHistoryContext documentation. func (api *Client) GetConversationHistory(params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) { return api.GetConversationHistoryContext(context.Background(), params) } // GetConversationHistoryContext retrieves the message history from the specified conversation with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.history func (api *Client) GetConversationHistoryContext(ctx context.Context, params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) { values := url.Values{"token": {api.token}, "channel": {params.ChannelID}} if params.Cursor != "" { values.Add("cursor", params.Cursor) } if params.Inclusive { values.Add("inclusive", "1") } else { values.Add("inclusive", "0") } if params.Latest != "" { values.Add("latest", params.Latest) } if params.Limit != 0 { values.Add("limit", strconv.Itoa(params.Limit)) } 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{} err := api.postMethod(ctx, "conversations.history", values, &response) if err != nil { return nil, err } return &response, response.Err() } // MarkConversation sets the read mark of a conversation to a specific point. // For more details, see MarkConversationContext documentation. func (api *Client) MarkConversation(channel, ts string) (err error) { return api.MarkConversationContext(context.Background(), channel, ts) } // MarkConversationContext sets the read mark of a conversation to a specific point with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.mark func (api *Client) MarkConversationContext(ctx context.Context, channel, ts string) error { values := url.Values{ "token": {api.token}, "channel": {channel}, "ts": {ts}, } response := &SlackResponse{} err := api.postMethod(ctx, "conversations.mark", values, response) if err != nil { return err } return response.Err() } // createChannelCanvasParams contains arguments for CreateChannelCanvas method call. type createChannelCanvasParams struct { title string documentContent *DocumentContent } // CreateChannelCanvasOption options for the CreateChannelCanvas method call. type CreateChannelCanvasOption func(*createChannelCanvasParams) // CreateChannelCanvasOptionTitle sets the title of the canvas. func CreateChannelCanvasOptionTitle(title string) CreateChannelCanvasOption { return func(params *createChannelCanvasParams) { params.title = title } } // CreateChannelCanvasOptionDocumentContent sets the document content of the canvas. func CreateChannelCanvasOptionDocumentContent(documentContent DocumentContent) CreateChannelCanvasOption { return func(params *createChannelCanvasParams) { params.documentContent = &documentContent } } // CreateChannelCanvas creates a new canvas in a channel. // For more details, see CreateChannelCanvasContext documentation. func (api *Client) CreateChannelCanvas(channel string, documentContent DocumentContent, options ...CreateChannelCanvasOption) (string, error) { return api.CreateChannelCanvasContext(context.Background(), channel, documentContent, options...) } // CreateChannelCanvasContext creates a new canvas in a channel with a custom context. // Slack API docs: https://api.slack.com/methods/conversations.canvases.create func (api *Client) CreateChannelCanvasContext(ctx context.Context, channel string, documentContent DocumentContent, options ...CreateChannelCanvasOption) (string, error) { params := createChannelCanvasParams{ documentContent: &documentContent, } for _, opt := range options { opt(¶ms) } values := url.Values{ "token": {api.token}, "channel_id": {channel}, } if params.title != "" { values.Add("title", params.title) } if params.documentContent != nil && params.documentContent.Type != "" { documentContentJSON, err := json.Marshal(params.documentContent) if err != nil { return "", err } values.Add("document_content", string(documentContentJSON)) } response := struct { SlackResponse CanvasID string `json:"canvas_id"` }{} err := api.postMethod(ctx, "conversations.canvases.create", values, &response) if err != nil { return "", err } return response.CanvasID, response.Err() }