Merge branch 'threads'
* threads: Update comment Update threads reply functionality Add reply to threads functionality Fix thread timestamp checking Fix pagination problem Update handling thread replies in event handler Update message event Implement new Message setup Start with thread support Reference #91
This commit is contained in:
commit
112524daad
@ -12,58 +12,19 @@ import (
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
)
|
||||
|
||||
var (
|
||||
COLORS = []string{
|
||||
"fg-black",
|
||||
"fg-red",
|
||||
"fg-green",
|
||||
"fg-yellow",
|
||||
"fg-blue",
|
||||
"fg-magenta",
|
||||
"fg-cyan",
|
||||
"fg-white",
|
||||
}
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Time time.Time
|
||||
Name string
|
||||
Content string
|
||||
|
||||
StyleTime string
|
||||
StyleName string
|
||||
StyleText string
|
||||
|
||||
FormatTime string
|
||||
}
|
||||
|
||||
func (m Message) colorizeName(styleName string) string {
|
||||
if strings.Contains(styleName, "colorize") {
|
||||
var sum int
|
||||
for _, c := range m.Name {
|
||||
sum = sum + int(c)
|
||||
}
|
||||
|
||||
i := sum % len(COLORS)
|
||||
|
||||
return strings.Replace(m.StyleName, "colorize", COLORS[i], -1)
|
||||
}
|
||||
|
||||
return styleName
|
||||
}
|
||||
|
||||
// Chat is the definition of a Chat component
|
||||
type Chat struct {
|
||||
List *termui.List
|
||||
Messages []Message
|
||||
Messages map[string]Message
|
||||
Offset int
|
||||
}
|
||||
|
||||
// CreateChat is the constructor for the Chat struct
|
||||
func CreateChatComponent(inputHeight int) *Chat {
|
||||
chat := &Chat{
|
||||
List: termui.NewList(),
|
||||
Offset: 0,
|
||||
List: termui.NewList(),
|
||||
Messages: make(map[string]Message),
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
chat.List.Height = termui.TermHeight() - inputHeight
|
||||
@ -74,59 +35,8 @@ func CreateChatComponent(inputHeight int) *Chat {
|
||||
|
||||
// Buffer implements interface termui.Bufferer
|
||||
func (c *Chat) Buffer() termui.Buffer {
|
||||
// Build cells. We're building parts of the message individually, or else
|
||||
// DefaultTxBuilder will interpret potential markdown usage in a message
|
||||
// as well.
|
||||
cells := make([]termui.Cell, 0)
|
||||
for i, msg := range c.Messages {
|
||||
|
||||
// When msg.Time and msg.Name are empty (in the case of attachments)
|
||||
// don't add the time and name parts.
|
||||
if (msg.Time != time.Time{} && msg.Name != "") {
|
||||
// Time
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
fmt.Sprintf(
|
||||
"[[%s]](%s) ",
|
||||
msg.Time.Format(msg.FormatTime),
|
||||
msg.StyleTime,
|
||||
),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
|
||||
// Name
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
fmt.Sprintf("[<%s>](%s) ",
|
||||
msg.Name,
|
||||
msg.colorizeName(msg.StyleName),
|
||||
),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Hack, in order to get the correct fg and bg attributes. This is
|
||||
// because the readAttr function in termui is unexported.
|
||||
txCells := termui.DefaultTxBuilder.Build(
|
||||
fmt.Sprintf("[.](%s)", msg.StyleText),
|
||||
termui.ColorDefault, termui.ColorDefault,
|
||||
)
|
||||
|
||||
// Text
|
||||
for _, r := range msg.Content {
|
||||
cells = append(
|
||||
cells,
|
||||
termui.Cell{
|
||||
Ch: r,
|
||||
Fg: txCells[0].Fg,
|
||||
Bg: txCells[0].Bg,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Add a newline after every message
|
||||
if i < len(c.Messages)-1 {
|
||||
cells = append(cells, termui.Cell{Ch: '\n'})
|
||||
}
|
||||
}
|
||||
// Convert Messages into termui.Cell
|
||||
cells := c.MessagesToCells(c.Messages)
|
||||
|
||||
// We will create an array of Line structs, this allows us
|
||||
// to more easily render the items in a list. We will range
|
||||
@ -262,17 +172,26 @@ func (c *Chat) SetMessages(messages []Message) {
|
||||
// Reset offset first, when scrolling in view and changing channels we
|
||||
// want the offset to be 0 when loading new messages
|
||||
c.Offset = 0
|
||||
c.Messages = messages
|
||||
for _, msg := range messages {
|
||||
c.Messages[msg.ID] = msg
|
||||
}
|
||||
}
|
||||
|
||||
// AddMessage adds a single message to Messages
|
||||
func (c *Chat) AddMessage(message Message) {
|
||||
c.Messages = append(c.Messages, 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
|
||||
}
|
||||
|
||||
// ClearMessages clear the c.Messages
|
||||
func (c *Chat) ClearMessages() {
|
||||
c.Messages = make([]Message, 0)
|
||||
c.Messages = make(map[string]Message)
|
||||
}
|
||||
|
||||
// ScrollUp will render the chat messages based on the Offset of the Chat
|
||||
@ -312,6 +231,80 @@ func (c *Chat) SetBorderLabel(channelName string) {
|
||||
c.List.BorderLabel = channelName
|
||||
}
|
||||
|
||||
// MessagesToCells is a wrapper around MessageToCells to use for a slice of
|
||||
// of type Message
|
||||
func (c *Chat) MessagesToCells(msgs map[string]Message) []termui.Cell {
|
||||
cells := make([]termui.Cell, 0)
|
||||
sortedMessages := SortMessages(msgs)
|
||||
|
||||
for i, msg := range sortedMessages {
|
||||
cells = append(cells, c.MessageToCells(msg)...)
|
||||
|
||||
if len(msg.Messages) > 0 {
|
||||
cells = append(cells, termui.Cell{Ch: '\n'})
|
||||
cells = append(cells, c.MessagesToCells(msg.Messages)...)
|
||||
}
|
||||
|
||||
// Add a newline after every message
|
||||
if i < len(sortedMessages)-1 {
|
||||
cells = append(cells, termui.Cell{Ch: '\n'})
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// MessageToCells will convert a Message struct to termui.Cell
|
||||
//
|
||||
// We're building parts of the message individually, or else DefaultTxBuilder
|
||||
// will interpret potential markdown usage in a message as well.
|
||||
func (c *Chat) MessageToCells(msg Message) []termui.Cell {
|
||||
cells := make([]termui.Cell, 0)
|
||||
|
||||
// When msg.Time and msg.Name are empty (in the case of attachments)
|
||||
// don't add the time and name parts.
|
||||
if (msg.Time != time.Time{} && msg.Name != "") {
|
||||
// Time
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
msg.GetTime(),
|
||||
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(),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Hack, in order to get the correct fg and bg attributes. This is
|
||||
// because the readAttr function in termui is unexported.
|
||||
txCells := termui.DefaultTxBuilder.Build(
|
||||
msg.GetContent(),
|
||||
termui.ColorDefault, termui.ColorDefault,
|
||||
)
|
||||
|
||||
// Text
|
||||
for _, r := range msg.Content {
|
||||
cells = append(
|
||||
cells,
|
||||
termui.Cell{
|
||||
Ch: r,
|
||||
Fg: txCells[0].Fg,
|
||||
Bg: txCells[0].Bg,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// Help shows the usage and key bindings in the chat pane
|
||||
func (c *Chat) Help(usage string, cfg *config.Config) {
|
||||
help := []Message{
|
||||
@ -348,5 +341,7 @@ func (c *Chat) Help(usage string, cfg *config.Config) {
|
||||
help = append(help, Message{Content: ""})
|
||||
}
|
||||
|
||||
c.Messages = help
|
||||
for _, msg := range help {
|
||||
c.Messages[msg.ID] = msg
|
||||
}
|
||||
}
|
||||
|
95
components/message.go
Normal file
95
components/message.go
Normal file
@ -0,0 +1,95 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
COLORS = []string{
|
||||
"fg-black",
|
||||
"fg-red",
|
||||
"fg-green",
|
||||
"fg-yellow",
|
||||
"fg-blue",
|
||||
"fg-magenta",
|
||||
"fg-cyan",
|
||||
"fg-white",
|
||||
}
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
ID string
|
||||
Messages map[string]Message
|
||||
|
||||
Time time.Time
|
||||
Thread string
|
||||
Name string
|
||||
Content string
|
||||
|
||||
StyleTime string
|
||||
StyleThread string
|
||||
StyleName string
|
||||
StyleText string
|
||||
|
||||
FormatTime string
|
||||
}
|
||||
|
||||
func (m Message) GetTime() string {
|
||||
return fmt.Sprintf(
|
||||
"[[%s]](%s) ",
|
||||
m.Time.Format(m.FormatTime),
|
||||
m.StyleTime,
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
m.colorizeName(m.StyleName),
|
||||
)
|
||||
}
|
||||
|
||||
func (m Message) GetContent() string {
|
||||
return fmt.Sprintf("[.](%s)", m.StyleText)
|
||||
}
|
||||
|
||||
func (m Message) colorizeName(styleName string) string {
|
||||
if strings.Contains(styleName, "colorize") {
|
||||
var sum int
|
||||
for _, c := range m.Name {
|
||||
sum = sum + int(c)
|
||||
}
|
||||
|
||||
i := sum % len(COLORS)
|
||||
|
||||
return strings.Replace(m.StyleName, "colorize", COLORS[i], -1)
|
||||
}
|
||||
|
||||
return styleName
|
||||
}
|
||||
|
||||
func SortMessages(msgs map[string]Message) []Message {
|
||||
keys := make([]string, 0)
|
||||
for k := range msgs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
sortedMessages := make([]Message, 0)
|
||||
for _, k := range keys {
|
||||
sortedMessages = append(sortedMessages, msgs[k])
|
||||
}
|
||||
|
||||
return sortedMessages
|
||||
}
|
@ -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"`
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func messageHandler(ctx *context.AppContext) {
|
||||
case *slack.MessageEvent:
|
||||
|
||||
// Construct message
|
||||
msg, err := ctx.Service.CreateMessageFromMessageEvent(ev)
|
||||
msg, err := ctx.Service.CreateMessageFromMessageEvent(ev, ev.Channel)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -121,12 +121,12 @@ func messageHandler(ctx *context.AppContext) {
|
||||
// Add message to the selected channel
|
||||
if ev.Channel == ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID {
|
||||
|
||||
// Reverse order of messages, mainly done
|
||||
// when attachments are added to message
|
||||
for i := len(msg) - 1; i >= 0; i-- {
|
||||
ctx.View.Chat.AddMessage(
|
||||
msg[i],
|
||||
)
|
||||
// When timestamp isn't set this is a thread reply,
|
||||
// handle as such
|
||||
if ev.ThreadTimestamp != "" {
|
||||
ctx.View.Chat.AddReply(ev.ThreadTimestamp, msg)
|
||||
} else {
|
||||
ctx.View.Chat.AddMessage(msg)
|
||||
}
|
||||
|
||||
termui.Render(ctx.View.Chat)
|
||||
@ -243,8 +243,8 @@ func actionSend(ctx *context.AppContext) {
|
||||
ctx.View.Input.Clear()
|
||||
ctx.View.Refresh()
|
||||
|
||||
// Send message
|
||||
err := ctx.Service.SendMessage(
|
||||
// Send slash command
|
||||
isCmd, err := ctx.Service.SendCommand(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
@ -254,6 +254,19 @@ func actionSend(ctx *context.AppContext) {
|
||||
)
|
||||
}
|
||||
|
||||
// Send message
|
||||
if !isCmd {
|
||||
err := ctx.Service.SendMessage(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.View.Debug.Println(
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear notification icon if there is any
|
||||
channelItem := ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel]
|
||||
if channelItem.Notification {
|
||||
|
327
service/slack.go
327
service/slack.go
@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -22,6 +23,7 @@ type SlackService struct {
|
||||
RTM *slack.RTM
|
||||
Conversations []slack.Channel
|
||||
UserCache map[string]string
|
||||
ThreadCache map[string]string
|
||||
CurrentUserID string
|
||||
CurrentUsername string
|
||||
}
|
||||
@ -30,9 +32,10 @@ type SlackService struct {
|
||||
// the RTM and a Client
|
||||
func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
svc := &SlackService{
|
||||
Config: config,
|
||||
Client: slack.New(config.SlackToken),
|
||||
UserCache: make(map[string]string),
|
||||
Config: config,
|
||||
Client: slack.New(config.SlackToken),
|
||||
UserCache: make(map[string]string),
|
||||
ThreadCache: make(map[string]string),
|
||||
}
|
||||
|
||||
// Get user associated with token, mainly
|
||||
@ -297,6 +300,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 "/thread":
|
||||
r := regexp.MustCompile(`(?P<cmd>^/\w+) (?P<id>\w+) (?P<msg>.*)`)
|
||||
subMatch := r.FindStringSubmatch(message)
|
||||
|
||||
if len(subMatch) < 4 {
|
||||
return false, errors.New("'/thread' 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) {
|
||||
@ -316,8 +379,8 @@ func (s *SlackService) GetMessages(channelID string, count int) ([]components.Me
|
||||
// Construct the messages
|
||||
var messages []components.Message
|
||||
for _, message := range history.Messages {
|
||||
msg := s.CreateMessage(message)
|
||||
messages = append(messages, msg...)
|
||||
msg := s.CreateMessage(message, channelID)
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
|
||||
// Reverse the order of the messages, we want the newest in
|
||||
@ -334,11 +397,7 @@ func (s *SlackService) GetMessages(channelID string, count int) ([]components.Me
|
||||
// in the Chat pane.
|
||||
//
|
||||
// [23:59] <erroneousboat> Hello world!
|
||||
//
|
||||
// This returns an array of string because we will try to uncover attachments
|
||||
// associated with messages.
|
||||
func (s *SlackService) CreateMessage(message slack.Message) []components.Message {
|
||||
var msgs []components.Message
|
||||
func (s *SlackService) CreateMessage(message slack.Message, channelID string) components.Message {
|
||||
var name string
|
||||
|
||||
// Get username from cache
|
||||
@ -371,11 +430,6 @@ func (s *SlackService) CreateMessage(message slack.Message) []components.Message
|
||||
name = "unknown"
|
||||
}
|
||||
|
||||
// When there are attachments append them
|
||||
if len(message.Attachments) > 0 {
|
||||
msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...)
|
||||
}
|
||||
|
||||
// Parse time
|
||||
floatTime, err := strconv.ParseFloat(message.Timestamp, 64)
|
||||
if err != nil {
|
||||
@ -385,91 +439,137 @@ func (s *SlackService) CreateMessage(message slack.Message) []components.Message
|
||||
|
||||
// Format message
|
||||
msg := components.Message{
|
||||
Time: time.Unix(intTime, 0),
|
||||
Name: name,
|
||||
Content: parseMessage(s, message.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
ID: message.Timestamp,
|
||||
Messages: make(map[string]components.Message),
|
||||
Time: time.Unix(intTime, 0),
|
||||
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,
|
||||
}
|
||||
|
||||
msgs = append(msgs, msg)
|
||||
// When there are attachments, add them to Messages
|
||||
//
|
||||
// NOTE: attachments don't have an id or a timestamp that we can
|
||||
// use as a key value for the Messages field, so we use the index
|
||||
// of the returned array.
|
||||
if len(message.Attachments) > 0 {
|
||||
atts := s.CreateMessageFromAttachments(message.Attachments)
|
||||
|
||||
return msgs
|
||||
for i, a := range atts {
|
||||
msg.Messages[strconv.Itoa(i)] = a
|
||||
}
|
||||
}
|
||||
|
||||
// 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 base62 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 := hashID(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
|
||||
}
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent) ([]components.Message, error) {
|
||||
// CreateMessageFromReplies will create components.Message struct from
|
||||
// the conversation replies from slack.
|
||||
//
|
||||
// Useful documentation:
|
||||
//
|
||||
// https://api.slack.com/docs/message-threading
|
||||
// https://api.slack.com/methods/conversations.replies
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.GetConversationReplies
|
||||
// https://godoc.org/github.com/nlopes/slack#GetConversationRepliesParameters
|
||||
func (s *SlackService) CreateMessageFromReplies(message slack.Message, channelID string) []components.Message {
|
||||
msgs := make([]slack.Message, 0)
|
||||
|
||||
var msgs []components.Message
|
||||
var name string
|
||||
initReplies, _, initCur, err := s.Client.GetConversationReplies(
|
||||
&slack.GetConversationRepliesParameters{
|
||||
ChannelID: channelID,
|
||||
Timestamp: message.ThreadTimestamp,
|
||||
Limit: 200,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
msgs = append(msgs, initReplies...)
|
||||
|
||||
nextCur := initCur
|
||||
for nextCur != "" {
|
||||
conversationReplies, _, cursor, err := s.Client.GetConversationReplies(&slack.GetConversationRepliesParameters{
|
||||
ChannelID: channelID,
|
||||
Timestamp: message.ThreadTimestamp,
|
||||
Cursor: nextCur,
|
||||
Limit: 200,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
msgs = append(msgs, conversationReplies...)
|
||||
nextCur = cursor
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// Keep in mind that the api returns the replies with the latest
|
||||
// as the first element.
|
||||
if reply.ThreadTimestamp != "" && reply.ThreadTimestamp == reply.Timestamp {
|
||||
continue
|
||||
}
|
||||
|
||||
msg := s.CreateMessage(reply, channelID)
|
||||
|
||||
// Set the thread separator
|
||||
msg.Thread = " "
|
||||
|
||||
replies = append(replies, msg)
|
||||
}
|
||||
|
||||
return replies
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent, channelID string) (components.Message, error) {
|
||||
msg := slack.Message{Msg: message.Msg}
|
||||
|
||||
switch message.SubType {
|
||||
case "message_changed":
|
||||
// Append (edited) when an edited message is received
|
||||
message = &slack.MessageEvent{Msg: *message.SubMessage}
|
||||
message.Text = fmt.Sprintf("%s (edited)", message.Text)
|
||||
msg = slack.Message{Msg: *message.SubMessage}
|
||||
msg.Text = fmt.Sprintf("%s (edited)", msg.Text)
|
||||
case "message_replied":
|
||||
// Ignore reply events
|
||||
return nil, errors.New("ignoring reply events")
|
||||
return components.Message{}, errors.New("ignoring reply events")
|
||||
}
|
||||
|
||||
// Get username from cache
|
||||
name, ok := s.UserCache[message.User]
|
||||
|
||||
// Name not in cache
|
||||
if !ok {
|
||||
if message.BotID != "" {
|
||||
// Name not found, perhaps a bot, use Username
|
||||
name, ok = s.UserCache[message.BotID]
|
||||
if !ok {
|
||||
// Not found in cache, add it
|
||||
name = message.Username
|
||||
s.UserCache[message.BotID] = message.Username
|
||||
}
|
||||
} else {
|
||||
// Not a bot, not in cache, get user info
|
||||
user, err := s.Client.GetUserInfo(message.User)
|
||||
if err != nil {
|
||||
name = "unknown"
|
||||
s.UserCache[message.User] = name
|
||||
} else {
|
||||
name = user.Name
|
||||
s.UserCache[message.User] = user.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = "unknown"
|
||||
}
|
||||
|
||||
// When there are attachments append them
|
||||
if len(message.Attachments) > 0 {
|
||||
msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...)
|
||||
}
|
||||
|
||||
// Parse time
|
||||
floatTime, err := strconv.ParseFloat(message.Timestamp, 64)
|
||||
if err != nil {
|
||||
floatTime = 0.0
|
||||
}
|
||||
intTime := int64(floatTime)
|
||||
|
||||
// Format message
|
||||
msg := components.Message{
|
||||
Time: time.Unix(intTime, 0),
|
||||
Name: name,
|
||||
Content: parseMessage(s, message.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
}
|
||||
|
||||
msgs = append(msgs, msg)
|
||||
|
||||
return msgs, nil
|
||||
return s.CreateMessage(msg, channelID), nil
|
||||
}
|
||||
|
||||
// parseMessage will parse a message string and find and replace:
|
||||
@ -546,22 +646,23 @@ func parseEmoji(msg string) string {
|
||||
)
|
||||
}
|
||||
|
||||
// CreateMessageFromAttachments will construct a array of string of the Field
|
||||
// values of Attachments from a Message.
|
||||
// CreateMessageFromAttachments will construct an array of strings from the
|
||||
// Field values of Attachments of a Message.
|
||||
func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []components.Message {
|
||||
var msgs []components.Message
|
||||
for _, att := range atts {
|
||||
for i := len(att.Fields) - 1; i >= 0; i-- {
|
||||
for _, field := range att.Fields {
|
||||
msgs = append(msgs, components.Message{
|
||||
Content: fmt.Sprintf(
|
||||
"%s %s",
|
||||
att.Fields[i].Title,
|
||||
att.Fields[i].Value,
|
||||
field.Title,
|
||||
field.Value,
|
||||
),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -570,11 +671,12 @@ func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []c
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -583,11 +685,12 @@ func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []c
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Title),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -607,3 +710,15 @@ func (s *SlackService) createChannelItem(chn slack.Channel) components.ChannelIt
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
}
|
||||
}
|
||||
|
||||
func hashID(input int) string {
|
||||
const base62Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
|
||||
hash := ""
|
||||
for input > 0 {
|
||||
hash = string(base62Alphabet[input%62]) + hash
|
||||
input = int(input / 62)
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user