GithubHelp home page GithubHelp logo

Comments (1)

TopherGopher avatar TopherGopher commented on June 11, 2024

Can confirm this issue is still present. If you add a block, then the update works great, as intended, but you can't update using text.

I've done my best to recreate some reproducible code - but you may need to tweak it a little:

package chatbot

import (
	"fmt"
	"github.com/slack-go/slack"
	"github.com/stretchr/testify/assert"
	"testing"
)

var slackClient *slack.Client
// ErrChannelNotFound can be used with errors.Is to determine if the channel
// doesn't exist
var ErrChannelNotFound = errors.New("channel_not_found")

func setup(t *testing.T) {
	t.Helper()
	slackClient = slack.New(slackBotToken,
		slack.OptionAppLevelToken(slackAppToken))
}

// ChatMessage is an abstraction, designed to make chat input/output more accessible from other components
// You should instantiate this by using NewChatMessage()
type ChatMessage struct {
	ShortMessage    string             `bson:"shortMessage" json:"shortMessage"`
	ChannelName     string             `bson:"channelName" json:"channelName"`
	SuccessResponse string             `bson:"successResponse" json:"successResponse"`
	FailureResponse string             `bson:"failureResponse" json:"failureResponse"`
	// AtMentionUser is the user to mention in the message, e.g. topher.sterling
	//     - there should not be an @prefix
	AtMentionUser string                 `bson:"atMentionUser" json:"atMentionUser"`
	Metadata      map[string]interface{} `bson:"metadata" json:"metadata"`
	Blocks        []CFChatbotBlock       `bson:"-" json:"-"`
}

func NewChatMessage(shortMessage, channelName string) *ChatMessage {
	c := &ChatMessage{
		ChannelName:  channelName,
		ShortMessage: shortMessage,
	}
	if len(shortMessage) > 0 {
		c.AddTitle(shortMessage)
	}
	return c
}

type CFChatbotBlock struct {
	slack.Block
}

// slackBlocks converts the ChatMessage's blocks to a slice of slack.Blocks
func (c *ChatMessage) slackBlocks() []slack.Block {
	blocks := make([]slack.Block, len(c.Blocks))
	for i := range c.Blocks {
		blocks[i] = c.Blocks[i].Block
	}
	if len(c.AtMentionUser) > 0 {
		// We're supposed to be able to reply with a user ID - but we can't. So... we'll have to get the user info
		// userInfo, err := bot.SlackClient.GetUserInfo(c.AtMentionUser)
		// if err != nil {
		// 	bot.log.WithError(err).Error("Could not get user info")
		// } else {
		// 	bot.log.WithField("user", userInfo).Info("Found user info")
		// }
		// Prepend the @mention to the message
		c.Blocks = append([]CFChatbotBlock{
			{Block: slack.NewSectionBlock(
				slack.NewTextBlockObject(
					slack.MarkdownType,
					fmt.Sprintf("FYI <@%s>?", c.AtMentionUser),
					false, false,
				),
				nil,
				nil,
			),
			},
		}, c.Blocks...)
	}
	return blocks
}

// Send Posts the message to Slack and saves it to the DB.
// It returns an error if the message could not be sent or saved.
func (c *ChatMessage) Send() (msgID string, err error) {
	_, msgID, err = slackClient.PostMessage(c.ChannelName,
		slack.MsgOptionEnableLinkUnfurl(),
		slack.MsgOptionBlocks(
			c.slackBlocks()...,
		),
		slack.MsgOptionMetadata(slack.SlackMetadata{
			EventType:    "cf-emitting-chat-message",
			EventPayload: c.Metadata,
		}),
		slack.MsgOptionUsername(fmt.Sprintf("CF %s Chat Bot", "DEV")),
	)
	if err != nil {
		if IsChannelNotFound(err) {
			err = ErrChannelNotFound
		}
		return msgID, fmt.Errorf("could not send message to Slack channel '%s': %w", c.ChannelName, err)
	}
	return msgID, nil
}

