Bump Go dependencies in both backend/ and backend/_example/memory_store.
Notable updates:
- github.com/go-pkgz/lgr v0.12.1 -> v0.12.3
- github.com/klauspost/compress v1.18.2 -> v1.18.5
- github.com/PuerkitoBio/goquery v1.11.0 -> v1.12.0
- github.com/montanaflynn/stats v0.7.1 -> v0.9.0
- github.com/redis/go-redis/v9 v9.17.2 -> v9.18.0
- github.com/slack-go/slack v0.17.3 -> v0.21.1
- go.mongodb.org/mongo-driver v1.17.6 -> v1.17.9
- golang.org/x/crypto v0.48.0 -> v0.50.0
- golang.org/x/net v0.49.0 -> v0.53.0
- golang.org/x/image v0.36.0 -> v0.39.0
- golang.org/x/sys v0.41.0 -> v0.43.0
- golang.org/x/{oauth2,sync,text} minor bumps
Key markdown/sanitisation libs (bluemonday v1.0.27,
alecthomas/chroma/v2 v2.23.1, russross/blackfriday/v2 v2.1.0,
Depado/bfchroma/v2 v2.0.0) are already at the latest available
versions and were not bumped.
Verified the Chroma span-class allowlist regex in
backend/app/store/comment.go:128-131 is still fully in sync with
chroma/v2 types.go StandardTypes map (86 classes, byte-equal after
sorting). The inline comment references commit c263f6f which is
stale (Chroma is at v2 now), but the class list content is current.
Ran `go mod tidy` + `go mod vendor` + full race test suite on both
modules. All green. Added a reminder in CLAUDE.md that updating
backend/ Go modules also requires `go mod tidy` in
backend/_example/memory_store since the example module uses a
local replace directive and inherits indirect deps from the main
module.
1105 lines
37 KiB
Go
1105 lines
37 KiB
Go
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()
|
|
}
|