871 lines
22 KiB
Go
Raw Normal View History

2016-09-25 22:34:02 +02:00
package service
import (
"errors"
2016-09-25 22:34:02 +02:00
"fmt"
2019-05-11 10:23:21 +02:00
"html"
2018-12-22 12:33:30 +01:00
"log"
2019-05-26 12:05:52 +02:00
"net/url"
2017-07-21 14:04:16 +02:00
"regexp"
2018-09-08 21:16:25 +02:00
"sort"
2016-09-27 22:05:44 +02:00
"strconv"
2017-07-22 14:36:55 +02:00
"strings"
2018-03-31 10:58:37 +02:00
"sync"
2016-09-27 22:05:44 +02:00
"time"
2016-09-25 22:34:02 +02:00
"github.com/nlopes/slack"
2017-07-21 14:04:16 +02:00
"github.com/erroneousboat/slack-term/components"
2017-07-21 14:04:16 +02:00
"github.com/erroneousboat/slack-term/config"
2016-09-25 22:34:02 +02:00
)
type SlackService struct {
2017-12-16 22:54:00 +01:00
Config *config.Config
2017-12-01 12:05:27 +01:00
Client *slack.Client
RTM *slack.RTM
Conversations []slack.Channel
2017-12-01 12:05:27 +01:00
UserCache map[string]string
2019-02-16 22:45:36 +01:00
ThreadCache map[string]string
2017-12-01 12:05:27 +01:00
CurrentUserID string
CurrentUsername string
2016-09-25 22:34:02 +02:00
}
2016-10-02 16:07:35 +02:00
// NewSlackService is the constructor for the SlackService and will initialize
// the RTM and a Client
2017-12-16 22:54:00 +01:00
func NewSlackService(config *config.Config) (*SlackService, error) {
2016-09-29 21:19:09 +02:00
svc := &SlackService{
2019-02-16 22:45:36 +01:00
Config: config,
Client: slack.New(config.SlackToken),
UserCache: make(map[string]string),
ThreadCache: make(map[string]string),
2016-09-29 21:19:09 +02:00
}
2016-09-25 22:34:02 +02:00
// Get user associated with token, mainly
// used to identify user when new messages
// arrives
authTest, err := svc.Client.AuthTest()
if err != nil {
return nil, errors.New("not able to authorize client, check your connection and if your slack-token is set correctly")
}
svc.CurrentUserID = authTest.UserID
2016-09-25 22:34:02 +02:00
// Create RTM
svc.RTM = svc.Client.NewRTM()
2016-09-25 22:34:02 +02:00
go svc.RTM.ManageConnection()
2016-10-02 16:07:35 +02:00
// Creation of user cache this speeds up
// the uncovering of usernames of messages
2016-09-30 11:22:40 +02:00
users, _ := svc.Client.GetUsers()
for _, user := range users {
// only add non-deleted users
if !user.Deleted {
svc.UserCache[user.ID] = user.Name
}
2016-09-30 11:22:40 +02:00
}
// Get name of current user, and set presence to active
2017-12-01 12:05:27 +01:00
currentUser, err := svc.Client.GetUserInfo(svc.CurrentUserID)
if err != nil {
svc.CurrentUsername = "slack-term"
}
svc.CurrentUsername = currentUser.Name
svc.SetUserAsActive()
2017-12-01 12:05:27 +01:00
return svc, nil
2016-09-25 22:34:02 +02:00
}
2018-10-27 14:44:20 +02:00
func (s *SlackService) GetChannels() ([]components.ChannelItem, error) {
2018-08-26 14:12:23 +02:00
slackChans := make([]slack.Channel, 0)
// Initial request
initChans, initCur, err := s.Client.GetConversations(
&slack.GetConversationsParameters{
ExcludeArchived: "true",
Limit: 1000,
2018-08-26 14:12:23 +02:00
Types: []string{
"public_channel",
"private_channel",
"im",
"mpim",
},
},
)
if err != nil {
2018-10-27 14:44:20 +02:00
return nil, err
2018-08-26 14:12:23 +02:00
}
slackChans = append(slackChans, initChans...)
// Paginate over additional channels
nextCur := initCur
for nextCur != "" {
channels, cursor, err := s.Client.GetConversations(
&slack.GetConversationsParameters{
Cursor: nextCur,
ExcludeArchived: "true",
Limit: 1000,
2018-08-26 14:12:23 +02:00
Types: []string{
"public_channel",
"private_channel",
"im",
"mpim",
},
},
)
if err != nil {
2018-10-27 14:44:20 +02:00
return nil, err
2018-08-26 14:12:23 +02:00
}
slackChans = append(slackChans, channels...)
nextCur = cursor
}
// We're creating tempChan, because we want to be able to
// sort the types of channels into buckets
2018-09-08 21:16:25 +02:00
type tempChan struct {
channelItem components.ChannelItem
slackChannel slack.Channel
}
// Initialize buckets
buckets := make(map[int]map[string]*tempChan)
buckets[0] = make(map[string]*tempChan) // Channels
buckets[1] = make(map[string]*tempChan) // Group
buckets[2] = make(map[string]*tempChan) // MpIM
buckets[3] = make(map[string]*tempChan) // IM
var wg sync.WaitGroup
2018-09-08 21:16:25 +02:00
for _, chn := range slackChans {
chanItem := s.createChannelItem(chn)
2018-08-26 14:12:23 +02:00
if chn.IsChannel {
if !chn.IsMember {
continue
}
2018-09-08 21:16:25 +02:00
chanItem.Type = components.ChannelTypeChannel
if chn.UnreadCount > 0 {
chanItem.Notification = true
}
buckets[0][chn.ID] = &tempChan{
channelItem: chanItem,
slackChannel: chn,
}
2018-08-26 14:12:23 +02:00
}
if chn.IsGroup {
if !chn.IsMember {
continue
}
2018-10-13 15:32:21 +02:00
// This is done because MpIM channels are also considered groups
if chn.IsMpIM {
if !chn.IsOpen {
continue
}
chanItem.Type = components.ChannelTypeMpIM
if chn.UnreadCount > 0 {
chanItem.Notification = true
}
2018-10-13 15:32:21 +02:00
buckets[2][chn.ID] = &tempChan{
channelItem: chanItem,
slackChannel: chn,
}
} else {
chanItem.Type = components.ChannelTypeGroup
if chn.UnreadCount > 0 {
chanItem.Notification = true
}
2018-10-13 15:32:21 +02:00
buckets[1][chn.ID] = &tempChan{
channelItem: chanItem,
slackChannel: chn,
}
}
2018-08-26 14:12:23 +02:00
}
// NOTE: user presence is set in the event handler by the function
// `actionSetPresenceAll`, that is why we set the presence to away
2018-08-26 14:12:23 +02:00
if chn.IsIM {
2018-09-01 18:59:00 +02:00
// Check if user is deleted, we do this by checking the user id,
// and see if we have the user in the UserCache
name, ok := s.UserCache[chn.User]
if !ok {
continue
}
2018-09-08 21:16:25 +02:00
chanItem.Name = name
chanItem.Type = components.ChannelTypeIM
chanItem.Presence = "away"
2018-09-02 10:14:34 +02:00
if chn.UnreadCount > 0 {
chanItem.Notification = true
}
buckets[3][chn.User] = &tempChan{
channelItem: chanItem,
slackChannel: chn,
}
2018-08-26 14:12:23 +02:00
}
2018-09-08 21:16:25 +02:00
}
2018-08-26 14:12:23 +02:00
wg.Wait()
2018-09-15 12:21:42 +02:00
// Sort the buckets
var keys []int
for k := range buckets {
keys = append(keys, k)
}
sort.Ints(keys)
2018-09-08 21:16:25 +02:00
var chans []components.ChannelItem
2018-09-15 12:21:42 +02:00
for _, k := range keys {
bucket := buckets[k]
2018-09-08 21:16:25 +02:00
// Sort channels in every bucket
tcArr := make([]tempChan, 0)
for _, v := range bucket {
tcArr = append(tcArr, *v)
2018-09-08 21:16:25 +02:00
}
2018-03-31 10:58:37 +02:00
sort.Slice(tcArr, func(i, j int) bool {
return tcArr[i].channelItem.Name < tcArr[j].channelItem.Name
})
// Add ChannelItem and SlackChannel to the SlackService struct
for _, tc := range tcArr {
chans = append(chans, tc.channelItem)
s.Conversations = append(s.Conversations, tc.slackChannel)
2018-03-31 10:58:37 +02:00
}
}
2018-10-27 14:44:20 +02:00
return chans, nil
2018-03-31 10:58:37 +02:00
}
// GetUserPresence will get the presence of a specific user
func (s *SlackService) GetUserPresence(userID string) (string, error) {
presence, err := s.Client.GetUserPresence(userID)
if err != nil {
return "", err
}
return presence.Presence, nil
}
// Set current user presence to active
func (s *SlackService) SetUserAsActive() {
s.Client.SetUserPresence("auto")
}
// MarkAsRead will set the channel as read
2019-05-11 12:20:52 +02:00
func (s *SlackService) MarkAsRead(channelItem components.ChannelItem) {
switch channelItem.Type {
case components.ChannelTypeChannel:
s.Client.SetChannelReadMark(
channelItem.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
case components.ChannelTypeGroup:
s.Client.SetGroupReadMark(
channelItem.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
case components.ChannelTypeMpIM:
s.Client.MarkIMChannel(
channelItem.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
case components.ChannelTypeIM:
s.Client.MarkIMChannel(
channelItem.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
}
2018-04-01 13:03:28 +02:00
}
2016-10-29 23:59:16 +02:00
// SendMessage will send a message to a particular channel
func (s *SlackService) SendMessage(channelID string, message string) error {
2016-09-27 22:05:44 +02:00
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
postParams := slack.MsgOptionPostMessageParameters(slack.PostMessageParameters{
AsUser: true,
Username: s.CurrentUsername,
LinkNames: 1,
})
text := slack.MsgOptionText(message, true)
2016-09-27 22:05:44 +02:00
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
_, _, err := s.Client.PostMessage(channelID, text, postParams)
if err != nil {
return err
}
return nil
2016-09-27 22:05:44 +02:00
}
2016-09-25 22:34:02 +02:00
2019-02-16 22:45:36 +01:00
// 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.MsgOptionPostMessageParameters(slack.PostMessageParameters{
2019-02-16 22:45:36 +01:00
AsUser: true,
Username: s.CurrentUsername,
LinkNames: 1,
ThreadTimestamp: threadID,
})
text := slack.MsgOptionText(message, true)
2019-02-16 22:45:36 +01:00
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
_, _, err := s.Client.PostMessage(channelID, text, postParams)
2019-02-16 22:45:36 +01:00
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.
2019-05-26 12:05:52 +02:00
//
// NOTE: slack slash commands that are sent to the slack api are undocumented,
// and as such we need to update the message option that direct it to the
// correct api endpoint.
//
// https://github.com/ErikKalkoken/slackApiDoc/blob/master/chat.command.md
2019-02-16 22:45:36 +01:00
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":
2019-02-16 22:45:36 +01:00
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")
2019-02-16 22:45:36 +01:00
}
threadID := s.ThreadCache[subMatch[2]]
msg := subMatch[3]
err := s.SendReply(channelID, threadID, msg)
if err != nil {
return false, err
}
2019-05-26 12:05:52 +02:00
return true, nil
default:
r := regexp.MustCompile(`(?P<cmd>^/\w+) (?P<text>.*)`)
subMatch := r.FindStringSubmatch(message)
if len(subMatch) < 3 {
return false, errors.New("slash command malformed")
}
cmd := subMatch[1]
text := subMatch[2]
msgOption := slack.UnsafeMsgOptionEndpoint(
fmt.Sprintf("%s%s", slack.APIURL, "chat.command"),
func(urlValues url.Values) {
urlValues.Add("command", cmd)
urlValues.Add("text", text)
},
)
_, _, err := s.Client.PostMessage(channelID, msgOption)
if err != nil {
return false, err
}
2019-02-16 22:45:36 +01:00
return true, nil
}
return false, nil
}
2016-10-29 23:59:16 +02:00
// GetMessages will get messages for a channel, group or im channel delimited
2019-06-29 11:58:39 +02:00
// by a count. It will return the messages, the thread identifiers
// (as ChannelItem), and and error.
2019-03-16 14:42:27 +01:00
func (s *SlackService) GetMessages(channelID string, count int) ([]components.Message, []components.ChannelItem, error) {
2018-10-27 14:44:20 +02:00
// https://godoc.org/github.com/nlopes/slack#GetConversationHistoryParameters
historyParams := slack.GetConversationHistoryParameters{
ChannelID: channelID,
Limit: count,
2016-09-30 23:52:26 +02:00
Inclusive: false,
}
history, err := s.Client.GetConversationHistory(&historyParams)
if err != nil {
2019-03-16 14:42:27 +01:00
return nil, nil, err
2016-09-30 23:52:26 +02:00
}
// Construct the messages
2017-12-03 21:43:33 +01:00
var messages []components.Message
2019-03-16 14:42:27 +01:00
var threads []components.ChannelItem
2016-09-30 23:52:26 +02:00
for _, message := range history.Messages {
2018-12-24 14:37:12 +01:00
msg := s.CreateMessage(message, channelID)
messages = append(messages, msg)
2019-03-16 14:42:27 +01:00
// FIXME: create boolean isThread
if msg.Thread != "" {
threads = append(threads, components.ChannelItem{
ID: msg.ID,
Name: msg.Thread,
Type: components.ChannelTypeGroup,
StylePrefix: s.Config.Theme.Channel.Prefix,
StyleIcon: s.Config.Theme.Channel.Icon,
StyleText: s.Config.Theme.Channel.Text,
})
}
2016-09-25 22:34:02 +02:00
}
2016-10-11 09:33:56 +02:00
// Reverse the order of the messages, we want the newest in
// the last place
2017-12-03 21:43:33 +01:00
var messagesReversed []components.Message
2016-10-11 09:33:56 +02:00
for i := len(messages) - 1; i >= 0; i-- {
messagesReversed = append(messagesReversed, messages[i])
}
2019-03-16 14:42:27 +01:00
return messagesReversed, threads, nil
2016-09-25 22:34:02 +02:00
}
2016-09-29 21:19:09 +02:00
2019-06-29 11:58:39 +02:00
// CreateMessageByID will construct an array of components.Message with only
// 1 message, using the message ID (Timestamp).
//
// For the choice of history parameters see:
// https://api.slack.com/messaging/retrieving
func (s *SlackService) GetMessageByID(messageID string, channelID string) ([]components.Message, error) {
var msgs []components.Message
// https://godoc.org/github.com/nlopes/slack#GetConversationHistoryParameters
historyParams := slack.GetConversationHistoryParameters{
ChannelID: channelID,
Limit: 1,
Inclusive: true,
Latest: messageID,
}
history, err := s.Client.GetConversationHistory(&historyParams)
if err != nil {
return msgs, err
}
// We break because we're only asking for 1 message
for _, message := range history.Messages {
msgs = append(msgs, s.CreateMessage(message, channelID))
break
}
return msgs, nil
}
2016-09-29 21:19:09 +02:00
// CreateMessage will create a string formatted message that can be rendered
// in the Chat pane.
//
// [23:59] <erroneousboat> Hello world!
2018-12-24 14:37:12 +01:00
func (s *SlackService) CreateMessage(message slack.Message, channelID string) components.Message {
2016-09-29 21:19:09 +02:00
var name string
// Get username from cache
name, ok := s.UserCache[message.User]
// Name not in cache
if !ok {
2016-09-30 11:22:40 +02:00
if message.BotID != "" {
2019-05-11 11:47:39 +02:00
name, ok = s.UserCache[message.BotID]
if !ok {
if message.Username != "" {
name = message.Username
s.UserCache[message.BotID] = message.Username
} else {
bot, err := s.Client.GetBotInfo(message.BotID)
if err != nil {
name = "unkown"
s.UserCache[message.BotID] = name
} else {
name = bot.Name
s.UserCache[message.BotID] = bot.Name
}
}
2016-09-30 11:22:40 +02:00
}
} 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
2016-09-29 21:19:09 +02:00
}
}
}
if name == "" {
name = "unknown"
}
// Parse time
floatTime, err := strconv.ParseFloat(message.Timestamp, 64)
if err != nil {
floatTime = 0.0
}
intTime := int64(floatTime)
// Format message
msg := components.Message{
2019-02-16 22:45:36 +01:00
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,
}
2016-09-29 21:19:09 +02:00
2018-12-24 14:37:12 +01:00
// 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)
2016-10-09 14:19:28 +02:00
2018-12-24 14:37:12 +01:00
for i, a := range atts {
msg.Messages[strconv.Itoa(i)] = a
}
}
2016-09-29 21:19:09 +02:00
2019-05-11 10:57:23 +02:00
// When there are files, add them to Messages
if len(message.Files) > 0 {
files := s.CreateMessageFromFiles(message.Files)
for _, file := range files {
msg.Messages[file.ID] = file
}
}
2018-12-24 14:37:12 +01:00
// When the message timestamp and thread timestamp are the same, we
// have a parent message. This means it contains a thread with replies.
2019-02-16 22:45:36 +01:00
//
// Additionally, we set the thread timestamp in the s.ThreadCache with
2019-03-16 12:29:10 +01:00
// the base62 representation of the timestamp. We do this because
2019-02-16 22:45:36 +01:00
// 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.
2018-12-24 14:37:12 +01:00
if message.ThreadTimestamp != "" && message.ThreadTimestamp == message.Timestamp {
2019-02-16 22:45:36 +01:00
// Set the thread identifier for thread cache
f, _ := strconv.ParseFloat(message.ThreadTimestamp, 64)
threadID := hashID(int(f))
2019-02-16 22:45:36 +01:00
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.ThreadTimestamp, channelID)
2018-12-24 14:37:12 +01:00
for _, reply := range replies {
msg.Messages[reply.ID] = reply
}
}
2016-09-29 21:19:09 +02:00
2018-12-24 14:37:12 +01:00
return msg
2016-09-29 21:19:09 +02:00
}
2018-12-24 14:37:12 +01:00
// 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(messageID string, channelID string) []components.Message {
2018-12-24 14:37:12 +01:00
msgs := make([]slack.Message, 0)
2018-12-22 12:33:30 +01:00
2018-12-24 14:37:12 +01:00
initReplies, _, initCur, err := s.Client.GetConversationReplies(
&slack.GetConversationRepliesParameters{
ChannelID: channelID,
Timestamp: messageID,
2018-12-24 14:37:12 +01:00
Limit: 200,
},
2018-12-22 12:33:30 +01:00
)
if err != nil {
log.Fatal(err) // FIXME
2016-10-30 15:38:13 +01:00
}
2018-12-24 14:37:12 +01:00
msgs = append(msgs, initReplies...)
2016-09-29 21:19:09 +02:00
2018-12-24 14:37:12 +01:00
nextCur := initCur
for nextCur != "" {
conversationReplies, _, cursor, err := s.Client.GetConversationReplies(&slack.GetConversationRepliesParameters{
ChannelID: channelID,
Timestamp: messageID,
2018-12-24 14:37:12 +01:00
Cursor: nextCur,
Limit: 200,
2019-01-05 13:53:34 +01:00
})
2018-12-24 14:37:12 +01:00
if err != nil {
log.Fatal(err) // FIXME
2016-09-29 21:19:09 +02:00
}
2018-12-24 14:37:12 +01:00
msgs = append(msgs, conversationReplies...)
nextCur = cursor
2016-09-29 21:19:09 +02:00
}
2018-12-22 12:33:30 +01:00
var replies []components.Message
2019-01-05 13:53:34 +01:00
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.
2019-01-05 13:58:17 +01:00
if reply.ThreadTimestamp != "" && reply.ThreadTimestamp == reply.Timestamp {
2018-12-22 12:33:30 +01:00
continue
}
2016-10-09 14:19:28 +02:00
2018-12-24 14:37:12 +01:00
msg := s.CreateMessage(reply, channelID)
2016-09-29 21:19:09 +02:00
2019-02-16 22:45:36 +01:00
// Set the thread separator
msg.Thread = " "
2018-12-24 14:37:12 +01:00
replies = append(replies, msg)
}
2018-12-24 14:37:12 +01:00
return replies
2018-12-22 12:33:30 +01:00
}
2019-05-11 10:57:23 +02:00
// 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 _, field := range att.Fields {
msgs = append(msgs, components.Message{
Content: fmt.Sprintf(
"%s %s",
field.Title,
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,
},
)
}
if att.Pretext != "" {
msgs = append(
msgs,
components.Message{
Content: fmt.Sprintf("%s", att.Pretext),
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,
},
)
}
2019-05-11 10:57:23 +02:00
if att.Text != "" {
msgs = append(
msgs,
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,
},
)
}
if att.Title != "" {
msgs = append(
msgs,
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,
},
)
}
}
return msgs
}
// CreateMessageFromFiles will create components.Message struct from
// conversation attached files
func (s *SlackService) CreateMessageFromFiles(files []slack.File) []components.Message {
var msgs []components.Message
for _, file := range files {
msgs = append(msgs, components.Message{
Content: fmt.Sprintf(
"%s %s", file.Title, file.URLPrivate,
),
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,
})
}
return msgs
}
2018-12-25 15:09:03 +01:00
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent, channelID string) (components.Message, error) {
msg := slack.Message{Msg: message.Msg}
2016-10-09 14:19:28 +02:00
2018-03-24 13:56:55 +01:00
switch message.SubType {
case "message_changed":
// Append (edited) when an edited message is received
2018-12-25 15:09:03 +01:00
msg = slack.Message{Msg: *message.SubMessage}
msg.Text = fmt.Sprintf("%s (edited)", msg.Text)
2018-03-24 13:56:55 +01:00
case "message_replied":
2018-12-25 15:09:03 +01:00
return components.Message{}, errors.New("ignoring reply events")
2016-09-29 21:19:09 +02:00
}
2018-12-25 15:09:03 +01:00
return s.CreateMessage(msg, channelID), nil
2016-10-09 14:19:28 +02:00
}
2017-07-22 14:36:55 +02:00
// parseMessage will parse a message string and find and replace:
// - emoji's
// - mentions
2019-05-11 10:57:23 +02:00
// - html unescape
2017-07-22 14:36:55 +02:00
func parseMessage(s *SlackService, msg string) string {
2018-08-11 13:19:58 +02:00
if s.Config.Emoji {
msg = parseEmoji(msg)
}
2017-07-22 14:36:55 +02:00
msg = parseMentions(s, msg)
2019-05-11 10:23:21 +02:00
msg = html.UnescapeString(msg)
2017-07-22 14:36:55 +02:00
return msg
}
// parseMentions will try to find mention placeholders in the message
// string and replace them with the correct username with and @ symbol
//
// Mentions have the following format:
// <@U12345|erroneousboat>
// <@U12345>
2017-07-22 14:36:55 +02:00
func parseMentions(s *SlackService, msg string) string {
r := regexp.MustCompile(`\<@(\w+\|*\w+)\>`)
return r.ReplaceAllStringFunc(
msg, func(str string) string {
2017-12-01 13:24:02 +01:00
rs := r.FindStringSubmatch(str)
if len(rs) < 1 {
return str
}
2017-07-22 14:36:55 +02:00
var userID string
split := strings.Split(rs[1], "|")
if len(split) > 0 {
userID = split[0]
} else {
userID = rs[1]
}
name, ok := s.UserCache[userID]
if !ok {
user, err := s.Client.GetUserInfo(userID)
if err != nil {
name = "unknown"
s.UserCache[userID] = name
} else {
name = user.Name
s.UserCache[userID] = user.Name
}
}
if name == "" {
name = "unknown"
}
return "@" + name
},
)
}
// parseEmoji will try to find emoji placeholders in the message
// string and replace them with the correct unicode equivalent
func parseEmoji(msg string) string {
r := regexp.MustCompile("(:\\w+:)")
return r.ReplaceAllStringFunc(
msg, func(str string) string {
code, ok := config.EmojiCodemap[str]
if !ok {
return str
}
return code
},
)
}
2018-09-08 21:16:25 +02:00
func (s *SlackService) createChannelItem(chn slack.Channel) components.ChannelItem {
return components.ChannelItem{
ID: chn.ID,
Name: chn.Name,
Topic: chn.Topic.Value,
UserID: chn.User,
StylePrefix: s.Config.Theme.Channel.Prefix,
StyleIcon: s.Config.Theme.Channel.Icon,
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
}