// CompleteInteraction allows you to mark a slack interaction as "completed"
func CompleteInteraction(channelName, msgId, result, completedByUser string) (err error) {
	channelID, _, err := ChannelNameToID(channelName)
	if err != nil {
		return fmt.Errorf("unable to complete slack interaction out of band: %w", err)
	}
	text := fmt.Sprintf("Thank you for submitting your answer of '%s' %s! (We realize this is a thread - slack does *not* like updating the original message without a user initiated action).", result, completedByUser)
	// Note -  will post a thread reply
	_, _, _, err = slackClient.UpdateMessage(channelID,
		msgId,
		slack.MsgOptionText(text, false),
		slack.MsgOptionAsUser(false),
		slack.MsgOptionUsername(fmt.Sprintf("CF %s Chat Bot", consts.ActualCFEnv)))
	if err != nil {
		return fmt.Errorf("unable to complete slack interaction out of band: %w", err)
	}
	return nil
}

// ChannelNameToID converts a channel name to a channel ID - a note that this
// will call the Slack API to get the list of channels. Caching has been removed for this example.
func ChannelNameToID(channelName string) (channelID string, isChannelMember bool, err error) {
	// Remove the # if it's there
	channelName = strings.TrimPrefix(channelName, "#")

        // There was a cache here - removing to make it easier

	channelNamesToIDs, err := ListChannels()
	if err != nil {
		return "", false, err
	}
	channelID, ok := channelNamesToIDs[channelName]
	if !ok {
		return "", false, ErrChannelNotFound
	}
	return channelID, false, nil
}

// ListChannels returns a map of channel names to channel IDs for all public and
// private channels. These channels are not auto-joined. Caching has been removed for this example.
func ListChannels() (channelNamesToIDs map[string]string, err error) {
	channels, _, err := slackClient.GetConversations(&slack.GetConversationsParameters{
		ExcludeArchived: true,
		Limit:           1000,
		Types:           []string{"public_channel", "private_channel"},
	})
	if err != nil {
		return nil, err
	}
	channelNamesToIDs = make(map[string]string)
	for i := range channels {
		channelNamesToIDs[channels[i].Name] = channels[i].ID
	}
	return channelNamesToIDs, nil
}

// TestCompleteInteraction is a test which should first send a message, then
// subsequently update that message. We are able to get this test to sort of work
// by updating the slack.MsgOptionTS(msgId) - but that's not ideal as it doesn't allow us to
// mark the interaction as complete.
// We also can send using ephemeral messages, but that's not ideal either, as we want the message
// to persist until the interaction is either completed from with-in Slack or the
// external system completes the interaction.
func TestCompleteInteraction(t *testing.T) {
	is := assert.New(t)
	setup(t)
	msg := NewChatMessage("My initial message", "cf-chatbot-test-")
	// msg.AddControl("How are you?", formtypes.ControlTypeButton, "Good", "Bad")
	msgId, err := msg.Send()
	is.NoError(err, "Could not send chat message")
	is.NoError(CompleteInteraction(msg.ChannelName, msgId, "Good", "Topher Sterling"))
}

Note that changing the UpdateMessage code to use blocks rather than slack.MsgOptionText(text, false) fixes the issue and the update succeeds. If you swap the CompleteInteration/UpdateMessage call for this, then you'll see it work:

_, _, _, err = slackClient.UpdateMessage(channelID,
		msgId,
		slack.MsgOptionBlocks(slack.NewSectionBlock(
			slack.NewTextBlockObject(
				"mrkdwn", text, false, false),
			nil, nil)),
		slack.MsgOptionAsUser(false),
		slack.MsgOptionUsername(fmt.Sprintf("CF %s Chat Bot", consts.ActualCFEnv)))

So perhaps the SDK could convert the MsgOptionText to a single MsgOptionBlocks?

from slack.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.