Add reply to threads functionality
This commit is contained in:
parent
56cad01ce0
commit
946a8ca086
@ -182,7 +182,10 @@ func (c *Chat) AddMessage(message Message) {
|
||||
c.Messages[message.ID] = message
|
||||
}
|
||||
|
||||
// AddReply adds a single reply to a parent thread, it also sets
|
||||
// the thread separator
|
||||
func (c *Chat) AddReply(parentID string, message Message) {
|
||||
message.Thread = " "
|
||||
c.Messages[parentID].Messages[message.ID] = message
|
||||
}
|
||||
|
||||
@ -267,6 +270,12 @@ func (c *Chat) MessageToCells(msg Message) []termui.Cell {
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
|
||||
// Thread
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
msg.GetThread(),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
|
||||
// Name
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
msg.GetName(),
|
||||
|
@ -25,10 +25,12 @@ type Message struct {
|
||||
Messages map[string]Message
|
||||
|
||||
Time time.Time
|
||||
Thread string
|
||||
Name string
|
||||
Content string
|
||||
|
||||
StyleTime string
|
||||
StyleThread string
|
||||
StyleName string
|
||||
StyleText string
|
||||
|
||||
@ -43,6 +45,13 @@ func (m Message) GetTime() string {
|
||||
)
|
||||
}
|
||||
|
||||
func (m Message) GetThread() string {
|
||||
return fmt.Sprintf("[%s](%s)",
|
||||
m.Thread,
|
||||
m.StyleThread,
|
||||
)
|
||||
}
|
||||
|
||||
func (m Message) GetName() string {
|
||||
return fmt.Sprintf("[<%s>](%s) ",
|
||||
m.Name,
|
||||
|
@ -128,6 +128,7 @@ func getDefaultConfig() Config {
|
||||
Message: Message{
|
||||
Time: "",
|
||||
TimeFormat: "15:04",
|
||||
Thread: "fg-bold",
|
||||
Name: "",
|
||||
Text: "",
|
||||
},
|
||||
|
@ -18,6 +18,7 @@ type View struct {
|
||||
type Message struct {
|
||||
Time string `json:"time"`
|
||||
Name string `json:"name"`
|
||||
Thread string `json:"thread"`
|
||||
Text string `json:"text"`
|
||||
TimeFormat string `json:"time_format"`
|
||||
}
|
||||
|
@ -121,6 +121,8 @@ func messageHandler(ctx *context.AppContext) {
|
||||
// Add message to the selected channel
|
||||
if ev.Channel == ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID {
|
||||
|
||||
// When timestamp isn't set this is a thread reply,
|
||||
// handle as such
|
||||
if ev.ThreadTimestamp != "" {
|
||||
ctx.View.Chat.AddReply(ev.ThreadTimestamp, msg)
|
||||
} else {
|
||||
@ -241,7 +243,19 @@ func actionSend(ctx *context.AppContext) {
|
||||
ctx.View.Input.Clear()
|
||||
ctx.View.Refresh()
|
||||
|
||||
// Send slash command
|
||||
isCmd, err := ctx.Service.SendCommand(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.View.Debug.Println(
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
// Send message
|
||||
if !isCmd {
|
||||
err := ctx.Service.SendMessage(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
@ -251,6 +265,7 @@ func actionSend(ctx *context.AppContext) {
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear notification icon if there is any
|
||||
channelItem := ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel]
|
||||
|
@ -23,6 +23,7 @@ type SlackService struct {
|
||||
RTM *slack.RTM
|
||||
Conversations []slack.Channel
|
||||
UserCache map[string]string
|
||||
ThreadCache map[string]string
|
||||
CurrentUserID string
|
||||
CurrentUsername string
|
||||
}
|
||||
@ -34,6 +35,7 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
Config: config,
|
||||
Client: slack.New(config.SlackToken),
|
||||
UserCache: make(map[string]string),
|
||||
ThreadCache: make(map[string]string),
|
||||
}
|
||||
|
||||
// Get user associated with token, mainly
|
||||
@ -276,6 +278,66 @@ func (s *SlackService) SendMessage(channelID string, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendReply will send a message to a particular thread, specifying the
|
||||
// ThreadTimestamp will make it reply to that specific thread. (see:
|
||||
// https://api.slack.com/docs/message-threading, 'Posting replies')
|
||||
func (s *SlackService) SendReply(channelID string, threadID string, message string) error {
|
||||
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
|
||||
postParams := slack.PostMessageParameters{
|
||||
AsUser: true,
|
||||
Username: s.CurrentUsername,
|
||||
LinkNames: 1,
|
||||
ThreadTimestamp: threadID,
|
||||
}
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
|
||||
_, _, err := s.Client.PostMessage(channelID, message, postParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendCommand will send a specific command to slack. First we check
|
||||
// wether we are dealing with a command, and if it is one of the supported
|
||||
// ones.
|
||||
func (s *SlackService) SendCommand(channelID string, message string) (bool, error) {
|
||||
// First check if it begins with slash and a command
|
||||
r, err := regexp.Compile(`^/\w+`)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
match := r.MatchString(message)
|
||||
if !match {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute the the command when supported
|
||||
switch r.FindString(message) {
|
||||
case "/reply":
|
||||
r := regexp.MustCompile(`(?P<cmd>^/\w+) (?P<id>\w+) (?P<msg>.*)`)
|
||||
subMatch := r.FindStringSubmatch(message)
|
||||
|
||||
if len(subMatch) < 4 {
|
||||
return false, errors.New("'/reply' command malformed")
|
||||
}
|
||||
|
||||
threadID := s.ThreadCache[subMatch[2]]
|
||||
msg := subMatch[3]
|
||||
|
||||
err := s.SendReply(channelID, threadID, msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetMessages will get messages for a channel, group or im channel delimited
|
||||
// by a count.
|
||||
func (s *SlackService) GetMessages(channelID string, count int) ([]components.Message, error) {
|
||||
@ -361,6 +423,7 @@ func (s *SlackService) CreateMessage(message slack.Message, channelID string) co
|
||||
Name: name,
|
||||
Content: parseMessage(s, message.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
@ -381,7 +444,23 @@ func (s *SlackService) CreateMessage(message slack.Message, channelID string) co
|
||||
|
||||
// When the message timestamp and thread timestamp are the same, we
|
||||
// have a parent message. This means it contains a thread with replies.
|
||||
//
|
||||
// Additionally, we set the thread timestamp in the s.ThreadCache with
|
||||
// the hexadecimal representation of the timestamp. We do this because
|
||||
// we if we want to reply to a thread, we need to reference this
|
||||
// timestamp. Which is too long to type, we shorten it and remember the
|
||||
// reference in the cache.
|
||||
if message.ThreadTimestamp != "" && message.ThreadTimestamp == message.Timestamp {
|
||||
|
||||
// Set the thread identifier for thread cache
|
||||
f, _ := strconv.ParseFloat(message.ThreadTimestamp, 64)
|
||||
threadID := fmt.Sprintf("thread_%x", int(f))
|
||||
s.ThreadCache[threadID] = message.ThreadTimestamp
|
||||
|
||||
// Set thread prefix for message
|
||||
msg.Thread = fmt.Sprintf("%s ", threadID)
|
||||
|
||||
// Create the message replies from the thread
|
||||
replies := s.CreateMessageFromReplies(message, channelID)
|
||||
for _, reply := range replies {
|
||||
msg.Messages[reply.ID] = reply
|
||||
@ -435,7 +514,6 @@ func (s *SlackService) CreateMessageFromReplies(message slack.Message, channelID
|
||||
|
||||
var replies []components.Message
|
||||
for _, reply := range msgs {
|
||||
|
||||
// Because the conversations api returns an entire thread (a
|
||||
// message plus all the messages in reply), we need to check if
|
||||
// one of the replies isn't the parent that we started with.
|
||||
@ -447,6 +525,10 @@ func (s *SlackService) CreateMessageFromReplies(message slack.Message, channelID
|
||||
}
|
||||
|
||||
msg := s.CreateMessage(reply, channelID)
|
||||
|
||||
// Set the thread separator
|
||||
msg.Thread = " "
|
||||
|
||||
replies = append(replies, msg)
|
||||
}
|
||||
|
||||
@ -555,6 +637,7 @@ func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []c
|
||||
field.Value,
|
||||
),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
@ -568,6 +651,7 @@ func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []c
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
@ -581,6 +665,7 @@ func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []c
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Title),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
|
Loading…
Reference in New Issue
Block a user